[tor-commits] [metrics-web/release] Run modules from Java only.

karsten at torproject.org karsten at torproject.org
Sat Nov 9 21:45:07 UTC 2019


commit 829863f48fb4e624f849df4c67bc970331014b0d
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Thu Jan 24 10:08:02 2019 +0100

    Run modules from Java only.
    
    Implements #29166.
---
 build.xml                                          | 136 +--------------------
 .../web/images => R/rserver}/no-data-available.pdf | Bin
 .../web/images => R/rserver}/no-data-available.png | Bin
 .../web/images => R/rserver}/no-data-available.xcf | Bin
 src/main/R/rserver/rserve-init.R                   |   6 +-
 .../torproject/metrics/stats/advbwdist/Main.java   |  15 ++-
 .../metrics/stats/bwhist/Configuration.java        |  18 ---
 .../org/torproject/metrics/stats/bwhist/Main.java  |  31 +++--
 .../torproject/metrics/stats/clients/Database.java |  10 +-
 .../torproject/metrics/stats/clients/Detector.java |   7 +-
 .../org/torproject/metrics/stats/clients/Main.java |  37 +++---
 .../metrics/stats/collectdescs/Main.java           |   5 +-
 .../metrics/stats/connbidirect/Main.java           |  15 ++-
 .../org/torproject/metrics/stats/hidserv/Main.java |  14 ++-
 .../org/torproject/metrics/stats/main/Main.java    | 122 ++++++++++++++++++
 .../torproject/metrics/stats/onionperf/Main.java   |  26 ++--
 .../metrics/stats/servers/Configuration.java       |  18 ---
 .../torproject/metrics/stats/servers/Database.java |  10 +-
 .../org/torproject/metrics/stats/servers/Main.java |  45 +++----
 .../metrics/stats/totalcw/Configuration.java       |  18 ---
 .../torproject/metrics/stats/totalcw/Database.java |  10 +-
 .../org/torproject/metrics/stats/totalcw/Main.java |  29 +++--
 .../torproject/metrics/stats/webstats/Main.java    |  23 ++--
 23 files changed, 296 insertions(+), 299 deletions(-)

diff --git a/build.xml b/build.xml
index 42965bf..254a71f 100644
--- a/build.xml
+++ b/build.xml
@@ -18,7 +18,8 @@
   <property name="name" value="metrics-web"/>
 
   <property name="project-main-class"
-            value="org.torproject.TBD" />
+            value="org.torproject.metrics.stats.main.Main" />
+  <property name="jarincludes" value="logback.xml" />
   <property name="additional2sign" value="${warfile}" />
   <property name="tardepends" value="war" />
 
@@ -26,11 +27,6 @@
 
   <property name="specdir" value="${basedir}/generated/spec" />
 
-  <!-- Deployment base folder.
-       Be aware that this is also set in R scripts and web.xml, currently! -->
-  <property name="metrics-web.deployment.base"
-            value="/srv/metrics.torproject.org/metrics" />
-
   <!-- The coverage needs to be improved! -->
   <target name="coverage-check">
     <cobertura-check totallinerate="0" totalbranchrate="0" >
@@ -301,85 +297,6 @@
     <delete file="${specdir}/${specfile}.tmp2" quiet="true" />
   </target>
 
-  <!-- This can be adapted to point at the actual work directory. -->
-  <property name="prepare.deployment"
-            value="/srv/metrics.torproject.org/metrics/work" />
-
-  <!-- Don't alter the following. -->
-  <property name="modulebase" value="${prepare.deployment}/modules" />
-
-  <!-- Operational tasks. -->
-  <target name="run-web-prepare" depends="init" >
-    <mkdir dir="${prepare.deployment}/modules" />
-    <antcall target="collectdescs" />
-    <antcall target="connbidirect" />
-    <antcall target="onionperf" />
-    <antcall target="bwhist" />
-    <antcall target="advbwdist" />
-    <antcall target="hidserv" />
-    <antcall target="clients" />
-    <antcall target="servers" />
-    <antcall target="webstats" />
-    <antcall target="totalcw" />
-    <antcall target="make-data-available" />
-  </target>
-
-  <target name="collectdescs" >
-    <property name="module.name" value="collectdescs" />
-    <antcall target="run-java" />
-  </target>
-
-  <target name="connbidirect" >
-    <property name="module.name" value="connbidirect" />
-    <antcall target="run-java" />
-  </target>
-
-  <target name="onionperf" >
-    <property name="module.name" value="onionperf" />
-    <antcall target="run-java" />
-  </target>
-
-  <target name="bwhist" >
-    <property name="module.name" value="bwhist" />
-    <antcall target="run-java" />
-  </target>
-
-  <target name="advbwdist">
-    <property name="module.name" value="advbwdist" />
-    <antcall target="run-java" />
-  </target>
-
-  <target name="hidserv" >
-    <property name="module.name" value="hidserv" />
-    <antcall target="run-java" />
-  </target>
-
-  <target name="clients" >
-    <property name="module.name" value="clients" />
-    <antcall target="run-java" />
-  </target>
-
-  <target name="servers" >
-    <property name="module.name" value="servers" />
-    <antcall target="run-java" >
-      <param name="module.main"
-             value="org.torproject.metrics.stats.servers.Main" />
-    </antcall>
-  </target>
-
-  <target name="webstats" >
-    <property name="module.name" value="webstats" />
-    <antcall target="run-java" />
-  </target>
-
-  <target name="totalcw" >
-    <property name="module.name" value="totalcw" />
-    <antcall target="run-java" >
-      <param name="module.main"
-             value="org.torproject.metrics.stats.totalcw.Main" />
-    </antcall>
-  </target>
-
   <!--
       The run-rserver target documents a working option of
       configuring an R server for running metrics-web.
@@ -401,55 +318,6 @@
     </exec>
   </target>
 
