* I2CP Compression:

- Add i2cp.gzip option (default true)
      - Don't bother compressing if really small
This commit is contained in:
zzz
2008-11-15 15:03:19 +00:00
parent c7bb2e8f76
commit afa17a8c04
5 changed files with 73 additions and 4 deletions

View File

@ -39,6 +39,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private final static long SEND_TIMEOUT = 60 * 1000; // 60 seconds to send private final static long SEND_TIMEOUT = 60 * 1000; // 60 seconds to send
/** should we gzip each payload prior to sending it? */ /** should we gzip each payload prior to sending it? */
private final static boolean SHOULD_COMPRESS = true; private final static boolean SHOULD_COMPRESS = true;
private final static boolean SHOULD_DECOMPRESS = true;
/** /**
* Create a new session, reading the Destination, PrivateKey, and SigningPrivateKey * Create a new session, reading the Destination, PrivateKey, and SigningPrivateKey
@ -64,6 +65,8 @@ class I2PSessionImpl2 extends I2PSessionImpl {
_context.statManager().createRateStat("i2cp.receiveStatusTime.4", "How long it took to get status=4 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); _context.statManager().createRateStat("i2cp.receiveStatusTime.4", "How long it took to get status=4 back", "i2cp", new long[] { 60*1000, 10*60*1000 });
_context.statManager().createRateStat("i2cp.receiveStatusTime.5", "How long it took to get status=5 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); _context.statManager().createRateStat("i2cp.receiveStatusTime.5", "How long it took to get status=5 back", "i2cp", new long[] { 60*1000, 10*60*1000 });
_context.statManager().createRateStat("i2cp.receiveStatusTime", "How long it took to get any status", "i2cp", new long[] { 60*1000, 10*60*1000 }); _context.statManager().createRateStat("i2cp.receiveStatusTime", "How long it took to get any status", "i2cp", new long[] { 60*1000, 10*60*1000 });
_context.statManager().createRateStat("i2cp.tx.msgCompressed", "compressed size transferred", "i2cp", new long[] { 60*1000, 30*60*1000 });
_context.statManager().createRateStat("i2cp.tx.msgExpanded", "size before compression", "i2cp", new long[] { 60*1000, 30*60*1000 });
} }
protected long getTimeout() { protected long getTimeout() {
@ -75,6 +78,27 @@ class I2PSessionImpl2 extends I2PSessionImpl {
clearStates(); clearStates();
super.destroySession(sendDisconnect); super.destroySession(sendDisconnect);
} }
/** Don't bother if really small.
* Three 66-byte messages will fit in one tunnel message.
* Four messages don't fit no matter how small. So below 66 it isn't worth it.
* See ConnectionOptions.java in the streaming lib for similar calculations.
* Since we still have to pass it through gzip -0 the CPU savings
* is trivial but it's the best we can do for now. See below.
* i2cp.gzip defaults to SHOULD_COMPRESS = true.
* Perhaps the http server (which does its own compression)
* and P2P apps (with generally uncompressible data) should
* set to false.
*/
private static final int DONT_COMPRESS_SIZE = 66;
private boolean shouldCompress(int size) {
if (size <= DONT_COMPRESS_SIZE)
return false;
String p = getOptions().getProperty("i2cp.gzip");
if (p != null)
return Boolean.valueOf(p).booleanValue();
return SHOULD_COMPRESS;
}
@Override @Override
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException { public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
@ -92,8 +116,30 @@ class I2PSessionImpl2 extends I2PSessionImpl {
throws I2PSessionException { throws I2PSessionException {
if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message"); if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message");
if (isClosed()) throw new I2PSessionException("Already closed"); if (isClosed()) throw new I2PSessionException("Already closed");
if (SHOULD_COMPRESS) payload = DataHelper.compress(payload, offset, size);
else throw new IllegalStateException("we need to update sendGuaranteed to support partial send"); // Sadly there is no way to send something completely uncompressed in a backward-compatible way,
// so we have to still send it in a gzip format, which adds 23 bytes (2.4% for a 960-byte msg)
// (10 byte header + 5 byte block header + 8 byte trailer)
// In the future we can add a one-byte magic number != 0x1F to signal an uncompressed msg
// (Gzip streams start with 0x1F 0x8B 0x08)
// assuming we don't need the CRC-32 that comes with gzip (do we?)
// Maybe implement this soon in receiveMessage() below so we are ready
// in case we ever make an incompatible network change.
// This would save 22 of the 23 bytes and a little CPU.
boolean sc = shouldCompress(size);
if (sc)
payload = DataHelper.compress(payload, offset, size);
else
payload = DataHelper.compress(payload, offset, size, DataHelper.NO_COMPRESSION);
//else throw new IllegalStateException("we need to update sendGuaranteed to support partial send");
int compressed = payload.length;
if (_log.shouldLog(Log.INFO)) {
String d = dest.calculateHash().toBase64().substring(0,4);
_log.info("sending message to: " + d + " compress? " + sc + " sizeIn=" + size + " sizeOut=" + compressed);
}
_context.statManager().addRateData("i2cp.tx.msgCompressed", compressed, 0);
_context.statManager().addRateData("i2cp.tx.msgExpanded", size, 0);
return sendBestEffort(dest, payload, keyUsed, tagsSent); return sendBestEffort(dest, payload, keyUsed, tagsSent);
} }
@ -107,7 +153,8 @@ class I2PSessionImpl2 extends I2PSessionImpl {
_log.error("Error: message " + msgId + " already received!"); _log.error("Error: message " + msgId + " already received!");
return null; return null;
} }
if (SHOULD_COMPRESS) { // future - check magic number to see whether to decompress
if (SHOULD_DECOMPRESS) {
try { try {
return DataHelper.decompress(compressed); return DataHelper.decompress(compressed);
} catch (IOException ioe) { } catch (IOException ioe) {

View File

@ -33,6 +33,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.zip.Deflater;
import net.i2p.util.ByteCache; import net.i2p.util.ByteCache;
import net.i2p.util.OrderedProperties; import net.i2p.util.OrderedProperties;
@ -852,15 +853,21 @@ public class DataHelper {
} }
private static final int MAX_UNCOMPRESSED = 40*1024; private static final int MAX_UNCOMPRESSED = 40*1024;
public static final int MAX_COMPRESSION = Deflater.BEST_COMPRESSION;
public static final int NO_COMPRESSION = Deflater.NO_COMPRESSION;
/** compress the data and return a new GZIP compressed array */ /** compress the data and return a new GZIP compressed array */
public static byte[] compress(byte orig[]) { public static byte[] compress(byte orig[]) {
return compress(orig, 0, orig.length); return compress(orig, 0, orig.length);
} }
public static byte[] compress(byte orig[], int offset, int size) { public static byte[] compress(byte orig[], int offset, int size) {
return compress(orig, offset, size, MAX_COMPRESSION);
}
public static byte[] compress(byte orig[], int offset, int size, int level) {
if ((orig == null) || (orig.length <= 0)) return orig; if ((orig == null) || (orig.length <= 0)) return orig;
if (size >= MAX_UNCOMPRESSED) if (size >= MAX_UNCOMPRESSED)
throw new IllegalArgumentException("tell jrandom size=" + size); throw new IllegalArgumentException("tell jrandom size=" + size);
ReusableGZIPOutputStream out = ReusableGZIPOutputStream.acquire(); ReusableGZIPOutputStream out = ReusableGZIPOutputStream.acquire();
out.setLevel(level);
try { try {
out.write(orig, offset, size); out.write(orig, offset, size);
out.finish(); out.finish();

View File

@ -3,6 +3,7 @@ package net.i2p.util;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
@ -51,6 +52,10 @@ public class ReusableGZIPOutputStream extends ResettableGZIPOutputStream {
public void reset() { public void reset() {
super.reset(); super.reset();
_buffer.reset(); _buffer.reset();
def.setLevel(Deflater.BEST_COMPRESSION);
}
public void setLevel(int level) {
def.setLevel(level);
} }
/** pull the contents of the stream written */ /** pull the contents of the stream written */
public byte[] getData() { return _buffer.toByteArray(); } public byte[] getData() { return _buffer.toByteArray(); }

View File

@ -1,3 +1,13 @@
2008-11-15 zzz
* Build files:
- Don't die if depend not available
- Only verify Jetty hash once
- Add streaming lib tests to depend task
* I2CP Compression:
- Add i2cp.gzip option (default true)
- Add compression stats
- Don't bother compressing if really small
2008-11-13 zzz 2008-11-13 zzz
* Streaming: * Streaming:
- Add more info to Connection.toString() for debugging - Add more info to Connection.toString() for debugging

View File

@ -17,7 +17,7 @@ import net.i2p.CoreVersion;
public class RouterVersion { public class RouterVersion {
public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $";
public final static String VERSION = "0.6.4"; public final static String VERSION = "0.6.4";
public final static long BUILD = 10; public final static long BUILD = 11;
public static void main(String args[]) { public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
System.out.println("Router ID: " + RouterVersion.ID); System.out.println("Router ID: " + RouterVersion.ID);