[tor-commits] [collector/release] Archive snowflake statistics.

karsten at torproject.org karsten at torproject.org
Thu Sep 12 08:58:28 UTC 2019


commit 569bb83ff5e50843079ebd1150801a8bd8efa747
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Wed Aug 14 08:51:29 2019 +0200

    Archive snowflake statistics.
    
    Implements #29461.
---
 CHANGELOG.md                                       |   2 +
 build.xml                                          |   2 +-
 .../org/torproject/metrics/collector/Main.java     |   3 +
 .../metrics/collector/conf/Annotation.java         |   3 +-
 .../metrics/collector/conf/Configuration.java      |   1 +
 .../org/torproject/metrics/collector/conf/Key.java |   8 +-
 .../persist/SnowflakeStatsPersistence.java         |  37 ++++
 .../snowflake/SnowflakeStatsDownloader.java        | 191 +++++++++++++++++++++
 .../metrics/collector/sync/SyncPersistence.java    |   5 +
 src/main/resources/collector.properties            |  20 +++
 src/main/resources/create-tarballs.sh              |   7 +
 src/main/resources/docs/PROTOCOL                   |  33 +++-
 .../metrics/collector/conf/ConfigurationTest.java  |   2 +-
 .../metrics/collector/cron/CollecTorMainTest.java  |   1 +
 .../metrics/collector/cron/SchedulerTest.java      |   9 +-
 15 files changed, 317 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fd0401..c537d22 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,8 @@
      versions resolved by Ivy are the same as in Debian stretch with
      few exceptions.
    - Remove Cobertura from the build process.
+   - Archive snowflake statistics.
+   - Update to metrics-lib 2.7.0.
 
 
 # Changes in version 1.9.1 - 2019-05-29
diff --git a/build.xml b/build.xml
index 39180c7..d1d5b70 100644
--- a/build.xml
+++ b/build.xml
@@ -12,7 +12,7 @@
   <property name="release.version" value="1.9.1-dev" />
   <property name="project-main-class" value="org.torproject.metrics.collector.Main" />
   <property name="name" value="collector"/>
-  <property name="metricslibversion" value="2.6.2" />
+  <property name="metricslibversion" value="2.7.0" />
   <property name="jarincludes" value="collector.properties logback.xml" />
 
   <patternset id="runtime" >
diff --git a/src/main/java/org/torproject/metrics/collector/Main.java b/src/main/java/org/torproject/metrics/collector/Main.java
index 46e93af..6907e93 100644
--- a/src/main/java/org/torproject/metrics/collector/Main.java
+++ b/src/main/java/org/torproject/metrics/collector/Main.java
@@ -14,6 +14,7 @@ import org.torproject.metrics.collector.exitlists.ExitListDownloader;
 import org.torproject.metrics.collector.indexer.CreateIndexJson;
 import org.torproject.metrics.collector.onionperf.OnionPerfDownloader;
 import org.torproject.metrics.collector.relaydescs.ArchiveWriter;
+import org.torproject.metrics.collector.snowflake.SnowflakeStatsDownloader;
 import org.torproject.metrics.collector.webstats.SanitizeWeblogs;
 
 import org.slf4j.Logger;
@@ -53,6 +54,8 @@ public class Main {
     collecTorMains.put(Key.RelaydescsActivated, ArchiveWriter.class);
     collecTorMains.put(Key.OnionPerfActivated, OnionPerfDownloader.class);
     collecTorMains.put(Key.WebstatsActivated, SanitizeWeblogs.class);
+    collecTorMains.put(Key.SnowflakeStatsActivated,
+        SnowflakeStatsDownloader.class);
   }
 
   private static Configuration conf = new Configuration();