-  <target name="make-data-available" >
-    <property name="statsdir" value="${metrics-web.deployment.base}/shared/stats/" />
-    <mkdir dir="${statsdir}" />
-    <property name="rdatadir" value="${metrics-web.deployment.base}/shared/RData" />
-    <mkdir dir="${rdatadir}" />
-    <copy todir="${statsdir}" >
-      <fileset dir="${modulebase}/onionperf/stats" includes="*.csv" />
-      <fileset dir="${modulebase}/connbidirect/stats" includes="connbidirect2.csv" />
-      <fileset dir="${modulebase}/advbwdist/stats" includes="advbwdist.csv" />
-      <fileset dir="${modulebase}/bwhist/stats" includes="*.csv" />
-      <fileset dir="${modulebase}/hidserv/stats" includes="hidserv.csv" />
-      <fileset dir="${modulebase}/clients/stats"
-               includes="clients*.csv userstats-combined.csv" />
-      <fileset dir="${modulebase}/servers/stats" includes="*.csv" />
-      <fileset dir="${modulebase}/webstats/stats" includes="webstats.csv" />
-      <fileset dir="${modulebase}/totalcw/stats" includes="totalcw.csv" />
-    </copy>
-    <copy todir="${rdatadir}" >
-      <fileset dir="${resources}/web/images/" includes="no-data-available.*" />
-    </copy>
-  </target>
-
-  <!-- Support tasks for operation -->
-  <target name="run-java">
-    <echo message="Running java module ${module.name} ... " />
-    <available file="${dist}/${jarfile}" property="have.jar"/>
-    <fail unless="have.jar" message="Please run 'ant jar' first."/>
-    <condition property="mainclass"
-               value="${module.main}"
-               else="org.torproject.metrics.stats.${module.name}.Main" >
-      <isset property="module.main"/>
-    </condition>
-    <property name="workingdir" value="${modulebase}/${module.name}" />
-    <mkdir dir="${workingdir}" />
-    <java dir="${workingdir}"
-          fork="true"
-          maxmemory="4g"
-          classname="${mainclass}">
-      <classpath>
-        <pathelement location="${dist}/${jarfile}"/>
-        <pathelement location="${resources}"/>
-      </classpath>
-      <jvmarg value="-DLOGBASE=../logs"/>
-      <jvmarg value="-Duser.language=us" />
-      <jvmarg value="-Duser.region=US" />
-    </java>
-    <echo message="Java module ${module.name} finished. " />
-  </target>
-
   <!-- The following line adds the common targets and properties
        for Metrics' Java Projects.
   -->
diff --git a/src/main/resources/web/images/no-data-available.pdf b/src/main/R/rserver/no-data-available.pdf
similarity index 100%
rename from src/main/resources/web/images/no-data-available.pdf
rename to src/main/R/rserver/no-data-available.pdf
diff --git a/src/main/resources/web/images/no-data-available.png b/src/main/R/rserver/no-data-available.png
similarity index 100%
rename from src/main/resources/web/images/no-data-available.png
rename to src/main/R/rserver/no-data-available.png
diff --git a/src/main/resources/web/images/no-data-available.xcf b/src/main/R/rserver/no-data-available.xcf
similarity index 100%
rename from src/main/resources/web/images/no-data-available.xcf
rename to src/main/R/rserver/no-data-available.xcf
diff --git a/src/main/R/rserver/rserve-init.R b/src/main/R/rserver/rserve-init.R
index c412e4c..5a47550 100644
--- a/src/main/R/rserver/rserve-init.R
+++ b/src/main/R/rserver/rserve-init.R
@@ -333,7 +333,7 @@ copyright_notice <- "The Tor Project - https://metrics.torproject.org/"
 
 stats_dir <- "/srv/metrics.torproject.org/metrics/shared/stats/"
 
