* 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
/** should we gzip each payload prior to sending it? */
private final static boolean SHOULD_COMPRESS = true;
private final static boolean SHOULD_DECOMPRESS = true;
/**
* 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.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.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() {
@ -75,6 +78,27 @@ class I2PSessionImpl2 extends I2PSessionImpl {
clearStates();
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
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
@ -92,8 +116,30 @@ class I2PSessionImpl2 extends I2PSessionImpl {
throws I2PSessionException {
if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message");
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);
}
@ -107,7 +153,8 @@ class I2PSessionImpl2 extends I2PSessionImpl {
_log.error("Error: message " + msgId + " already received!");
return null;
}
if (SHOULD_COMPRESS) {
// future - check magic number to see whether to decompress
if (SHOULD_DECOMPRESS) {
try {
return DataHelper.decompress(compressed);
} catch (IOException ioe) {

View File

@ -33,6 +33,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.TreeMap;
import java.util.zip.Deflater;
import net.i2p.util.ByteCache;
import net.i2p.util.OrderedProperties;
@ -852,15 +853,21 @@ public class DataHelper {
}
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 */
public static byte[] compress(byte orig[]) {
return compress(orig, 0, orig.length);
}
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 (size >= MAX_UNCOMPRESSED)
throw new IllegalArgumentException("tell jrandom size=" + size);
ReusableGZIPOutputStream out = ReusableGZIPOutputStream.acquire();
out.setLevel(level);
try {
out.write(orig, offset, size);
out.finish();

View File

@ -3,6 +3,7 @@ package net.i2p.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import net.i2p.data.DataHelper;
@ -51,6 +52,10 @@ public class ReusableGZIPOutputStream extends ResettableGZIPOutputStream {
public void reset() {
super.reset();
_buffer.reset();
def.setLevel(Deflater.BEST_COMPRESSION);
}
public void setLevel(int level) {
def.setLevel(level);
}
/** pull the contents of the stream written */
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
* Streaming:
- Add more info to Connection.toString() for debugging

View File

@ -17,7 +17,7 @@ import net.i2p.CoreVersion;
public class RouterVersion {
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 long BUILD = 10;
public final static long BUILD = 11;
public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
System.out.println("Router ID: " + RouterVersion.ID);