diff --git a/src/main/java/org/torproject/metrics/collector/conf/Annotation.java b/src/main/java/org/torproject/metrics/collector/conf/Annotation.java
index 2e47df0..8cd3324 100644
--- a/src/main/java/org/torproject/metrics/collector/conf/Annotation.java
+++ b/src/main/java/org/torproject/metrics/collector/conf/Annotation.java
@@ -18,7 +18,8 @@ public enum Annotation {
   Server("@type server-descriptor 1.0\n"),
   Status("@type bridge-network-status 1.2\n"),
   OnionPerf("@type torperf 1.1\n"),
-  Vote("@type network-status-vote-3 1.0\n");
+  Vote("@type network-status-vote-3 1.0\n"),
+  SnowflakeStats("@type snowflake-stats 1.0\n");
 
   private final String annotation;
   private final byte[] bytes;
diff --git a/src/main/java/org/torproject/metrics/collector/conf/Configuration.java b/src/main/java/org/torproject/metrics/collector/conf/Configuration.java
index 69d3bcd..27f5125 100644
--- a/src/main/java/org/torproject/metrics/collector/conf/Configuration.java
+++ b/src/main/java/org/torproject/metrics/collector/conf/Configuration.java
@@ -91,6 +91,7 @@ public class Configuration extends Observable implements Cloneable {
         || this.getBool(Key.ExitlistsActivated)
         || this.getBool(Key.UpdateindexActivated)
         || this.getBool(Key.OnionPerfActivated)
+        || this.getBool(Key.SnowflakeStatsActivated)
         || this.getBool(Key.WebstatsActivated))) {
       throw new ConfigurationException("Nothing is activated!\n"
           + "Please edit collector.properties. Exiting.");
diff --git a/src/main/java/org/torproject/metrics/collector/conf/Key.java b/src/main/java/org/torproject/metrics/collector/conf/Key.java
index ba4bcd9..e683fe2 100644
--- a/src/main/java/org/torproject/metrics/collector/conf/Key.java
+++ b/src/main/java/org/torproject/metrics/collector/conf/Key.java
@@ -66,7 +66,13 @@ public enum Key {
   WebstatsActivated(Boolean.class),
   WebstatsLimits(Boolean.class),
   WebstatsOffsetMinutes(Integer.class),
-  WebstatsPeriodMinutes(Integer.class);
+  WebstatsPeriodMinutes(Integer.class),
+  SnowflakeStatsActivated(Boolean.class),
+  SnowflakeStatsOffsetMinutes(Integer.class),
+  SnowflakeStatsPeriodMinutes(Integer.class),
+  SnowflakeStatsUrl(URL.class),
+  SnowflakeStatsSources(SourceType[].class),
+  SnowflakeStatsSyncOrigins(URL[].class);
 
   private Class clazz;
   private static Set<String> keys;
diff --git a/src/main/java/org/torproject/metrics/collector/persist/SnowflakeStatsPersistence.java b/src/main/java/org/torproject/metrics/collector/persist/SnowflakeStatsPersistence.java
new file mode 100644
index 0000000..ee6e029
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/collector/persist/SnowflakeStatsPersistence.java
@@ -0,0 +1,37 @@
+/* Copyright 2019 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.collector.persist;
+
+import org.torproject.descriptor.SnowflakeStats;
+import org.torproject.metrics.collector.conf.Annotation;
+
+import java.nio.file.Paths;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+
+public class SnowflakeStatsPersistence
+    extends DescriptorPersistence<SnowflakeStats> {
+
+  private static final String SNOWFLAKES = "snowflakes";
+
+  public SnowflakeStatsPersistence(SnowflakeStats desc) {
+    super(desc, Annotation.SnowflakeStats.bytes());
+    calculatePaths();
+  }
+
+  private void calculatePaths() {
+    DateTimeFormatter directoriesFormatter = DateTimeFormatter
+        .ofPattern("uuuu/MM/dd").withZone(ZoneOffset.UTC);
+    String[] directories = this.desc.snowflakeStatsEnd()
+        .format(directoriesFormatter).split("/");
+    DateTimeFormatter fileFormatter = DateTimeFormatter
+        .ofPattern("uuuu-MM-dd-HH-mm-ss").withZone(ZoneOffset.UTC);
+    String fileOut = this.desc.snowflakeStatsEnd().format(fileFormatter)
+        + "-snowflake-stats";
+    this.recentPath = Paths.get(SNOWFLAKES, fileOut).toString();
+    this.storagePath = Paths.get(SNOWFLAKES, directories[0], directories[1],
+        directories[2], fileOut).toString();
+  }
+}
+
diff --git a/src/main/java/org/torproject/metrics/collector/snowflake/SnowflakeStatsDownloader.java b/src/main/java/org/torproject/metrics/collector/snowflake/SnowflakeStatsDownloader.java
new file mode 100644
index 0000000..4f7994e
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/collector/snowflake/SnowflakeStatsDownloader.java
@@ -0,0 +1,191 @@
+/* Copyright 2019 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.collector.snowflake;
+
+import org.torproject.descriptor.Descriptor;
+import org.torproject.descriptor.DescriptorParser;
+import org.torproject.descriptor.DescriptorSourceFactory;
+import org.torproject.descriptor.SnowflakeStats;
+import org.torproject.metrics.collector.conf.Annotation;
+import org.torproject.metrics.collector.conf.Configuration;
+import org.torproject.metrics.collector.conf.ConfigurationException;
+import org.torproject.metrics.collector.conf.Key;
+import org.torproject.metrics.collector.cron.CollecTorMain;
+import org.torproject.metrics.collector.persist.SnowflakeStatsPersistence;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.SortedSet;
+import java.util.Stack;
+import java.util.TreeSet;
+
+public class SnowflakeStatsDownloader extends CollecTorMain {
+
+  private static final Logger logger = LoggerFactory.getLogger(
+      SnowflakeStatsDownloader.class);
+
+  private String recentPathName;
+
+  /** Instantiate the snowflake-stats module using the given configuration. */
+  public SnowflakeStatsDownloader(Configuration config) {
+    super(config);
+    this.mapPathDescriptors.put("recent/snowflakes", SnowflakeStats.class);
+  }
+
+  @Override
+  public String module() {
+    return "SnowflakeStats";
+  }
+
+  @Override
+  protected String syncMarker() {
+    return "SnowflakeStats";
+  }
+
+  @Override
+  protected void startProcessing() throws ConfigurationException {
+
+    this.recentPathName = config.getPath(Key.RecentPath).toString();
+    logger.debug("Downloading snowflake stats...");
+    URL url = config.getUrl(Key.SnowflakeStatsUrl);
+    ByteArrayOutputStream downloadedSnowflakeStats
+        = this.downloadFromHttpServer(url);
+    if (null == downloadedSnowflakeStats) {
+      return;
+    }
+    logger.debug("Finished downloading {}.", url);
+
+    DescriptorParser descriptorParser =
+        DescriptorSourceFactory.createDescriptorParser();
+    SortedSet<LocalDateTime> snowflakeStatsEnds = new TreeSet<>();
+    String outputPathName = config.getPath(Key.OutputPath).toString();
+    for (Descriptor descriptor : descriptorParser.parseDescriptors(
+        downloadedSnowflakeStats.toByteArray(), null, null)) {
+      if (descriptor instanceof SnowflakeStats) {
+        SnowflakeStats snowflakeStats = (SnowflakeStats) descriptor;
+        LocalDateTime snowflakeStatsEnd = snowflakeStats.snowflakeStatsEnd();
+        snowflakeStatsEnds.add(snowflakeStatsEnd);
+        SnowflakeStatsPersistence persistence
+            = new SnowflakeStatsPersistence(snowflakeStats);
+        File tarballFile = new File(outputPathName + "/"
+            + persistence.getStoragePath());
+        if (tarballFile.exists()) {
+          continue;
+        }
+        File rsyncFile = new File(this.recentPathName + "/"
+            + persistence.getRecentPath());
+        File[] outputFiles = new File[] { tarballFile, rsyncFile };
+        for (File outputFile : outputFiles) {
+          this.writeToFile(outputFile, Annotation.SnowflakeStats.bytes(),
+              snowflakeStats.getRawDescriptorBytes());
+        }
+      }
+    }
+    if (snowflakeStatsEnds.isEmpty()) {
+      logger.warn("Could not parse downloaded snowflake stats.");
+      return;
+    } else if (snowflakeStatsEnds.last().isBefore(LocalDateTime.now()
+        .minusHours(48L))) {
+      logger.warn("The latest snowflake stats are older than 48 hours: {}.",
+          snowflakeStatsEnds.last());
+    }
+
+    this.cleanUpRsyncDirectory();
+  }
+
+  /**
+   * Download the given URL from an HTTP server and return a stream with
+   * downloaded bytes.
+   *
+   * <p>If anything goes wrong while downloading, log a warning and return
+   * {@code null}.</p>
+   *
+   * @param url URL to download.
+   * @return Stream with downloaded bytes, or {@code null} if an error has
+   *     occurred.
+   */
+  private ByteArrayOutputStream downloadFromHttpServer(URL url) {
+    ByteArrayOutputStream downloadedBytes = new ByteArrayOutputStream();
+    try  {
+      HttpURLConnection huc = (HttpURLConnection) url.openConnection();
+      huc.setRequestMethod("GET");
+      huc.setReadTimeout(5000);
+      huc.connect();
+      int response = huc.getResponseCode();
+      if (response != 200) {
+        logger.warn("Could not download {}. Response code {}", url, response);
+        return null;
+      }
+      try (BufferedInputStream in = new BufferedInputStream(
+          huc.getInputStream())) {
+        int len;
+        byte[] data = new byte[1024];
+        while ((len = in.read(data, 0, 1024)) >= 0) {
+          downloadedBytes.write(data, 0, len);
+        }
+      }
+    } catch (IOException e) {
+      logger.warn("Failed downloading {}.", url, e);
+      return null;
+    }
+    return downloadedBytes;
+  }
+
+  /**
+   * Write the given byte array(s) to the given file.
+   *
+   * <p>If the file already exists, it is overwritten. If the parent directory
+   * (or any of its parent directories) does not exist, it is created. If
+   * anything goes wrong, log a warning and return.</p>
+   *
+   * @param outputFile File to write to.
+   * @param bytes One or more byte arrays.
+   */
+  private void writeToFile(File outputFile, byte[] ... bytes) {
+    try {
+      if (!outputFile.getParentFile().exists()
+          && !outputFile.getParentFile().mkdirs()) {
+        logger.warn("Could not create parent directories of {}.", outputFile);
+        return;
+      }
+      OutputStream os = new FileOutputStream(outputFile);
+      for (byte[] b : bytes) {
+        os.write(b);
+      }
+      os.close();
+    } catch (IOException e) {
+      logger.warn("Could not write downloaded snowflake stats to {}",
+          outputFile.getAbsolutePath(), e);
+    }
+  }
+
+  /** Delete all files from the rsync directory that have not been modified
+   * in the last three days. */
+  public void cleanUpRsyncDirectory() {
+    long cutOffMillis = System.currentTimeMillis()
+        - 3L * 24L * 60L * 60L * 1000L;
+    Stack<File> allFiles = new Stack<>();
+    allFiles.add(new File(recentPathName));
+    while (!allFiles.isEmpty()) {
+      File file = allFiles.pop();
+      if (file.isDirectory()) {
+        allFiles.addAll(Arrays.asList(file.listFiles()));
+      } else if (file.lastModified() < cutOffMillis) {
+        file.delete();
+      }
+    }
+  }
+}
+
diff --git a/src/main/java/org/torproject/metrics/collector/sync/SyncPersistence.java b/src/main/java/org/torproject/metrics/collector/sync/SyncPersistence.java
index 0d344bf..4b3b7bc 100644
--- a/src/main/java/org/torproject/metrics/collector/sync/SyncPersistence.java
+++ b/src/main/java/org/torproject/metrics/collector/sync/SyncPersistence.java
@@ -13,6 +13,7 @@ import org.torproject.descriptor.RelayExtraInfoDescriptor;
 import org.torproject.descriptor.RelayNetworkStatusConsensus;
 import org.torproject.descriptor.RelayNetworkStatusVote;
 import org.torproject.descriptor.RelayServerDescriptor;
+import org.torproject.descriptor.SnowflakeStats;
 import org.torproject.descriptor.TorperfResult;
 import org.torproject.descriptor.WebServerAccessLog;
 import org.torproject.metrics.collector.conf.Configuration;
@@ -29,6 +30,7 @@ import org.torproject.metrics.collector.persist.MicroConsensusPersistence;
 import org.torproject.metrics.collector.persist.OnionPerfPersistence;
 import org.torproject.metrics.collector.persist.PersistenceUtils;
 import org.torproject.metrics.collector.persist.ServerDescriptorPersistence;
+import org.torproject.metrics.collector.persist.SnowflakeStatsPersistence;
 import org.torproject.metrics.collector.persist.StatusPersistence;
 import org.torproject.metrics.collector.persist.VotePersistence;
 import org.torproject.metrics.collector.persist.WebServerAccessLogPersistence;
@@ -143,6 +145,9 @@ public class SyncPersistence {
         case "BandwidthFile":
           descPersist = new BandwidthFilePersistence((BandwidthFile) desc);
           break;
+        case "SnowflakeStats":
+          descPersist = new SnowflakeStatsPersistence((SnowflakeStats) desc);
+          break;
         default:
           log.trace("Invalid descriptor type {} for sync-merge.",
               clazz.getName());
diff --git a/src/main/resources/collector.properties b/src/main/resources/collector.properties
index 292e876..a4eed7a 100644
--- a/src/main/resources/collector.properties
+++ b/src/main/resources/collector.properties
@@ -47,6 +47,13 @@ WebstatsActivated = false
 WebstatsPeriodMinutes = 360
 # offset in minutes since the epoch and
 WebstatsOffsetMinutes = 31
+# the following defines, if this module is activated
+SnowflakeStatsActivated = false
+# period in minutes
+SnowflakeStatsPeriodMinutes = 480
+# offset in minutes since the epoch and
+SnowflakeStatsOffsetMinutes = 100
+
 ##########################################
 ## All below can be changed at runtime.
 #####
@@ -178,3 +185,16 @@ WebstatsLocalOrigins = in/webstats
 # Default 'true' behaves as stated in section 4 of
 # https://metrics.torproject.org/web-server-logs.html
 WebstatsLimits = true
+#
+#
+######## Snowflake statistics ########
+#
+## Define descriptor sources
+#  possible values: Sync, Remote
+SnowflakeStatsSources = Remote
+##  Retrieve files from the following instances.
+##  List of URLs separated by comma.
+SnowflakeStatsSyncOrigins = https://collector.torproject.org
+## Where to download snowflake statistics from.
+SnowflakeStatsUrl = https://snowflake-broker.torproject.net/metrics
+#
diff --git a/src/main/resources/create-tarballs.sh b/src/main/resources/create-tarballs.sh
index 7e4668a..50b7fdb 100755
--- a/src/main/resources/create-tarballs.sh
+++ b/src/main/resources/create-tarballs.sh
@@ -59,6 +59,8 @@ TARBALLS=(
   bridge-server-descriptors-$YEARTWO-$MONTHTWO
   bridge-extra-infos-$YEARONE-$MONTHONE
   bridge-extra-infos-$YEARTWO-$MONTHTWO
+  snowflakes-$YEARONE-$MONTHONE
+  snowflakes-$YEARTWO-$MONTHTWO
 )
 TARBALLS=($(printf "%s\n" "${TARBALLS[@]}" | uniq))
 
@@ -86,6 +88,8 @@ DIRECTORIES=(
   $OUTDIR/bridge-descriptors/$YEARTWO/$MONTHTWO/server-descriptors/
   $OUTDIR/bridge-descriptors/$YEARONE/$MONTHONE/extra-infos/
   $OUTDIR/bridge-descriptors/$YEARTWO/$MONTHTWO/extra-infos/
+  $OUTDIR/snowflakes/$YEARONE/$MONTHONE/
+  $OUTDIR/snowflakes/$YEARTWO/$MONTHTWO/
 )
 DIRECTORIES=($(printf "%s\n" "${DIRECTORIES[@]}" | uniq))
 
@@ -169,4 +173,7 @@ ln -f -s -t $ARCHIVEDIR/torperf/ $TARBALLTARGETDIR/torperf-20??-??.tar.xz
 mkdir -p $ARCHIVEDIR/webstats/
 ln -f -s -t $ARCHIVEDIR/webstats/ $TARBALLTARGETDIR/webstats-20??-??.tar
 
+mkdir -p $ARCHIVEDIR/snowflakes/
+ln -f -s -t $ARCHIVEDIR/snowflakes/ $TARBALLTARGETDIR/snowflakes-20??-??.tar.xz
+
 echo `date` "Finished."
diff --git a/src/main/resources/docs/PROTOCOL b/src/main/resources/docs/PROTOCOL
index 58ed4dc..478f168 100644
--- a/src/main/resources/docs/PROTOCOL
+++ b/src/main/resources/docs/PROTOCOL
@@ -45,6 +45,7 @@
    * exit-lists
    * relay-descriptors
    * torperf
+   * snowflakes
 
    The substructure of these folders differs depending on their content.
 
@@ -116,6 +117,13 @@
    * for bandwidths, from the file_created value if available, otherwise the
      timestamp.
 
+2.5 'snowflakes' below 'archive'
+
+   'snowflakes' contains compressed tarballs with snowflake statistics,
+   named in the following way:
+
+   'snowflakes' DASH year DASH month DOT TAR DOT compression-type
+
 3.0 Index Files
 
    The index.json file and its compressed versions of various types are
@@ -132,6 +140,7 @@
    * exit-lists
    * relay-descriptors
    * torperf
+   * snowflakes
 
 4.1 'exit-lists' and 'torperf' below 'recent'
 
@@ -254,6 +263,16 @@
    'webstats' contains compressed log files named according to
    the 'Tor web server logs' specification, section 4.3 [0].
 
+4.5 'snowflakes' below 'recent'
+
+   'snowflakes' contains files named
+
+   year DASH month DASH day DASH hour DASH minute DASH second
+   DASH SNOWFLAKESTATS
+
+   Where SNOWFLAKESTATS is the string "snowflake-stats" and all time related
+   values are derived from the snowflake statistics interval end.
+
 5.0 The Tar-ball's Directory Structure and the Internal Structure
 
    The 'out' directory is the internal storage that is used for
@@ -266,8 +285,9 @@
    * exit-lists
    * relay-descriptors
    * torperf
+   * snowflakes
 
-   (There has been a fifth subdirectory 'bridge-pool-assignments' which has been
+   (There has been another subdirectory 'bridge-pool-assignments' which has been
    removed when CollecTor stopped collecting those descriptors.  However, it's
    structure can still be found in the tarballs.)
 
@@ -416,6 +436,17 @@
    'webstats' contains compressed log files structured and named according
    to the 'Tor web server logs' specification, section 4.3 [0].
 
+5.5 'snowflakes' below 'out'
+
+   'snowflakes' contains the subdirectory structure
+
+   year SEP month SEP day
+
+   Where the time related values are taken from the snowflake statistics
+   interval end.
+
+   The files are named according to the structure in 4.5.
+
 6.0 The 'contrib' Folder
 
    The 'contrib' folder contains third-party contributions.
diff --git a/src/test/java/org/torproject/metrics/collector/conf/ConfigurationTest.java b/src/test/java/org/torproject/metrics/collector/conf/ConfigurationTest.java
index 4ac623e..3a69c0c 100644
--- a/src/test/java/org/torproject/metrics/collector/conf/ConfigurationTest.java
+++ b/src/test/java/org/torproject/metrics/collector/conf/ConfigurationTest.java
@@ -38,7 +38,7 @@ public class ConfigurationTest {
   public void testKeyCount() {
     assertEquals("The number of properties keys in enum Key changed."
         + "\n This test class should be adapted.",
-        53, Key.values().length);
+        59, Key.values().length);
   }
 
   @Test()
diff --git a/src/test/java/org/torproject/metrics/collector/cron/CollecTorMainTest.java b/src/test/java/org/torproject/metrics/collector/cron/CollecTorMainTest.java
index 78b6ac7..d0fe173 100644
--- a/src/test/java/org/torproject/metrics/collector/cron/CollecTorMainTest.java
+++ b/src/test/java/org/torproject/metrics/collector/cron/CollecTorMainTest.java
@@ -72,6 +72,7 @@ public class CollecTorMainTest {
         case "Exitlist":
         case "OnionPerf":
         case "Webstats":
+        case "SnowflakeStats":
           assertNotNull("Property '" + key
               + "' not specified in " + Main.CONF_FILE + ".",
               props.getProperty(key));
diff --git a/src/test/java/org/torproject/metrics/collector/cron/SchedulerTest.java b/src/test/java/org/torproject/metrics/collector/cron/SchedulerTest.java
index e0496fb..3f20646 100644
--- a/src/test/java/org/torproject/metrics/collector/cron/SchedulerTest.java
+++ b/src/test/java/org/torproject/metrics/collector/cron/SchedulerTest.java
@@ -31,7 +31,9 @@ public class SchedulerTest {
       + "UpdateindexActivated=true\nUpdateindexPeriodMinutes=1\n"
       + "UpdateindexOffsetMinutes=0\n"
       + "BridgedescsActivated=true\nBridgedescsPeriodMinutes=1\n"
-      + "BridgedescsOffsetMinutes=0\n";
+      + "BridgedescsOffsetMinutes=0\n"
+      + "SnowflakeStatsActivated=true\nSnowflakeStatsPeriodMinutes=1\n"
+      + "SnowflakeStatsOffsetMinutes=0\n";
 
   @Test()
   public void testSimpleSchedule() throws Exception {
@@ -42,6 +44,7 @@ public class SchedulerTest {
     ctms.put(Key.BridgedescsActivated, Dummy.class);
     ctms.put(Key.RelaydescsActivated, Dummy.class);
     ctms.put(Key.ExitlistsActivated, Dummy.class);
+    ctms.put(Key.SnowflakeStatsActivated, Dummy.class);
     ctms.put(Key.UpdateindexActivated, Dummy.class);
     Field schedulerField = Scheduler.class.getDeclaredField("scheduler");
     schedulerField.setAccessible(true);
@@ -74,6 +77,7 @@ public class SchedulerTest {
     ctms.put(Key.BridgedescsActivated, Counter.class);
     ctms.put(Key.RelaydescsActivated, Counter.class);
     ctms.put(Key.ExitlistsActivated, Counter.class);
+    ctms.put(Key.SnowflakeStatsActivated, Counter.class);
     ctms.put(Key.UpdateindexActivated, Counter.class);
     conf.setProperty(Key.BridgeSources.name(), "Local");
     conf.setProperty(Key.RelaySources.name(), "Remote");
@@ -84,7 +88,7 @@ public class SchedulerTest {
         schedulerField.get(Scheduler.getInstance());
     Scheduler.getInstance().scheduleModuleRuns(ctms, conf);
     Scheduler.getInstance().shutdownScheduler();
-    assertEquals(5, Counter.count.get());
+    assertEquals(6, Counter.count.get());
   }
 
   @Ignore("This test takes 180 seconds, which is too long.")
@@ -97,6 +101,7 @@ public class SchedulerTest {
     ctms.put(Key.BridgedescsActivated, Broken.class);
     ctms.put(Key.RelaydescsActivated, Broken.class);
     ctms.put(Key.ExitlistsActivated, Broken.class);
+    ctms.put(Key.SnowflakeStatsActivated, Broken.class);
     ctms.put(Key.UpdateindexActivated, Broken.class);
     Field schedulerField = Scheduler.class.getDeclaredField("scheduler");
     schedulerField.setAccessible(true);





More information about the tor-commits mailing list