diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9bbedc4..a55a0fc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changes in version 1.1?.? - 2020-0?-??
+ * Medium changes
+ - Update to metrics-lib 2.12.1.
+ - Download OnionPerf analysis .json files in addition to .tpf
+ files.
+
* Minor changes
- Simplify logging configuration.
- Set default locale `US` and default time zone `UTC` at the
diff --git a/build.xml b/build.xml
index 748351e..a9988f5 100644
--- a/build.xml
+++ b/build.xml
@@ -12,7 +12,7 @@
-
+
diff --git a/src/main/java/org/torproject/metrics/collector/onionperf/OnionPerfDownloader.java b/src/main/java/org/torproject/metrics/collector/onionperf/OnionPerfDownloader.java
index b651620..d22ac0b 100644
--- a/src/main/java/org/torproject/metrics/collector/onionperf/OnionPerfDownloader.java
+++ b/src/main/java/org/torproject/metrics/collector/onionperf/OnionPerfDownloader.java
@@ -13,6 +13,7 @@ import org.torproject.metrics.collector.conf.Key;
import org.torproject.metrics.collector.cron.CollecTorMain;
import org.torproject.metrics.collector.downloader.Downloader;
+import org.apache.commons.compress.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,14 +33,16 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-/** Download download .tpf files from OnionPerf hosts. */
+/** Download OnionPerf files from OnionPerf hosts. */
public class OnionPerfDownloader extends CollecTorMain {
private static final Logger logger = LoggerFactory.getLogger(
@@ -47,6 +50,8 @@ public class OnionPerfDownloader extends CollecTorMain {
private static final String TORPERF = "torperf";
+ private static final String ONIONPERF = "onionperf";
+
/** Instantiate the OnionPerf module using the given configuration. */
public OnionPerfDownloader(Configuration config) {
super(config);
@@ -54,21 +59,25 @@ public class OnionPerfDownloader extends CollecTorMain {
}
/** File containing the download history, which is necessary, because
- * OnionPerf does not delete older .tpf files, but which enables us to do
- * so. */
+ * OnionPerf does not delete older files, but which enables us to do so. */
private File onionPerfDownloadedFile;
- /** Full URLs of .tpf files downloaded in the current or in past
- * executions. */
- private SortedSet downloadedTpfFiles = new TreeSet<>();
+ /** Full URLs of files downloaded in the current or in past executions. */
+ private SortedSet downloadedFiles = new TreeSet<>();
/** Base URLs of configured OnionPerf hosts. */
private URL[] onionPerfHosts = null;
- /** Directory for storing archived .tpf files. */
+ /** Relative URLs of available .tpf files by base URL. */
+ private Map> tpfFileUrls = new HashMap<>();
+
+ /** Relative URLs of available OnionPerf analysis files by base URL. */
+ private Map> onionPerfAnalysisFileUrls = new HashMap<>();
+
+ /** Directory for storing archived files. */
private File archiveDirectory = null;
- /** Directory for storing recent .tpf files. */
+ /** Directory for storing recent files. */
private File recentDirectory = null;
@Override
@@ -87,19 +96,17 @@ public class OnionPerfDownloader extends CollecTorMain {
new File(config.getPath(Key.StatsPath).toFile(),
"onionperf-downloaded");
this.onionPerfHosts = config.getUrlArray(Key.OnionPerfHosts);
- this.readDownloadedOnionPerfTpfFiles();
- this.archiveDirectory = new File(config.getPath(Key.OutputPath).toFile(),
- TORPERF);
- this.recentDirectory = new File(config.getPath(Key.RecentPath).toFile(),
- TORPERF);
+ this.readDownloadedOnionPerfFiles();
+ this.archiveDirectory = config.getPath(Key.OutputPath).toFile();
+ this.recentDirectory = config.getPath(Key.RecentPath).toFile();
for (URL baseUrl : this.onionPerfHosts) {
this.downloadFromOnionPerfHost(baseUrl);
}
- this.writeDownloadedOnionPerfTpfFiles();
+ this.writeDownloadedOnionPerfFiles();
this.cleanUpRsyncDirectory();
}
- private void readDownloadedOnionPerfTpfFiles() {
+ private void readDownloadedOnionPerfFiles() {
if (!this.onionPerfDownloadedFile.exists()) {
return;
}
@@ -107,47 +114,69 @@ public class OnionPerfDownloader extends CollecTorMain {
this.onionPerfDownloadedFile))) {
String line;
while ((line = br.readLine()) != null) {
- this.downloadedTpfFiles.add(line);
+ this.downloadedFiles.add(line);
}
} catch (IOException e) {
logger.info("Unable to read download history file '{}'. Ignoring "
- + "download history and downloading all available .tpf files.",
+ + "download history and downloading all available files.",
this.onionPerfDownloadedFile.getAbsolutePath());
- this.downloadedTpfFiles.clear();
+ this.downloadedFiles.clear();
}
}
private void downloadFromOnionPerfHost(URL baseUrl) {
logger.info("Downloading from OnionPerf host {}", baseUrl);
- List tpfFileNames =
- this.downloadOnionPerfDirectoryListing(baseUrl);
+ this.downloadOnionPerfDirectoryListing(baseUrl);
String source = baseUrl.getHost().split("\\.")[0];
- for (String tpfFileName : tpfFileNames) {
- this.downloadAndParseOnionPerfTpfFile(baseUrl, source, tpfFileName);
+ if (this.tpfFileUrls.containsKey(baseUrl)) {
+ for (String tpfFileName : this.tpfFileUrls.get(baseUrl)) {
+ this.downloadAndParseOnionPerfTpfFile(baseUrl, source, tpfFileName);
+ }
+ }
+ if (this.onionPerfAnalysisFileUrls.containsKey(baseUrl)) {
+ for (String onionPerfAnalysisFileName
+ : this.onionPerfAnalysisFileUrls.get(baseUrl)) {
+ this.downloadAndParseOnionPerfAnalysisFile(baseUrl, source,
+ onionPerfAnalysisFileName);
+ }
}
}
- /** Pattern for links contained in directory listings. */
+ /** Patterns for links contained in directory listings. */
private static final Pattern TPF_FILE_URL_PATTERN =
Pattern.compile(".*.*");
- private List downloadOnionPerfDirectoryListing(URL baseUrl) {
- List tpfFileUrls = new ArrayList<>();
+ private static final Pattern ONIONPERF_ANALYSIS_FILE_URL_PATTERN =
+ Pattern.compile(
+ ".*.*");
+
+ private void downloadOnionPerfDirectoryListing(URL baseUrl) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(
baseUrl.openStream()))) {
String line;
while ((line = br.readLine()) != null) {
- Matcher matcher = TPF_FILE_URL_PATTERN.matcher(line);
- if (matcher.matches() && !matcher.group(1).startsWith("/")) {
- tpfFileUrls.add(matcher.group(1));
+ Matcher tpfFileMatcher = TPF_FILE_URL_PATTERN.matcher(line);
+ if (tpfFileMatcher.matches()
+ && !tpfFileMatcher.group(1).startsWith("/")) {
+ this.tpfFileUrls.putIfAbsent(baseUrl, new ArrayList<>());
+ this.tpfFileUrls.get(baseUrl).add(tpfFileMatcher.group(1));
+ }
+ Matcher onionPerfAnalysisFileMatcher
+ = ONIONPERF_ANALYSIS_FILE_URL_PATTERN.matcher(line);
+ if (onionPerfAnalysisFileMatcher.matches()
+ && !onionPerfAnalysisFileMatcher.group(1).startsWith("/")) {
+ this.onionPerfAnalysisFileUrls.putIfAbsent(baseUrl,
+ new ArrayList<>());
+ this.onionPerfAnalysisFileUrls.get(baseUrl)
+ .add(onionPerfAnalysisFileMatcher.group(1));
}
}
} catch (IOException e) {
logger.warn("Unable to download directory listing from '{}'. Skipping "
+ "this OnionPerf host.", baseUrl);
- tpfFileUrls.clear();
+ this.tpfFileUrls.remove(baseUrl);
+ this.onionPerfAnalysisFileUrls.remove(baseUrl);
}
- return tpfFileUrls;
}
private static final DateFormat DATE_FORMAT;
@@ -169,7 +198,7 @@ public class OnionPerfDownloader extends CollecTorMain {
}
/* Skip if we successfully downloaded this file before. */
- if (this.downloadedTpfFiles.contains(tpfFileUrl.toString())) {
+ if (this.downloadedFiles.contains(tpfFileUrl.toString())) {
return;
}
@@ -197,7 +226,8 @@ public class OnionPerfDownloader extends CollecTorMain {
}
/* Download file contents to temporary file. */
- File tempFile = new File(this.recentDirectory, "." + tpfFileName);
+ File tempFile = new File(this.recentDirectory,
+ TORPERF + "/." + tpfFileName);
byte[] downloadedBytes;
try {
downloadedBytes = Downloader.downloadFromHttpServer(
@@ -263,7 +293,7 @@ public class OnionPerfDownloader extends CollecTorMain {
/* Copy/move files in place. */
File archiveFile = new File(this.archiveDirectory,
- date.replaceAll("-", "/") + "/" + tpfFileName);
+ TORPERF + "/" + date.replaceAll("-", "/") + "/" + tpfFileName);
archiveFile.getParentFile().mkdirs();
try {
Files.copy(tempFile.toPath(), archiveFile.toPath(),
@@ -274,18 +304,132 @@ public class OnionPerfDownloader extends CollecTorMain {
tempFile.delete();
return;
}
- File recentFile = new File(this.recentDirectory, tpfFileName);
+ File recentFile = new File(this.recentDirectory,
+ TORPERF + "/" + tpfFileName);
tempFile.renameTo(recentFile);
/* Add to download history to avoid downloading it again. */
- this.downloadedTpfFiles.add(baseUrl + tpfFileName);
+ this.downloadedFiles.add(baseUrl + tpfFileName);
}
- private void writeDownloadedOnionPerfTpfFiles() {
+
+ private void downloadAndParseOnionPerfAnalysisFile(URL baseUrl, String source,
+ String onionPerfAnalysisFileName) {
+ URL onionPerfAnalysisFileUrl;
+ try {
+ onionPerfAnalysisFileUrl = new URL(baseUrl, onionPerfAnalysisFileName);
+ } catch (MalformedURLException e1) {
+ logger.warn("Unable to put together base URL '{}' and file path '{}' to "
+ + "a URL. Skipping.", baseUrl, onionPerfAnalysisFileName);
+ return;
+ }
+
+ /* Skip if we successfully downloaded this file before. */
+ if (this.downloadedFiles.contains(onionPerfAnalysisFileUrl.toString())) {
+ return;
+ }
+
+ /* Parse date from file name: yyyy-MM-dd.onionperf.analysis.json.xz */
+ String date;
+ try {
+ date = onionPerfAnalysisFileName.substring(0, 10);
+ DATE_FORMAT.parse(date);
+ } catch (NumberFormatException | ParseException e) {
+ logger.warn("Invalid file name '{}{}'. Skipping.", baseUrl,
+ onionPerfAnalysisFileName, e);
+ return;
+ }
+
+ /* Download file contents to temporary file. */
+ File tempFile = new File(this.recentDirectory,
+ ONIONPERF + "/." + onionPerfAnalysisFileName);
+ byte[] downloadedBytes;
+ try {
+ downloadedBytes = Downloader.downloadFromHttpServer(
+ new URL(baseUrl + onionPerfAnalysisFileName));
+ } catch (IOException e) {
+ logger.warn("Unable to download '{}{}'. Skipping.", baseUrl,
+ onionPerfAnalysisFileName, e);
+ return;
+ }
+ if (null == downloadedBytes) {
+ logger.warn("Unable to download '{}{}'. Skipping.", baseUrl,
+ onionPerfAnalysisFileName);
+ return;
+ }
+ tempFile.getParentFile().mkdirs();
+ try {
+ Files.write(tempFile.toPath(), downloadedBytes);
+ } catch (IOException e) {
+ logger.warn("Unable to write previously downloaded '{}{}' to temporary "
+ + "file '{}'. Skipping.", baseUrl, onionPerfAnalysisFileName,
+ tempFile, e);
+ return;
+ }
+
+ /* Validate contained descriptors. */
+ DescriptorParser descriptorParser =
+ DescriptorSourceFactory.createDescriptorParser();
+ byte[] rawDescriptorBytes;
+ try {
+ rawDescriptorBytes = IOUtils.toByteArray(
+ Files.newInputStream(tempFile.toPath()));
+ } catch (IOException e) {
+ logger.warn("OnionPerf file '{}{}' could not be read. Skipping.", baseUrl,
+ onionPerfAnalysisFileName, e);
+ tempFile.delete();
+ return;
+ }
+ Iterable descriptors = descriptorParser.parseDescriptors(
+ rawDescriptorBytes, null, onionPerfAnalysisFileName);
+ String message = null;
+ for (Descriptor descriptor : descriptors) {
+ if (!(descriptor instanceof TorperfResult)) {
+ message = "File contains descriptors other than an OnionPerf analysis "
+ + "document: " + descriptor.getClass();
+ break;
+ }
+ TorperfResult torperf = (TorperfResult) descriptor;
+ if (!source.equals(torperf.getSource())) {
+ message = "File contains transfer from another source: "
+ + torperf.getSource();
+ break;
+ }
+ }
+ if (null != message) {
+ logger.warn("OnionPerf file '{}{}' was found to be invalid: {}. "
+ + "Skipping.", baseUrl, onionPerfAnalysisFileName, message);
+ tempFile.delete();
+ return;
+ }
+
+ /* Copy/move files in place. */
+ File archiveFile = new File(this.archiveDirectory,
+ ONIONPERF + "/" + date.replaceAll("-", "/") + "/" + date + "." + source
+ + ".onionperf.analysis.json.xz");
+ archiveFile.getParentFile().mkdirs();
+ try {
+ Files.copy(tempFile.toPath(), archiveFile.toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ logger.warn("Unable to copy OnionPerf file {} to {}. Skipping.",
+ tempFile, archiveFile, e);
+ tempFile.delete();
+ return;
+ }
+ File recentFile = new File(this.recentDirectory,
+ ONIONPERF + "/" + date + "." + source + ".onionperf.analysis.json.xz");
+ tempFile.renameTo(recentFile);
+
+ /* Add to download history to avoid downloading it again. */
+ this.downloadedFiles.add(baseUrl + onionPerfAnalysisFileName);
+ }
+
+ private void writeDownloadedOnionPerfFiles() {
this.onionPerfDownloadedFile.getParentFile().mkdirs();
try (BufferedWriter bw = new BufferedWriter(new FileWriter(
this.onionPerfDownloadedFile))) {
- for (String line : this.downloadedTpfFiles) {
+ for (String line : this.downloadedFiles) {
bw.write(line);
bw.newLine();
}
diff --git a/src/main/resources/collector.properties b/src/main/resources/collector.properties
index 61baed5..2347021 100644
--- a/src/main/resources/collector.properties
+++ b/src/main/resources/collector.properties
@@ -175,7 +175,7 @@ ExitlistUrl = https://check.torproject.org/exit-addresses
######## OnionPerf downloader ########
#
## Define descriptor sources
-# possible values: Remote,Sync
+# possible values: Remote,Sync (.tpf files only!)
OnionPerfSources = Remote
# Retrieve files from the following CollecTor instances.
# List of URLs separated by comma.
diff --git a/src/main/resources/create-tarballs.sh b/src/main/resources/create-tarballs.sh
index 07952c7..fcac2f3 100755
--- a/src/main/resources/create-tarballs.sh
+++ b/src/main/resources/create-tarballs.sh
@@ -40,6 +40,8 @@ TARBALLS=(
exit-list-$YEARTWO-$MONTHTWO
torperf-$YEARONE-$MONTHONE
torperf-$YEARTWO-$MONTHTWO
+ onionperf-$YEARONE-$MONTHONE
+ onionperf-$YEARTWO-$MONTHTWO
certs
microdescs-$YEARONE-$MONTHONE
microdescs-$YEARTWO-$MONTHTWO
@@ -73,6 +75,8 @@ DIRECTORIES=(
$OUTDIR/exit-lists/$YEARTWO/$MONTHTWO/
$OUTDIR/torperf/$YEARONE/$MONTHONE/
$OUTDIR/torperf/$YEARTWO/$MONTHTWO/
+ $OUTDIR/onionperf/$YEARONE/$MONTHONE/
+ $OUTDIR/onionperf/$YEARTWO/$MONTHTWO/
$OUTDIR/relay-descriptors/certs/
$OUTDIR/relay-descriptors/microdesc/$YEARONE/$MONTHONE
$OUTDIR/relay-descriptors/microdesc/$YEARTWO/$MONTHTWO
@@ -178,6 +182,9 @@ ln -f -s -t $ARCHIVEDIR/relay-descriptors/bandwidths/ $TARBALLTARGETDIR/bandwidt
mkdir -p $ARCHIVEDIR/torperf/
ln -f -s -t $ARCHIVEDIR/torperf/ $TARBALLTARGETDIR/torperf-20??-??.tar.xz
+mkdir -p $ARCHIVEDIR/onionperf/
+ln -f -s -t $ARCHIVEDIR/onionperf/ $TARBALLTARGETDIR/onionperf-20??-??.tar.xz
+
mkdir -p $ARCHIVEDIR/webstats/
ln -f -s -t $ARCHIVEDIR/webstats/ $TARBALLTARGETDIR/webstats-20??-??.tar