-rdata_dir <- "/srv/metrics.torproject.org/metrics/shared/RData/"
+no_data_available_dir <- "/srv/metrics.torproject.org/metrics/src/main/R/rserver/"
 
 # Helper function that copies the appropriate no data object to filename.
 copy_no_data <- function(filename) {
@@ -342,8 +342,8 @@ copy_no_data <- function(filename) {
   if (".csv" == extension) {
     write("# No data available for the given parameters.", file=filename)
   } else {
-    file.copy(paste(rdata_dir, "no-data-available", extension, sep = ""),
-      filename)
+    file.copy(paste(no_data_available_dir, "no-data-available", extension,
+      sep = ""), filename)
   }
 }
 
diff --git a/src/main/java/org/torproject/metrics/stats/advbwdist/Main.java b/src/main/java/org/torproject/metrics/stats/advbwdist/Main.java
index 6c4f4ac..3afcb80 100644
--- a/src/main/java/org/torproject/metrics/stats/advbwdist/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/advbwdist/Main.java
@@ -32,6 +32,9 @@ import java.util.TreeMap;
 
 public class Main {
 
+  private static final File baseDir = new File(
+      org.torproject.metrics.stats.main.Main.modulesDir, "advbwdist");
+
   /** Executes this data-processing module. */
   public static void main(String[] args) throws IOException {
 
@@ -41,7 +44,8 @@ public class Main {
     DescriptorReader descriptorReader =
         DescriptorSourceFactory.createDescriptorReader();
     for (Descriptor descriptor : descriptorReader.readDescriptors(new File(
-        "../../shared/in/recent/relay-descriptors/server-descriptors"))) {
+        org.torproject.metrics.stats.main.Main.descriptorsDir,
+        "recent/relay-descriptors/server-descriptors"))) {
       if (!(descriptor instanceof ServerDescriptor)) {
         continue;
       }
@@ -56,9 +60,9 @@ public class Main {
 
     /* Parse consensuses, keeping a parse history. */
     descriptorReader = DescriptorSourceFactory.createDescriptorReader();
-    File historyFile = new File("status/parsed-consensuses");
+    File historyFile = new File(baseDir, "status/parsed-consensuses");
     descriptorReader.setHistoryFile(historyFile);
-    File resultsFile = new File("stats/advbwdist-validafter.csv");
+    File resultsFile = new File(baseDir, "stats/advbwdist-validafter.csv");
     resultsFile.getParentFile().mkdirs();
     boolean writeHeader = !resultsFile.exists();
     BufferedWriter bw = new BufferedWriter(new FileWriter(resultsFile,
@@ -70,7 +74,8 @@ public class Main {
         "yyyy-MM-dd HH:mm:ss");
     dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
     for (Descriptor descriptor : descriptorReader.readDescriptors(new File(
-        "../../shared/in/recent/relay-descriptors/consensuses"))) {
+        org.torproject.metrics.stats.main.Main.descriptorsDir,
+        "recent/relay-descriptors/consensuses"))) {
       if (!(descriptor instanceof RelayNetworkStatusConsensus)) {
         continue;
       }
@@ -165,7 +170,7 @@ public class Main {
         preAggregatedValues.get(keyWithoutTime).add(value);
       }
     }
-    File aggregateResultsFile = new File("stats/advbwdist.csv");
+    File aggregateResultsFile = new File(baseDir, "stats/advbwdist.csv");
     aggregateResultsFile.getParentFile().mkdirs();
     try (BufferedWriter bw2 = new BufferedWriter(
         new FileWriter(aggregateResultsFile))) {
diff --git a/src/main/java/org/torproject/metrics/stats/bwhist/Configuration.java b/src/main/java/org/torproject/metrics/stats/bwhist/Configuration.java
deleted file mode 100644
index 2a0fbc5..0000000
--- a/src/main/java/org/torproject/metrics/stats/bwhist/Configuration.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Copyright 2011--2018 The Tor Project
- * See LICENSE for licensing information */
-
-package org.torproject.metrics.stats.bwhist;
-
-/** Configuration options parsed from Java properties with reasonable hard-coded
- * defaults. */
-public class Configuration {
-  static String descriptors = System.getProperty("bwhist.descriptors",
-      "../../shared/in/");
-  static String database = System.getProperty("bwhist.database",
-      "jdbc:postgresql:tordir");
-  static String history = System.getProperty("bwhist.history",
-      "status/read-descriptors");
-  static String output = System.getProperty("bwhist.output",
-      "stats/");
-}
-
diff --git a/src/main/java/org/torproject/metrics/stats/bwhist/Main.java b/src/main/java/org/torproject/metrics/stats/bwhist/Main.java
index 61c1435..30358f2 100644
--- a/src/main/java/org/torproject/metrics/stats/bwhist/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/bwhist/Main.java
@@ -7,7 +7,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.nio.file.Paths;
 import java.util.Arrays;
 
 /**
@@ -18,11 +17,19 @@ public class Main {
 
   private static Logger log = LoggerFactory.getLogger(Main.class);
 
-  private static String[][] paths =  {
-      {"recent", "relay-descriptors", "consensuses"},
-      {"recent", "relay-descriptors", "extra-infos"},
-      {"archive", "relay-descriptors", "consensuses"},
-      {"archive", "relay-descriptors", "extra-infos"}};
+  private static String[] paths =  {
+      "recent/relay-descriptors/consensuses",
+      "recent/relay-descriptors/extra-infos",
+      "archive/relay-descriptors/consensuses",
+      "archive/relay-descriptors/extra-infos" };
+
+  private static final String jdbcString = String.format(
+      "jdbc:postgresql://localhost/tordir?user=%s&password=%s",
+      System.getProperty("metrics.dbuser", "metrics"),
+      System.getProperty("metrics.dbpass", "password"));
+
+  private static final File baseDir = new File(
+      org.torproject.metrics.stats.main.Main.modulesDir, "bwhist");
 
   /** Executes this data-processing module. */
   public static void main(String[] args) throws Exception {
@@ -31,20 +38,20 @@ public class Main {
 
     log.info("Reading descriptors and inserting relevant parts into the "
         + "database.");
-    File[] descriptorDirectories = Arrays.stream(paths).map((String[] path)
-        -> Paths.get(Configuration.descriptors, path).toFile())
-        .toArray(File[]::new);
-    File historyFile = new File(Configuration.history);
+    File[] descriptorDirectories = Arrays.stream(paths).map((String path)
+        -> new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+        path)).toArray(File[]::new);
+    File historyFile = new File(baseDir, "status/read-descriptors");
     RelayDescriptorDatabaseImporter database
         = new RelayDescriptorDatabaseImporter(descriptorDirectories,
-        historyFile, Configuration.database);
+        historyFile, jdbcString);
     database.importRelayDescriptors();
 
     log.info("Aggregating database entries.");
     database.aggregate();
 
     log.info("Querying aggregated statistics from the database.");
-    new Writer().write(Paths.get(Configuration.output, "bandwidth.csv"),
+    new Writer().write(new File(baseDir, "stats/bandwidth.csv").toPath(),
         database.queryBandwidth());
 
     log.info("Closing database connection.");
diff --git a/src/main/java/org/torproject/metrics/stats/clients/Database.java b/src/main/java/org/torproject/metrics/stats/clients/Database.java
index 7e783dc..f8bca92 100644
--- a/src/main/java/org/torproject/metrics/stats/clients/Database.java
+++ b/src/main/java/org/torproject/metrics/stats/clients/Database.java
@@ -22,7 +22,10 @@ import java.util.TimeZone;
 class Database implements AutoCloseable {
 
   /** Database connection string. */
-  private String jdbcString;
+  private static final String jdbcString = String.format(
+      "jdbc:postgresql://localhost/userstats?user=%s&password=%s",
+      System.getProperty("metrics.dbuser", "metrics"),
+      System.getProperty("metrics.dbpass", "password"));
 
   /** Connection object for all interactions with the database. */
   private Connection connection;
@@ -33,14 +36,13 @@ class Database implements AutoCloseable {
 
   /** Create a new Database instance and prepare for inserting or querying
    * data. */
-  Database(String jdbcString) throws SQLException {
-    this.jdbcString = jdbcString;
+  Database() throws SQLException {
     this.connect();
     this.prepareStatements();
   }
 
   private void connect() throws SQLException {
-    this.connection = DriverManager.getConnection(this.jdbcString);
+    this.connection = DriverManager.getConnection(jdbcString);
     this.connection.setAutoCommit(false);
   }
 
diff --git a/src/main/java/org/torproject/metrics/stats/clients/Detector.java b/src/main/java/org/torproject/metrics/stats/clients/Detector.java
index 1a523c2..cceac5e 100644
--- a/src/main/java/org/torproject/metrics/stats/clients/Detector.java
+++ b/src/main/java/org/torproject/metrics/stats/clients/Detector.java
@@ -53,7 +53,6 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.io.LineNumberReader;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.time.LocalDate;
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
@@ -70,10 +69,12 @@ import java.util.stream.Collectors;
 public class Detector {
 
   /** Input file. */
-  private static final Path INPUT_PATH = Paths.get("stats", "userstats.csv");
+  private static final Path INPUT_PATH = new File(Main.baseDir,
+      "stats/userstats.csv").toPath();
 
   /** Output file. */
-  private static final Path OUTPUT_PATH = Paths.get("stats", "clients.csv");
+  private static final Path OUTPUT_PATH = new File(Main.baseDir,
+      "stats/clients.csv").toPath();
 
   /** Number of largest locations to be included in the detection algorithm. */
   private static final int NUM_LARGEST_LOCATIONS = 50;
diff --git a/src/main/java/org/torproject/metrics/stats/clients/Main.java b/src/main/java/org/torproject/metrics/stats/clients/Main.java
index 0f1087b..2f22f72 100644
--- a/src/main/java/org/torproject/metrics/stats/clients/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/clients/Main.java
@@ -16,7 +16,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.nio.file.Paths;
 import java.sql.SQLException;
 import java.util.Map;
 import java.util.SortedMap;
@@ -26,18 +25,18 @@ public class Main {
 
   private static Logger log = LoggerFactory.getLogger(Main.class);
 
-  private static final String jdbcString
-      = System.getProperty("clients.database", "jdbc:postgresql:userstats");
-
   private static Database database;
 
+  static final File baseDir = new File(
+      org.torproject.metrics.stats.main.Main.modulesDir, "clients");
+
   /** Executes this data-processing module. */
   public static void main(String[] args) throws Exception {
 
     log.info("Starting clients module.");
 
     log.info("Connecting to database.");
-    database = new Database(jdbcString);
+    database = new Database();
 
     log.info("Reading relay descriptors and importing relevant parts into the "
         + "database.");
@@ -52,10 +51,10 @@ public class Main {
     database.commit();
 
     log.info("Querying aggregated statistics from the database.");
-    new Writer().write(Paths.get("stats", "userstats.csv"),
+    new Writer().write(new File(baseDir, "stats/userstats.csv").toPath(),
         database.queryEstimated());
-    new Writer().write(Paths.get("stats", "userstats-combined.csv"),
-        database.queryCombined());
+    new Writer().write(new File(baseDir, "stats/userstats-combined.csv")
+        .toPath(), database.queryCombined());
 
     log.info("Disconnecting from database.");
     database.close();
@@ -75,13 +74,17 @@ public class Main {
   private static void parseRelayDescriptors() throws Exception {
     DescriptorReader descriptorReader =
         DescriptorSourceFactory.createDescriptorReader();
-    File historyFile = new File("status/relay-descriptors");
+    File historyFile = new File(baseDir, "status/relay-descriptors");
     descriptorReader.setHistoryFile(historyFile);
     for (Descriptor descriptor : descriptorReader.readDescriptors(
-        new File("../../shared/in/recent/relay-descriptors/consensuses"),
-        new File("../../shared/in/recent/relay-descriptors/extra-infos"),
-        new File("../../shared/in/archive/relay-descriptors/consensuses"),
-        new File("../../shared/in/archive/relay-descriptors/extra-infos"))) {
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "recent/relay-descriptors/consensuses"),
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "recent/relay-descriptors/extra-infos"),
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "archive/relay-descriptors/consensuses"),
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "archive/relay-descriptors/extra-infos"))) {
       if (descriptor instanceof ExtraInfoDescriptor) {
         parseRelayExtraInfoDescriptor((ExtraInfoDescriptor) descriptor);
       } else if (descriptor instanceof RelayNetworkStatusConsensus) {
@@ -209,11 +212,13 @@ public class Main {
   private static void parseBridgeDescriptors() throws Exception {
     DescriptorReader descriptorReader =
         DescriptorSourceFactory.createDescriptorReader();
-    File historyFile = new File("status/bridge-descriptors");
+    File historyFile = new File(baseDir, "status/bridge-descriptors");
     descriptorReader.setHistoryFile(historyFile);
     for (Descriptor descriptor : descriptorReader.readDescriptors(
-        new File("../../shared/in/recent/bridge-descriptors"),
-        new File("../../shared/in/archive/bridge-descriptors"))) {
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "recent/bridge-descriptors"),
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "archive/bridge-descriptors"))) {
       if (descriptor instanceof ExtraInfoDescriptor) {
         parseBridgeExtraInfoDescriptor(
             (ExtraInfoDescriptor) descriptor);
diff --git a/src/main/java/org/torproject/metrics/stats/collectdescs/Main.java b/src/main/java/org/torproject/metrics/stats/collectdescs/Main.java
index a9620f5..1d0d840 100644
--- a/src/main/java/org/torproject/metrics/stats/collectdescs/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/collectdescs/Main.java
@@ -10,6 +10,9 @@ import java.io.File;
 
 public class Main {
 
+  private static final File baseDir = new File(
+      org.torproject.metrics.stats.main.Main.modulesDir, "collectdescs");
+
   /** Executes this data-processing module. */
   public static void main(String[] args) {
     /* Fetch recent descriptors from CollecTor. */
@@ -27,7 +30,7 @@ public class Main {
             "/recent/relay-descriptors/votes/",
             "/recent/torperf/",
             "/recent/webstats/"
-        }, 0L, new File("../../shared/in"), true);
+        }, 0L, org.torproject.metrics.stats.main.Main.descriptorsDir, true);
   }
 }
 
diff --git a/src/main/java/org/torproject/metrics/stats/connbidirect/Main.java b/src/main/java/org/torproject/metrics/stats/connbidirect/Main.java
index 468ffba..c7fefee 100644
--- a/src/main/java/org/torproject/metrics/stats/connbidirect/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/connbidirect/Main.java
@@ -129,13 +129,18 @@ public class Main {
 
   static final long ONE_DAY_IN_MILLIS = 86400000L;
 
+  private static final File baseDir = new File(
+      org.torproject.metrics.stats.main.Main.modulesDir, "connbidirect");
+
   /** Executes this data-processing module. */
   public static void main(String[] args) throws IOException {
-    File parseHistoryFile = new File("stats/parse-history");
-    File aggregateStatsFile = new File("stats/connbidirect2.csv");
+    File parseHistoryFile = new File(baseDir, "stats/parse-history");
+    File aggregateStatsFile = new File(baseDir, "stats/connbidirect2.csv");
     File[] descriptorsDirectories = new File[] {
-        new File("../../shared/in/archive/relay-descriptors/extra-infos"),
-        new File("../../shared/in/recent/relay-descriptors/extra-infos")};
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "archive/relay-descriptors/extra-infos"),
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "recent/relay-descriptors/extra-infos")};
     SortedMap<String, Long> parseHistory = parseParseHistory(
         readStringFromFile(parseHistoryFile));
     if (parseHistory == null) {
@@ -160,7 +165,7 @@ public class Main {
           + "leave out those descriptors in future runs.");
       return;
     }
-    File rawStatsFile = new File("stats/raw-stats");
+    File rawStatsFile = new File(baseDir, "stats/raw-stats");
     SortedSet<RawStat> rawStats = parseRawStats(
         readStringFromFile(rawStatsFile));
     if (rawStats == null) {
diff --git a/src/main/java/org/torproject/metrics/stats/hidserv/Main.java b/src/main/java/org/torproject/metrics/stats/hidserv/Main.java
index b6a4e43..5076353 100644
--- a/src/main/java/org/torproject/metrics/stats/hidserv/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/hidserv/Main.java
@@ -16,6 +16,9 @@ public class Main {
 
   private static Logger log = LoggerFactory.getLogger(Main.class);
 
+  private static final File baseDir = new File(
+      org.torproject.metrics.stats.main.Main.modulesDir, "hidserv");
+
   /** Parses new descriptors, extrapolate contained statistics using
    * computed network fractions, aggregate results, and writes results to
    * disk. */
@@ -23,9 +26,11 @@ public class Main {
 
     /* Initialize directories and file paths. */
     File[] inDirectories = new File[] {
-        new File("../../shared/in/recent/relay-descriptors/consensuses"),
-        new File("../../shared/in/recent/relay-descriptors/extra-infos") };
-    File statusDirectory = new File("status");
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "recent/relay-descriptors/consensuses"),
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "recent/relay-descriptors/extra-infos") };
+    File statusDirectory = new File(baseDir, "status");
 
     /* Initialize parser and read parse history to avoid parsing
      * descriptor files that haven't changed since the last execution. */
@@ -71,7 +76,8 @@ public class Main {
      * other statistics.  Write the result to a .csv file that can be
      * processed by other tools. */
     log.info("Aggregating statistics...");
-    File hidservStatsExtrapolatedCsvFile = new File("stats/hidserv.csv");
+    File hidservStatsExtrapolatedCsvFile = new File(baseDir,
+        "stats/hidserv.csv");
     Aggregator aggregator = new Aggregator(statusDirectory,
         extrapolatedHidServStatsStore, hidservStatsExtrapolatedCsvFile);
     aggregator.aggregateHidServStats();
diff --git a/src/main/java/org/torproject/metrics/stats/main/Main.java b/src/main/java/org/torproject/metrics/stats/main/Main.java
new file mode 100644
index 0000000..3dc2775
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/stats/main/Main.java
@@ -0,0 +1,122 @@
+package org.torproject.metrics.stats.main;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Main {
+
+  private static final Logger log = LoggerFactory.getLogger(Main.class);
+
+  private static final String baseDir = System.getProperty("metrics.basedir",
+      "/srv/metrics.torproject.org/metrics");
+
+  public static final File modulesDir = new File(baseDir, "work/modules");
+
+  public static final File descriptorsDir = new File(baseDir, "work/shared/in");
+
+  private static final File statsDir = new File(baseDir, "shared/stats");
+
+  /** Start the metrics update run. */
+  public static void main(String[] args) {
+
+    log.info("Starting metrics update run.");
+
+    File[] outputDirs = new File[] { modulesDir, statsDir };
+    for (File outputDir : outputDirs) {
+      if (outputDir.exists()) {
+        continue;
+      }
+      if (outputDir.mkdirs()) {
+        log.info("Successfully created module base directory {} and any "
+              + "nonexistent parent directories.",
+            outputDir.getAbsolutePath());
+      } else {
+        log.error("Unable to create module base directory {} and any "
+              + "nonexistent parent directories. Exiting.",
+            outputDir.getAbsolutePath());
+        return;
+      }
+    }
+
+    Class<?>[] modules = new Class<?>[] {
+        org.torproject.metrics.stats.collectdescs.Main.class,
+        org.torproject.metrics.stats.connbidirect.Main.class,
+        org.torproject.metrics.stats.onionperf.Main.class,
+        org.torproject.metrics.stats.bwhist.Main.class,
+        org.torproject.metrics.stats.advbwdist.Main.class,
+        org.torproject.metrics.stats.hidserv.Main.class,
+        org.torproject.metrics.stats.clients.Main.class,
+        org.torproject.metrics.stats.servers.Main.class,
+        org.torproject.metrics.stats.webstats.Main.class,
+        org.torproject.metrics.stats.totalcw.Main.class
+    };
+
+    for (Class<?> module : modules) {
+      try {
+        log.info("Starting {} module.", module.getName());
+        module.getDeclaredMethod("main", String[].class)
+            .invoke(null, (Object) args);
+        log.info("Completed {} module.", module.getName());
+      } catch (NoSuchMethodException | IllegalAccessException
+          | InvocationTargetException e) {
+        log.warn("Caught an exception when invoking the main method of the {} "
+            + "module. Moving on to the next module, if available.", e);
+      }
+    }
+
+    log.info("Making module data available.");
+    File[] moduleStatsDirs = new File[] {
+        new File(modulesDir, "connbidirect/stats"),
+        new File(modulesDir, "onionperf/stats"),
+        new File(modulesDir, "bwhist/stats"),
+        new File(modulesDir, "advbwdist/stats/advbwdist.csv"),
+        new File(modulesDir, "hidserv/stats"),
+        new File(modulesDir, "clients/stats/clients.csv"),
+        new File(modulesDir, "clients/stats/userstats-combined.csv"),
+        new File(modulesDir, "servers/stats"),
+        new File(modulesDir, "webstats/stats"),
+        new File(modulesDir, "totalcw/stats")
+    };
+    List<String> copiedFiles = new ArrayList<>();
+    for (File moduleStatsDir : moduleStatsDirs) {
+      if (!moduleStatsDir.exists()) {
+        log.warn("Skipping nonexistent module stats dir {}.", moduleStatsDir);
+        continue;
+      }
+      File[] moduleStatsFiles = moduleStatsDir.isDirectory()
+          ? moduleStatsDir.listFiles() : new File[] { moduleStatsDir };
+      if (null == moduleStatsFiles) {
+        log.warn("Skipping nonexistent module stats dir {}.", moduleStatsDir);
+        continue;
+      }
+      for (File statsFile : moduleStatsFiles) {
+        if (!statsFile.isFile() || !statsFile.getName().endsWith(".csv")) {
+          continue;
+        }
+        try {
+          Files.copy(statsFile.toPath(),
+              new File(statsDir, statsFile.getName()).toPath(),
+              StandardCopyOption.REPLACE_EXISTING);
+          copiedFiles.add(statsFile.getName());
+        } catch (IOException e) {
+          log.warn("Unable to copy module stats file {} to stats output "
+              + "directory {}. Skipping.", statsFile, statsDir, e);
+        }
+      }
+    }
+    if (!copiedFiles.isEmpty()) {
+      log.info("Successfully copied {} files to stats output directory: {}",
+          copiedFiles.size(), copiedFiles);
+    }
+
+    log.info("Completed metrics update run.");
+  }
+}
diff --git a/src/main/java/org/torproject/metrics/stats/onionperf/Main.java b/src/main/java/org/torproject/metrics/stats/onionperf/Main.java
index 02b70af..a970434 100644
--- a/src/main/java/org/torproject/metrics/stats/onionperf/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/onionperf/Main.java
@@ -13,7 +13,6 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
@@ -37,23 +36,30 @@ public class Main {
   /** Logger for this class. */
   private static Logger log = LoggerFactory.getLogger(Main.class);
 
+  private static final String jdbcString = String.format(
+      "jdbc:postgresql://localhost/onionperf?user=%s&password=%s",
+      System.getProperty("metrics.dbuser", "metrics"),
+      System.getProperty("metrics.dbpass", "password"));
+
+  private static final File baseDir = new File(
+      org.torproject.metrics.stats.main.Main.modulesDir, "onionperf");
+
   /** Executes this data-processing module. */
   public static void main(String[] args) throws Exception {
     log.info("Starting onionperf module.");
-    String dbUrlString = "jdbc:postgresql:onionperf";
-    Connection connection = connectToDatabase(dbUrlString);
+    Connection connection = connectToDatabase();
     importOnionPerfFiles(connection);
-    writeStatistics(Paths.get("stats", "torperf-1.1.csv"),
+    writeStatistics(new File(baseDir, "stats/torperf-1.1.csv").toPath(),
         queryOnionPerf(connection));
-    writeStatistics(Paths.get("stats", "buildtimes.csv"),
+    writeStatistics(new File(baseDir, "stats/buildtimes.csv").toPath(),
         queryBuildTimes(connection));
-    writeStatistics(Paths.get("stats", "latencies.csv"),
+    writeStatistics(new File(baseDir, "stats/latencies.csv").toPath(),
         queryLatencies(connection));
     disconnectFromDatabase(connection);
     log.info("Terminated onionperf module.");
   }
 
-  private static Connection connectToDatabase(String jdbcString)
+  private static Connection connectToDatabase()
       throws SQLException {
     log.info("Connecting to database.");
     Connection connection = DriverManager.getConnection(jdbcString);
@@ -91,8 +97,10 @@ public class Main {
     Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
     DescriptorReader dr = DescriptorSourceFactory.createDescriptorReader();
     for (Descriptor d : dr.readDescriptors(
-        new File("../../shared/in/archive/torperf"),
-        new File("../../shared/in/recent/torperf"))) {
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "archive/torperf"),
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "recent/torperf"))) {
       if (!(d instanceof TorperfResult)) {
         continue;
       }
diff --git a/src/main/java/org/torproject/metrics/stats/servers/Configuration.java b/src/main/java/org/torproject/metrics/stats/servers/Configuration.java
deleted file mode 100644
index 1b7c217..0000000
--- a/src/main/java/org/torproject/metrics/stats/servers/Configuration.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Copyright 2017--2018 The Tor Project
- * See LICENSE for licensing information */
-
-package org.torproject.metrics.stats.servers;
-
-/** Configuration options parsed from Java properties with reasonable hard-coded
- * defaults. */
-class Configuration {
-  static String descriptors = System.getProperty("servers.descriptors",
-      "../../shared/in/");
-  static String database = System.getProperty("servers.database",
-      "jdbc:postgresql:ipv6servers");
-  static String history = System.getProperty("servers.history",
-      "status/read-descriptors");
-  static String output = System.getProperty("servers.output",
-      "stats/");
-}
-
diff --git a/src/main/java/org/torproject/metrics/stats/servers/Database.java b/src/main/java/org/torproject/metrics/stats/servers/Database.java
index 9c9bda3..f7f1f0f 100644
--- a/src/main/java/org/torproject/metrics/stats/servers/Database.java
+++ b/src/main/java/org/torproject/metrics/stats/servers/Database.java
@@ -29,7 +29,10 @@ import java.util.TimeZone;
 class Database implements AutoCloseable {
 
   /** Database connection string. */
-  private String jdbcString;
+  private static final String jdbcString = String.format(
+      "jdbc:postgresql://localhost/ipv6servers?user=%s&password=%s",
+      System.getProperty("metrics.dbuser", "metrics"),
+      System.getProperty("metrics.dbpass", "password"));
 
   /** Connection object for all interactions with the database. */
   private Connection connection;
@@ -87,15 +90,14 @@ class Database implements AutoCloseable {
 
   /** Create a new Database instance and prepare for inserting or querying
    * data. */
-  Database(String jdbcString) throws SQLException {
-    this.jdbcString = jdbcString;
+  Database() throws SQLException {
     this.connect();
     this.prepareStatements();
     this.initializeCaches();
   }
 
   private void connect() throws SQLException {
-    this.connection = DriverManager.getConnection(this.jdbcString);
+    this.connection = DriverManager.getConnection(jdbcString);
     this.connection.setAutoCommit(false);
   }
 
diff --git a/src/main/java/org/torproject/metrics/stats/servers/Main.java b/src/main/java/org/torproject/metrics/stats/servers/Main.java
index 54f44d0..30010a2 100644
--- a/src/main/java/org/torproject/metrics/stats/servers/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/servers/Main.java
@@ -14,7 +14,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.nio.file.Paths;
 import java.sql.SQLException;
 import java.util.Arrays;
 
@@ -25,15 +24,18 @@ public class Main {
 
   private static Logger log = LoggerFactory.getLogger(Main.class);
 
-  private static String[][] paths =  {
-    {"recent", "relay-descriptors", "consensuses"},
-    {"recent", "relay-descriptors", "server-descriptors"},
-    {"recent", "bridge-descriptors", "statuses"},
-    {"recent", "bridge-descriptors", "server-descriptors"},
-    {"archive", "relay-descriptors", "consensuses"},
-    {"archive", "relay-descriptors", "server-descriptors"},
-    {"archive", "bridge-descriptors", "statuses"},
-    {"archive", "bridge-descriptors", "server-descriptors"}};
+  private static final File baseDir = new File(
+      org.torproject.metrics.stats.main.Main.modulesDir, "servers");
+
+  private static String[] paths = {
+      "recent/relay-descriptors/consensuses",
+      "recent/relay-descriptors/server-descriptors",
+      "recent/bridge-descriptors/statuses",
+      "recent/bridge-descriptors/server-descriptors",
+      "archive/relay-descriptors/consensuses",
+      "archive/relay-descriptors/server-descriptors",
+      "archive/bridge-descriptors/statuses",
+      "archive/bridge-descriptors/server-descriptors" };
 
   /** Run the module. */
   public static void main(String[] args) throws Exception {
@@ -43,15 +45,15 @@ public class Main {
     log.info("Reading descriptors and inserting relevant parts into the "
         + "database.");
     DescriptorReader reader = DescriptorSourceFactory.createDescriptorReader();
-    File historyFile = new File(Configuration.history);
+    File historyFile = new File(baseDir, "status/read-descriptors");
     reader.setHistoryFile(historyFile);
     Parser parser = new Parser();
-    try (Database database = new Database(Configuration.database)) {
+    try (Database database = new Database()) {
       try {
         for (Descriptor descriptor : reader.readDescriptors(
-            Arrays.stream(paths).map((String[] path)
-                -> Paths.get(Configuration.descriptors, path).toFile())
-            .toArray(File[]::new))) {
+            Arrays.stream(paths).map((String path) -> new File(
+                org.torproject.metrics.stats.main.Main.descriptorsDir, path))
+                .toArray(File[]::new))) {
           if (descriptor instanceof ServerDescriptor) {
             database.insertServerDescriptor(parser.parseServerDescriptor(
                 (ServerDescriptor) descriptor));
@@ -86,17 +88,18 @@ public class Main {
       reader.saveHistoryFile(historyFile);
 
       log.info("Querying aggregated statistics from the database.");
-      new Writer().write(Paths.get(Configuration.output, "ipv6servers.csv"),
+      File outputDir = new File(baseDir, "stats");
+      new Writer().write(new File(outputDir, "ipv6servers.csv").toPath(),
           database.queryServersIpv6());
-      new Writer().write(Paths.get(Configuration.output, "advbw.csv"),
+      new Writer().write(new File(outputDir, "advbw.csv").toPath(),
           database.queryAdvbw());
-      new Writer().write(Paths.get(Configuration.output, "networksize.csv"),
+      new Writer().write(new File(outputDir, "networksize.csv").toPath(),
           database.queryNetworksize());
-      new Writer().write(Paths.get(Configuration.output, "relayflags.csv"),
+      new Writer().write(new File(outputDir, "relayflags.csv").toPath(),
           database.queryRelayflags());
-      new Writer().write(Paths.get(Configuration.output, "versions.csv"),
+      new Writer().write(new File(outputDir, "versions.csv").toPath(),
           database.queryVersions());
-      new Writer().write(Paths.get(Configuration.output, "platforms.csv"),
+      new Writer().write(new File(outputDir, "platforms.csv").toPath(),
           database.queryPlatforms());
 
       log.info("Terminating servers module.");
diff --git a/src/main/java/org/torproject/metrics/stats/totalcw/Configuration.java b/src/main/java/org/torproject/metrics/stats/totalcw/Configuration.java
deleted file mode 100644
index a424f84..0000000
--- a/src/main/java/org/torproject/metrics/stats/totalcw/Configuration.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Copyright 2018 The Tor Project
- * See LICENSE for licensing information */
-
-package org.torproject.metrics.stats.totalcw;
-
-/** Configuration options parsed from Java properties with reasonable hard-coded
- * defaults. */
-class Configuration {
-  static String descriptors = System.getProperty("totalcw.descriptors",
-      "../../shared/in/");
-  static String database = System.getProperty("totalcw.database",
-      "jdbc:postgresql:totalcw");
-  static String history = System.getProperty("totalcw.history",
-      "status/read-descriptors");
-  static String output = System.getProperty("totalcw.output",
-      "stats/totalcw.csv");
-}
-
diff --git a/src/main/java/org/torproject/metrics/stats/totalcw/Database.java b/src/main/java/org/torproject/metrics/stats/totalcw/Database.java
index e842bc6..eeb24a0 100644
--- a/src/main/java/org/torproject/metrics/stats/totalcw/Database.java
+++ b/src/main/java/org/torproject/metrics/stats/totalcw/Database.java
@@ -25,7 +25,10 @@ import java.util.TimeZone;
 class Database implements AutoCloseable {
 
   /** Database connection string. */
-  private String jdbcString;
+  private static final String jdbcString = String.format(
+      "jdbc:postgresql://localhost/totalcw?user=%s&password=%s",
+      System.getProperty("metrics.dbuser", "metrics"),
+      System.getProperty("metrics.dbpass", "password"));
 
   /** Connection object for all interactions with the database. */
   private Connection connection;
@@ -46,14 +49,13 @@ class Database implements AutoCloseable {
 
   /** Create a new Database instance and prepare for inserting or querying
    * data. */
-  Database(String jdbcString) throws SQLException {
-    this.jdbcString = jdbcString;
+  Database() throws SQLException {
     this.connect();
     this.prepareStatements();
   }
 
   private void connect() throws SQLException {
-    this.connection = DriverManager.getConnection(this.jdbcString);
+    this.connection = DriverManager.getConnection(jdbcString);
     this.connection.setAutoCommit(false);
   }
 
diff --git a/src/main/java/org/torproject/metrics/stats/totalcw/Main.java b/src/main/java/org/torproject/metrics/stats/totalcw/Main.java
index e5ba8ab..54296be 100644
--- a/src/main/java/org/torproject/metrics/stats/totalcw/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/totalcw/Main.java
@@ -13,7 +13,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.nio.file.Paths;
 import java.sql.SQLException;
 import java.util.Arrays;
 
@@ -24,11 +23,14 @@ public class Main {
 
   private static Logger log = LoggerFactory.getLogger(Main.class);
 
-  private static String[][] paths =  {
-    {"recent", "relay-descriptors", "consensuses"},
-    {"archive", "relay-descriptors", "consensuses"},
-    {"recent", "relay-descriptors", "votes"},
-    {"archive", "relay-descriptors", "votes"}};
+  private static final File baseDir = new File(
+      org.torproject.metrics.stats.main.Main.modulesDir, "totalcw");
+
+  private static String[] paths =  {
+      "recent/relay-descriptors/consensuses",
+      "archive/relay-descriptors/consensuses",
+      "recent/relay-descriptors/votes",
+      "archive/relay-descriptors/votes" };
 
   /** Run the module. */
   public static void main(String[] args) throws Exception {
@@ -38,15 +40,15 @@ public class Main {
     log.info("Reading consensuses and votes and inserting relevant parts into "
         + "the database.");
     DescriptorReader reader = DescriptorSourceFactory.createDescriptorReader();
-    File historyFile = new File(Configuration.history);
+    File historyFile = new File(baseDir, "status/read-descriptors");
     reader.setHistoryFile(historyFile);
     Parser parser = new Parser();
-    try (Database database = new Database(Configuration.database)) {
+    try (Database database = new Database()) {
       try {
         for (Descriptor descriptor : reader.readDescriptors(
-            Arrays.stream(paths).map((String[] path)
-                -> Paths.get(Configuration.descriptors, path).toFile())
-            .toArray(File[]::new))) {
+            Arrays.stream(paths).map((String path) -> new File(
+                org.torproject.metrics.stats.main.Main.descriptorsDir, path))
+                .toArray(File[]::new))) {
           if (descriptor instanceof RelayNetworkStatusConsensus) {
             database.insertConsensus(parser.parseRelayNetworkStatusConsensus(
                 (RelayNetworkStatusConsensus) descriptor));
@@ -71,9 +73,10 @@ public class Main {
 
       log.info("Querying aggregated statistics from the database.");
       Iterable<OutputLine> output = database.queryTotalcw();
-      log.info("Writing aggregated statistics to {}.", Configuration.output);
+      File outputFile = new File(baseDir, "stats/totalcw.csv");
+      log.info("Writing aggregated statistics to {}.", outputFile);
       if (null != output) {
-        new Writer().write(Paths.get(Configuration.output), output);
+        new Writer().write(outputFile.toPath(), output);
       }
 
       log.info("Terminating totalcw module.");
diff --git a/src/main/java/org/torproject/metrics/stats/webstats/Main.java b/src/main/java/org/torproject/metrics/stats/webstats/Main.java
index fb0a903..87b643a 100644
--- a/src/main/java/org/torproject/metrics/stats/webstats/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/webstats/Main.java
@@ -19,7 +19,6 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.sql.Connection;
 import java.sql.Date;
 import java.sql.DriverManager;
@@ -48,6 +47,11 @@ public class Main {
   /** Logger for this class. */
   private static Logger log = LoggerFactory.getLogger(Main.class);
 
+  private static final String jdbcString = String.format(
+      "jdbc:postgresql://localhost/webstats?user=%s&password=%s",
+      System.getProperty("metrics.dbuser", "metrics"),
+      System.getProperty("metrics.dbpass", "password"));
+
   private static final String LOG_DATE = "log_date";
 
   private static final String REQUEST_TYPE = "request_type";
@@ -66,22 +70,27 @@ public class Main {
       + PLATFORM + "," + CHANNEL + "," + LOCALE + "," + INCREMENTAL + ","
       + COUNT;
 
+  private static final File baseDir = new File(
+      org.torproject.metrics.stats.main.Main.modulesDir, "webstats");
+
   /** Executes this data-processing module. */
   public static void main(String[] args) throws Exception {
     log.info("Starting webstats module.");
-    String dbUrlString = "jdbc:postgresql:webstats";
-    Connection connection = connectToDatabase(dbUrlString);
+    Connection connection = connectToDatabase();
     SortedSet<String> skipFiles = queryImportedFileNames(connection);
     importLogFiles(connection, skipFiles,
-        new File("../../shared/in/recent/webstats"),
-        new File("../../shared/in/archive/webstats"));
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "recent/webstats"),
+        new File(org.torproject.metrics.stats.main.Main.descriptorsDir,
+            "archive/webstats"));
     SortedSet<String> statistics = queryWebstats(connection);
-    writeStatistics(Paths.get("stats", "webstats.csv"), statistics);
+    writeStatistics(new File(baseDir, "stats/webstats.csv").toPath(),
+        statistics);
     disconnectFromDatabase(connection);
     log.info("Terminated webstats module.");
   }
 
-  private static Connection connectToDatabase(String jdbcString)
+  private static Connection connectToDatabase()
       throws SQLException {
     log.info("Connecting to database.");
     Connection connection = DriverManager.getConnection(jdbcString);





More information about the tor-commits mailing list