[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [onionoo/master] Add performance metrics to the servlet.
commit 4ac8773be3c5d2296fd3ae293427a33f42d9fe7d
Author: Karsten Loesing <karsten.loesing@xxxxxxx>
Date: Tue Aug 12 09:41:47 2014 +0200
Add performance metrics to the servlet.
Implements #12655.
---
.../onionoo/server/PerformanceMetrics.java | 198 +++++++++++++++++++
.../torproject/onionoo/server/ResourceServlet.java | 12 ++
.../torproject/onionoo/server/ResponseBuilder.java | 27 ++-
.../onionoo/server/PerformanceMetricsTest.java | 199 ++++++++++++++++++++
4 files changed, 430 insertions(+), 6 deletions(-)
diff --git a/src/main/java/org/torproject/onionoo/server/PerformanceMetrics.java b/src/main/java/org/torproject/onionoo/server/PerformanceMetrics.java
new file mode 100644
index 0000000..5f780d9
--- /dev/null
+++ b/src/main/java/org/torproject/onionoo/server/PerformanceMetrics.java
@@ -0,0 +1,198 @@
+/* Copyright 2014 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.onionoo.server;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.torproject.onionoo.util.ApplicationFactory;
+import org.torproject.onionoo.util.DateTimeHelper;
+import org.torproject.onionoo.util.Time;
+
+class Counter {
+ int value = 0;
+ void increment() {
+ this.value++;
+ }
+ public String toString() {
+ return String.valueOf(this.value);
+ }
+ void clear() {
+ this.value = 0;
+ }
+}
+
+class MostFrequentString {
+ Map<String, Integer> stringFrequencies = new HashMap<String, Integer>();
+ void addString(String string) {
+ if (!this.stringFrequencies.containsKey(string)) {
+ this.stringFrequencies.put(string, 1);
+ } else {
+ this.stringFrequencies.put(string,
+ this.stringFrequencies.get(string) + 1);
+ }
+ }
+ public String toString() {
+ SortedMap<Integer, SortedSet<String>> sortedFrequencies =
+ new TreeMap<Integer, SortedSet<String>>(
+ Collections.reverseOrder());
+ if (this.stringFrequencies.isEmpty()) {
+ return "null (0)";
+ }
+ for (Map.Entry<String, Integer> e : stringFrequencies.entrySet()) {
+ if (!sortedFrequencies.containsKey(e.getValue())) {
+ sortedFrequencies.put(e.getValue(), new TreeSet<String>(
+ Arrays.asList(new String[] { e.getKey() } )));
+ } else {
+ sortedFrequencies.get(e.getValue()).add(e.getKey());
+ }
+ }
+ StringBuilder sb = new StringBuilder();
+ int stringsToAdd = 3, written = 0;
+ for (Map.Entry<Integer, SortedSet<String>> e :
+ sortedFrequencies.entrySet()) {
+ for (String string : e.getValue()) {
+ if (stringsToAdd-- > 0) {
+ sb.append((written++ > 0 ? ", " : "") + string + " ("
+ + e.getKey() + ")");
+ }
+ }
+ if (stringsToAdd == 0) {
+ break;
+ }
+ }
+ return sb.toString();
+ }
+ void clear() {
+ this.stringFrequencies.clear();
+ }
+}
+
+class IntegerDistribution {
+ int[] logValues = new int[64];
+ void addLong(long value) {
+ logValues[64 - Long.numberOfLeadingZeros(value)]++;
+ }
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ int totalValues = 0;
+ for (int i = 0; i < logValues.length; i++) {
+ totalValues += logValues[i];
+ }
+ int[] permilles = new int[] { 500, 900, 990, 999 };
+ if (totalValues > 0) {
+ int seenValues = 0;
+ for (int i = 0, j = 0; i < logValues.length; i++) {
+ seenValues += logValues[i];
+ while (j < permilles.length &&
+ (seenValues * 1000 > totalValues * permilles[j])) {
+ sb.append((j > 0 ? ", " : "") + "." + permilles[j]
+ + (i < logValues.length - 1 ? "<" + (1L << i)
+ : ">=" + (1L << i - 1)));
+ j++;
+ }
+ if (j == permilles.length) {
+ break;
+ }
+ }
+ } else {
+ for (int j = 0; j < permilles.length; j++) {
+ sb.append((j > 0 ? ", " : "") + "." + permilles[j] + "<null");
+ }
+ }
+ return sb.toString();
+ }
+ void clear() {
+ Arrays.fill(logValues, 0, logValues.length - 1, 0);
+ }
+}
+
+public class PerformanceMetrics {
+
+ private static final Object lock = new Object();
+
+ private static Time time;
+
+ private static long lastLoggedMillis = -1L;
+
+ private static final long LOG_INTERVAL = DateTimeHelper.ONE_HOUR;
+
+ private static Counter totalProcessedRequests = new Counter();
+
+ private static MostFrequentString
+ requestsByResourceType = new MostFrequentString(),
+ requestsByParameters = new MostFrequentString();
+
+ private static IntegerDistribution
+ matchingRelayDocuments = new IntegerDistribution(),
+ matchingBridgeDocuments = new IntegerDistribution(),
+ writtenChars = new IntegerDistribution(),
+ handleRequestMillis = new IntegerDistribution(),
+ buildResponseMillis = new IntegerDistribution();
+
+ public static void logStatistics(long receivedRequestMillis,
+ String resourceType, Collection<String> parameterKeys,
+ long parsedRequestMillis, int relayDocumentsWritten,
+ int bridgeDocumentsWritten, int charsWritten,
+ long writtenResponseMillis) {
+ synchronized (lock) {
+ if (time == null) {
+ time = ApplicationFactory.getTime();
+ }
+ if (lastLoggedMillis < 0L) {
+ lastLoggedMillis = time.currentTimeMillis();
+ } else if (receivedRequestMillis - lastLoggedMillis >
+ LOG_INTERVAL) {
+ System.err.println("Request statistics ("
+ + DateTimeHelper.format(lastLoggedMillis + LOG_INTERVAL)
+ + ", " + LOG_INTERVAL + " s):");
+ System.err.println(" Total processed requests: "
+ + totalProcessedRequests);
+ System.err.println(" Most frequently requested resource: "
+ + requestsByResourceType);
+ System.err.println(" Most frequently requested parameter "
+ + "combinations: " + requestsByParameters);
+ System.err.println(" Matching relays per request: "
+ + matchingRelayDocuments);
+ System.err.println(" Matching bridges per request: "
+ + matchingBridgeDocuments);
+ System.err.println(" Written characters per response: "
+ + writtenChars);
+ System.err.println(" Milliseconds to handle request: "
+ + handleRequestMillis);
+ System.err.println(" Milliseconds to build response: "
+ + buildResponseMillis);
+ totalProcessedRequests.clear();
+ requestsByResourceType.clear();
+ requestsByParameters.clear();
+ matchingRelayDocuments.clear();
+ matchingBridgeDocuments.clear();
+ writtenChars.clear();
+ handleRequestMillis.clear();
+ buildResponseMillis.clear();
+ do {
+ lastLoggedMillis += LOG_INTERVAL;
+ } while (receivedRequestMillis - lastLoggedMillis > LOG_INTERVAL);
+ }
+ totalProcessedRequests.increment();
+ handleRequestMillis.addLong(parsedRequestMillis
+ - receivedRequestMillis);
+ requestsByResourceType.addString(resourceType);
+ requestsByParameters.addString(parameterKeys.toString());
+ matchingRelayDocuments.addLong(relayDocumentsWritten);
+ matchingBridgeDocuments.addLong(bridgeDocumentsWritten);
+ writtenChars.addLong(charsWritten);
+ buildResponseMillis.addLong(writtenResponseMillis
+ - parsedRequestMillis);
+ }
+ }
+
+}
+
diff --git a/src/main/java/org/torproject/onionoo/server/ResourceServlet.java b/src/main/java/org/torproject/onionoo/server/ResourceServlet.java
index 6f01448..481462e 100644
--- a/src/main/java/org/torproject/onionoo/server/ResourceServlet.java
+++ b/src/main/java/org/torproject/onionoo/server/ResourceServlet.java
@@ -19,6 +19,7 @@ import javax.servlet.http.HttpServletResponse;
import org.torproject.onionoo.util.ApplicationFactory;
import org.torproject.onionoo.util.DateTimeHelper;
+import org.torproject.onionoo.util.Time;
public class ResourceServlet extends HttpServlet {
@@ -74,6 +75,9 @@ public class ResourceServlet extends HttpServlet {
return;
}
+ Time time = ApplicationFactory.getTime();
+ long receivedRequestMillis = time.currentTimeMillis();
+
String uri = request.getRequestURI();
if (uri.startsWith("/onionoo/")) {
uri = uri.substring("/onionoo".length());
@@ -279,6 +283,7 @@ public class ResourceServlet extends HttpServlet {
rh.setFamily(family);
}
rh.handleRequest();
+ long parsedRequestMillis = time.currentTimeMillis();
ResponseBuilder rb = new ResponseBuilder();
rb.setResourceType(resourceType);
@@ -301,8 +306,15 @@ public class ResourceServlet extends HttpServlet {
response.setCharacterEncoding("utf-8");
PrintWriter pw = response.getWriter();
rb.buildResponse(pw);
+ int relayDocumentsWritten = rh.getOrderedRelays().size();
+ int bridgeDocumentsWritten = rh.getOrderedBridges().size();
+ int charsWritten = rb.getCharsWritten();
pw.flush();
pw.close();
+ long writtenResponseMillis = time.currentTimeMillis();
+ PerformanceMetrics.logStatistics(receivedRequestMillis, resourceType,
+ parameterMap.keySet(), parsedRequestMillis, relayDocumentsWritten,
+ bridgeDocumentsWritten, charsWritten, writtenResponseMillis);
}
private static Pattern searchParameterPattern =
diff --git a/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java b/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java
index 161692c..458f7db 100644
--- a/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java
+++ b/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java
@@ -64,31 +64,46 @@ public class ResponseBuilder {
writeBridges(orderedBridges, pw);
}
+ private int charsWritten = 0;
+ public int getCharsWritten() {
+ return this.charsWritten;
+ }
+
private void writeRelays(List<SummaryDocument> relays, PrintWriter pw) {
- pw.write("{\"relays_published\":\"" + relaysPublishedString
- + "\",\n\"relays\":[");
+ String header = "{\"relays_published\":\"" + relaysPublishedString
+ + "\",\n\"relays\":[";
+ this.charsWritten += header.length();
+ pw.write(header);
int written = 0;
for (SummaryDocument entry : relays) {
String lines = this.formatNodeStatus(entry);
if (lines.length() > 0) {
+ this.charsWritten += (written > 0 ? 2 : 1) + lines.length();
pw.print((written++ > 0 ? ",\n" : "\n") + lines);
}
}
- pw.print("\n],\n");
+ String footer = "\n],\n";
+ this.charsWritten += footer.length();
+ pw.print(footer);
}
private void writeBridges(List<SummaryDocument> bridges,
PrintWriter pw) {
- pw.write("\"bridges_published\":\"" + bridgesPublishedString
- + "\",\n\"bridges\":[");
+ String header = "\"bridges_published\":\"" + bridgesPublishedString
+ + "\",\n\"bridges\":[";
+ this.charsWritten += header.length();
+ pw.write(header);
int written = 0;
for (SummaryDocument entry : bridges) {
String lines = this.formatNodeStatus(entry);
if (lines.length() > 0) {
+ this.charsWritten += (written > 0 ? 2 : 1) + lines.length();
pw.print((written++ > 0 ? ",\n" : "\n") + lines);
}
}
- pw.print("\n]}\n");
+ String footer = "\n]}\n";
+ this.charsWritten += footer.length();
+ pw.print(footer);
}
private String formatNodeStatus(SummaryDocument entry) {
diff --git a/src/test/java/org/torproject/onionoo/server/PerformanceMetricsTest.java b/src/test/java/org/torproject/onionoo/server/PerformanceMetricsTest.java
new file mode 100644
index 0000000..7a6614e
--- /dev/null
+++ b/src/test/java/org/torproject/onionoo/server/PerformanceMetricsTest.java
@@ -0,0 +1,199 @@
+/* Copyright 2014 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.onionoo.server;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class PerformanceMetricsTest {
+
+ @Test
+ public void testCounterZero() {
+ Counter c = new Counter();
+ assertEquals("0", c.toString());
+ }
+
+ @Test
+ public void testCounterClear() {
+ Counter c = new Counter();
+ c.increment();
+ c.clear();
+ assertEquals("0", c.toString());
+ }
+
+ @Test
+ public void testCounterOne() {
+ Counter c = new Counter();
+ c.increment();
+ assertEquals("1", c.toString());
+ }
+
+ @Test
+ public void testCounterTwo() {
+ Counter c = new Counter();
+ c.increment();
+ c.increment();
+ assertEquals("2", c.toString());
+ }
+
+ @Test
+ public void testCounterOverflow() {
+ Counter c = new Counter();
+ c.value = Integer.MAX_VALUE;
+ c.increment();
+ assertEquals(String.valueOf(Integer.MIN_VALUE), c.toString());
+ }
+
+ @Test
+ public void testMostFrequentStringNothing() {
+ MostFrequentString mfs = new MostFrequentString();
+ assertEquals("null (0)", mfs.toString());
+ }
+
+ @Test
+ public void testMostFrequentStringClear() {
+ MostFrequentString mfs = new MostFrequentString();
+ mfs.addString("foo");
+ mfs.clear();
+ assertEquals("null (0)", mfs.toString());
+ }
+
+ @Test
+ public void testMostFrequentStringOneFoo() {
+ MostFrequentString mfs = new MostFrequentString();
+ mfs.addString("foo");
+ assertEquals("foo (1)", mfs.toString());
+ }
+
+ @Test
+ public void testMostFrequentStringTwoFoos() {
+ MostFrequentString mfs = new MostFrequentString();
+ mfs.addString("foo");
+ mfs.addString("foo");
+ assertEquals("foo (2)", mfs.toString());
+ }
+
+ @Test
+ public void testMostFrequentStringTwoFoosOneBar() {
+ MostFrequentString mfs = new MostFrequentString();
+ mfs.addString("foo");
+ mfs.addString("foo");
+ mfs.addString("bar");
+ assertEquals("foo (2), bar (1)", mfs.toString());
+ }
+
+ @Test
+ public void testMostFrequentStringABCD() {
+ MostFrequentString mfs = new MostFrequentString();
+ mfs.addString("A");
+ mfs.addString("B");
+ mfs.addString("C");
+ mfs.addString("D");
+ assertEquals("A (1), B (1), C (1)", mfs.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionNothing() {
+ IntegerDistribution id = new IntegerDistribution();
+ assertEquals(".500<null, .900<null, .990<null, .999<null",
+ id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionClear() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(1);
+ id.clear();
+ assertEquals(".500<null, .900<null, .990<null, .999<null",
+ id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionZero() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(0);
+ assertEquals(".500<1, .900<1, .990<1, .999<1", id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionOne() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(1);
+ assertEquals(".500<2, .900<2, .990<2, .999<2", id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionTwo() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(2);
+ assertEquals(".500<4, .900<4, .990<4, .999<4", id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionThree() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(3);
+ assertEquals(".500<4, .900<4, .990<4, .999<4", id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionFour() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(4);
+ assertEquals(".500<8, .900<8, .990<8, .999<8", id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionFive() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(5);
+ assertEquals(".500<8, .900<8, .990<8, .999<8", id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionFifteen() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(15);
+ assertEquals(".500<16, .900<16, .990<16, .999<16", id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionSixteen() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(16);
+ assertEquals(".500<32, .900<32, .990<32, .999<32", id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionSeventeen() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(17);
+ assertEquals(".500<32, .900<32, .990<32, .999<32", id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionMaxLong() {
+ IntegerDistribution id = new IntegerDistribution();
+ id.addLong(Long.MAX_VALUE);
+ long val = Long.highestOneBit(Long.MAX_VALUE);
+ assertEquals(".500>=" + val + ", .900>=" + val + ", .990>=" + val
+ + ", .999>=" + val, id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionToThirtyTwo() {
+ IntegerDistribution id = new IntegerDistribution();
+ for (int i = 13; i <= 32; i++) id.addLong(i);
+ assertEquals(".500<32, .900<32, .990<64, .999<64", id.toString());
+ }
+
+ @Test
+ public void testIntegerDistributionToOneHundredTwentyEight() {
+ IntegerDistribution id = new IntegerDistribution();
+ for (int i = 27; i <= 128; i++) id.addLong(i);
+ assertEquals(".500<128, .900<128, .990<128, .999<256",
+ id.toString());
+ }
+}
+
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits