瀏覽代碼

- Added throuput statistics

Sebastian Stenzel 10 年之前
父節點
當前提交
863b2ec423
共有 19 個文件被更改,包括 332 次插入32 次删除
  1. 1 1
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/FileTimeUtils.java
  2. 4 14
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java
  3. 0 13
      main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java
  4. 4 0
      main/crypto-api/pom.xml
  5. 8 0
      main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptor.java
  6. 20 0
      main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java
  7. 26 0
      main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorIOSampling.java
  8. 8 0
      main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorIOSupport.java
  9. 161 0
      main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java
  10. 8 0
      main/crypto-api/src/main/java/org/cryptomator/crypto/SensitiveDataSwipeListener.java
  11. 0 0
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java
  12. 0 0
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/StorageCryptingException.java
  13. 0 0
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedKeyLengthException.java
  14. 0 0
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/WrongPasswordException.java
  15. 0 1
      main/ui/src/main/java/org/cryptomator/ui/InitializeController.java
  16. 79 1
      main/ui/src/main/java/org/cryptomator/ui/UnlockedController.java
  17. 4 2
      main/ui/src/main/java/org/cryptomator/ui/model/Directory.java
  18. 1 0
      main/ui/src/main/resources/localization.properties
  19. 8 0
      main/ui/src/main/resources/unlocked.fxml

+ 1 - 1
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/FileTimeUtils.java

@@ -21,7 +21,7 @@ public final class FileTimeUtils {
 	}
 
 	public static String toRfc1123String(FileTime time) {
-		final Temporal date = OffsetDateTime.ofInstant(time.toInstant(), ZoneOffset.UTC.normalized());
+		final Temporal date = OffsetDateTime.ofInstant(time.toInstant(), ZoneOffset.UTC);
 		return DateTimeFormatter.RFC_1123_DATE_TIME.format(date);
 	}
 

+ 4 - 14
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java

@@ -42,7 +42,6 @@ import javax.crypto.spec.SecretKeySpec;
 
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.cryptomator.crypto.AbstractCryptor;
 import org.cryptomator.crypto.CryptorIOSupport;
@@ -78,11 +77,6 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 	 */
 	private static final int AES_KEY_LENGTH;
 
-	/**
-	 * 
-	 */
-	private static final byte[] EMPTY_MASTER_KEY = new byte[MASTER_KEY_LENGTH];
-
 	/**
 	 * Jackson JSON-Mapper.
 	 */
@@ -92,7 +86,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 	 * The decrypted master key. Its lifecycle starts with {@link #randomData(int)} or {@link #encryptMasterKey(Path, CharSequence)}. Its
 	 * lifecycle ends with {@link #swipeSensitiveData()}.
 	 */
-	private final byte[] masterKey = Arrays.copyOf(EMPTY_MASTER_KEY, MASTER_KEY_LENGTH);
+	private final byte[] masterKey = new byte[MASTER_KEY_LENGTH];
 
 	private static final int SIZE_OF_LONG = Long.SIZE / Byte.SIZE;
 	private static final int SIZE_OF_INT = Integer.SIZE / Byte.SIZE;
@@ -108,10 +102,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 		}
 	}
 
-	/**
-	 * Fills the masterkey with new random bytes.
-	 */
-	public void randomizeMasterKey() {
+	public Aes256Cryptor() {
 		SECURE_PRNG.setSeed(SECURE_PRNG.generateSeed(PRNG_SEED_LENGTH));
 		SECURE_PRNG.nextBytes(this.masterKey);
 	}
@@ -119,10 +110,8 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 	/**
 	 * Encrypts the current masterKey with the given password and writes the result to the given output stream.
 	 */
+	@Override
 	public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
-		if (ArrayUtils.isEquals(this.masterKey, EMPTY_MASTER_KEY)) {
-			throw new IllegalStateException("Masterkey not yet initialized.");
-		}
 		try {
 			// derive key:
 			final byte[] userSalt = randomData(SALT_LENGTH);
@@ -157,6 +146,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 	 * @throws UnsupportedKeyLengthException If the masterkey has been encrypted with a higher key length than supported by the system. In
 	 *             this case Java JCE needs to be installed.
 	 */
+	@Override
 	public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
 		byte[] decrypted = new byte[0];
 		try {

+ 0 - 13
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java

@@ -50,20 +50,11 @@ public class Aes256CryptorTest {
 
 	/* ------------------------------------------------------------------------------- */
 
-	@Test(expected = IllegalStateException.class)
-	public void testUninitializedMasterKey() throws IOException {
-		final String pw = "asd";
-		final Aes256Cryptor cryptor = new Aes256Cryptor();
-		final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
-		cryptor.encryptMasterKey(out, pw);
-	}
-
 	@Test
 	public void testCorrectPassword() throws IOException, WrongPasswordException, DecryptFailedException, UnsupportedKeyLengthException {
 		final String pw = "asd";
 		final Aes256Cryptor cryptor = new Aes256Cryptor();
 		final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
-		cryptor.randomizeMasterKey();
 		cryptor.encryptMasterKey(out, pw);
 		cryptor.swipeSensitiveData();
 
@@ -77,7 +68,6 @@ public class Aes256CryptorTest {
 		final String pw = "asd";
 		final Aes256Cryptor cryptor = new Aes256Cryptor();
 		final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
-		cryptor.randomizeMasterKey();
 		cryptor.encryptMasterKey(out, pw);
 		cryptor.swipeSensitiveData();
 
@@ -92,7 +82,6 @@ public class Aes256CryptorTest {
 		final String pw = "asd";
 		final Aes256Cryptor cryptor = new Aes256Cryptor();
 		final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
-		cryptor.randomizeMasterKey();
 		cryptor.encryptMasterKey(out, pw);
 		cryptor.swipeSensitiveData();
 
@@ -107,7 +96,6 @@ public class Aes256CryptorTest {
 		final String pw = "asd";
 		final Aes256Cryptor cryptor = new Aes256Cryptor();
 		final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
-		cryptor.randomizeMasterKey();
 		cryptor.encryptMasterKey(out, pw);
 		cryptor.swipeSensitiveData();
 
@@ -120,7 +108,6 @@ public class Aes256CryptorTest {
 	public void testEncryptionOfFilenames() throws IOException {
 		final CryptorIOSupport ioSupportMock = new CryptoIOSupportMock();
 		final Aes256Cryptor cryptor = new Aes256Cryptor();
-		cryptor.randomizeMasterKey();
 
 		// short path components
 		final String originalPath1 = "foo/bar/baz";

+ 4 - 0
main/crypto-api/pom.xml

@@ -22,5 +22,9 @@
 			<groupId>commons-io</groupId>
 			<artifactId>commons-io</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+		</dependency>
 	</dependencies>
 </project>

+ 8 - 0
main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptor.java

@@ -1,3 +1,11 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Sebastian Stenzel
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ * 
+ * Contributors:
+ *     Sebastian Stenzel - initial API and implementation
+ ******************************************************************************/
 package org.cryptomator.crypto;
 
 import java.util.HashSet;

+ 20 - 0
main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java

@@ -15,11 +15,31 @@ import java.nio.channels.SeekableByteChannel;
 import java.nio.file.DirectoryStream.Filter;
 import java.nio.file.Path;
 
+import org.cryptomator.crypto.exceptions.DecryptFailedException;
+import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.WrongPasswordException;
+
 /**
  * Provides access to cryptographic functions. All methods are threadsafe.
  */
 public interface Cryptor extends SensitiveDataSwipeListener {
 
+	/**
+	 * Encrypts the current masterKey with the given password and writes the result to the given output stream.
+	 */
+	void encryptMasterKey(OutputStream out, CharSequence password) throws IOException;
+
+	/**
+	 * Reads the encrypted masterkey from the given input stream and decrypts it with the given password.
+	 * 
+	 * @throws DecryptFailedException If the decryption failed for various reasons (including wrong password).
+	 * @throws WrongPasswordException If the provided password was wrong. Note: Sometimes the algorithm itself fails due to a wrong
+	 *             password. In this case a DecryptFailedException will be thrown.
+	 * @throws UnsupportedKeyLengthException If the masterkey has been encrypted with a higher key length than supported by the system. In
+	 *             this case Java JCE needs to be installed.
+	 */
+	void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException;
+
 	/**
 	 * Encrypts each plaintext path component for its own.
 	 * 

+ 26 - 0
main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorIOSampling.java

@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Sebastian Stenzel
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ * 
+ * Contributors:
+ *     Sebastian Stenzel - initial API and implementation
+ ******************************************************************************/
+package org.cryptomator.crypto;
+
+/**
+ * Optional monitoring interface. If a cryptor implements this interface, it counts bytes de- and encrypted in a thread-safe manner.
+ */
+public interface CryptorIOSampling {
+
+	/**
+	 * @return Number of encrypted bytes since the last reset.
+	 */
+	Long pollEncryptedBytes(boolean resetCounter);
+
+	/**
+	 * @return Number of decrypted bytes since the last reset.
+	 */
+	Long pollDecryptedBytes(boolean resetCounter);
+
+}

+ 8 - 0
main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorIOSupport.java

@@ -1,3 +1,11 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Sebastian Stenzel
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ * 
+ * Contributors:
+ *     Sebastian Stenzel - initial API and implementation
+ ******************************************************************************/
 package org.cryptomator.crypto;
 
 import java.io.IOException;

+ 161 - 0
main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java

@@ -0,0 +1,161 @@
+package org.cryptomator.crypto;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.DirectoryStream.Filter;
+import java.nio.file.Path;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.lang3.StringUtils;
+import org.cryptomator.crypto.exceptions.DecryptFailedException;
+import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.WrongPasswordException;
+
+public class SamplingDecorator implements Cryptor, CryptorIOSampling {
+
+	private final Cryptor cryptor;
+	private final AtomicLong encryptedBytes;
+	private final AtomicLong decryptedBytes;
+
+	private SamplingDecorator(Cryptor cryptor) {
+		this.cryptor = cryptor;
+		encryptedBytes = new AtomicLong();
+		decryptedBytes = new AtomicLong();
+	}
+
+	public static Cryptor decorate(Cryptor cryptor) {
+		return new SamplingDecorator(cryptor);
+	}
+
+	@Override
+	public void swipeSensitiveData() {
+		cryptor.swipeSensitiveData();
+	}
+
+	@Override
+	public Long pollEncryptedBytes(boolean resetCounter) {
+		if (resetCounter) {
+			return encryptedBytes.getAndSet(0);
+		} else {
+			return encryptedBytes.get();
+		}
+	}
+
+	@Override
+	public Long pollDecryptedBytes(boolean resetCounter) {
+		if (resetCounter) {
+			return decryptedBytes.getAndSet(0);
+		} else {
+			return decryptedBytes.get();
+		}
+	}
+
+	/* Cryptor */
+
+	@Override
+	public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
+		cryptor.encryptMasterKey(out, password);
+	}
+
+	@Override
+	public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
+		cryptor.decryptMasterKey(in, password);
+	}
+
+	@Override
+	public String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) {
+		encryptedBytes.addAndGet(StringUtils.length(cleartextPath));
+		return cryptor.encryptPath(cleartextPath, encryptedPathSep, cleartextPathSep, ioSupport);
+	}
+
+	@Override
+	public String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) {
+		decryptedBytes.addAndGet(StringUtils.length(encryptedPath));
+		return cryptor.decryptPath(encryptedPath, encryptedPathSep, cleartextPathSep, ioSupport);
+	}
+
+	@Override
+	public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException {
+		return cryptor.decryptedContentLength(encryptedFile);
+	}
+
+	@Override
+	public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException {
+		final OutputStream countingInputStream = new CountingOutputStream(decryptedBytes, plaintextFile);
+		return cryptor.decryptedFile(encryptedFile, countingInputStream);
+	}
+
+	@Override
+	public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException {
+		final InputStream countingInputStream = new CountingInputStream(encryptedBytes, plaintextFile);
+		return cryptor.encryptFile(countingInputStream, encryptedFile);
+	}
+
+	@Override
+	public Filter<Path> getPayloadFilesFilter() {
+		return cryptor.getPayloadFilesFilter();
+	}
+
+	@Override
+	public void addSensitiveDataSwipeListener(SensitiveDataSwipeListener listener) {
+		cryptor.addSensitiveDataSwipeListener(listener);
+	}
+
+	@Override
+	public void removeSensitiveDataSwipeListener(SensitiveDataSwipeListener listener) {
+		cryptor.removeSensitiveDataSwipeListener(listener);
+	}
+
+	private class CountingInputStream extends InputStream {
+
+		private final InputStream in;
+		private final AtomicLong counter;
+
+		private CountingInputStream(AtomicLong counter, InputStream in) {
+			this.in = in;
+			this.counter = counter;
+		}
+
+		@Override
+		public int read() throws IOException {
+			int count = in.read();
+			counter.addAndGet(count);
+			return count;
+		}
+
+		@Override
+		public int read(byte[] b, int off, int len) throws IOException {
+			int count = in.read(b, off, len);
+			counter.addAndGet(count);
+			return count;
+		}
+
+	}
+
+	private class CountingOutputStream extends OutputStream {
+
+		private final OutputStream out;
+		private final AtomicLong counter;
+
+		private CountingOutputStream(AtomicLong counter, OutputStream out) {
+			this.out = out;
+			this.counter = counter;
+		}
+
+		@Override
+		public void write(int b) throws IOException {
+			counter.incrementAndGet();
+			out.write(b);
+		}
+
+		@Override
+		public void write(byte[] b, int off, int len) throws IOException {
+			counter.addAndGet(len);
+			out.write(b, off, len);
+		}
+
+	}
+
+}

+ 8 - 0
main/crypto-api/src/main/java/org/cryptomator/crypto/SensitiveDataSwipeListener.java

@@ -1,3 +1,11 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Sebastian Stenzel
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ * 
+ * Contributors:
+ *     Sebastian Stenzel - initial API and implementation
+ ******************************************************************************/
 package org.cryptomator.crypto;
 
 public interface SensitiveDataSwipeListener {

main/crypto-aes/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java → main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java


main/crypto-aes/src/main/java/org/cryptomator/crypto/exceptions/StorageCryptingException.java → main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/StorageCryptingException.java


main/crypto-aes/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedKeyLengthException.java → main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedKeyLengthException.java


main/crypto-aes/src/main/java/org/cryptomator/crypto/exceptions/WrongPasswordException.java → main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/WrongPasswordException.java


+ 0 - 1
main/ui/src/main/java/org/cryptomator/ui/InitializeController.java

@@ -132,7 +132,6 @@ public class InitializeController implements Initializable {
 		OutputStream masterKeyOutputStream = null;
 		try {
 			masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
-			directory.getCryptor().randomizeMasterKey();
 			directory.getCryptor().encryptMasterKey(masterKeyOutputStream, password);
 			encryptExistingContents();
 			directory.getCryptor().swipeSensitiveData();

+ 79 - 1
main/ui/src/main/java/org/cryptomator/ui/UnlockedController.java

@@ -11,26 +11,44 @@ package org.cryptomator.ui;
 import java.net.URL;
 import java.util.ResourceBundle;
 
+import javafx.animation.Animation;
+import javafx.animation.KeyFrame;
+import javafx.animation.Timeline;
 import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
 import javafx.fxml.FXML;
 import javafx.fxml.Initializable;
+import javafx.scene.chart.LineChart;
+import javafx.scene.chart.NumberAxis;
+import javafx.scene.chart.XYChart.Data;
+import javafx.scene.chart.XYChart.Series;
 import javafx.scene.control.Label;
+import javafx.util.Duration;
 
+import org.cryptomator.crypto.CryptorIOSampling;
 import org.cryptomator.ui.model.Directory;
 
 public class UnlockedController implements Initializable {
 
+	private static final int IO_SAMPLING_STEPS = 100;
+	private static final double IO_SAMPLING_INTERVAL = 0.25;
 	private ResourceBundle rb;
 	private LockListener listener;
 	private Directory directory;
+	private Timeline ioAnimation;
 
 	@FXML
 	private Label messageLabel;
 
+	@FXML
+	private LineChart<Number, Number> ioGraph;
+
+	@FXML
+	private NumberAxis xAxis;
+
 	@Override
 	public void initialize(URL url, ResourceBundle rb) {
 		this.rb = rb;
-
 	}
 
 	@FXML
@@ -43,6 +61,60 @@ public class UnlockedController implements Initializable {
 		}
 	}
 
+	// ****************************************
+	// IO Graph
+	// ****************************************
+
+	private void startIoSampling(final CryptorIOSampling sampler) {
+		final Series<Number, Number> decryptedBytes = new Series<>();
+		decryptedBytes.setName("decrypted");
+		final Series<Number, Number> encryptedBytes = new Series<>();
+		encryptedBytes.setName("encrypted");
+
+		ioGraph.getData().add(decryptedBytes);
+		ioGraph.getData().add(encryptedBytes);
+
+		ioAnimation = new Timeline();
+		ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(sampler, decryptedBytes, encryptedBytes)));
+		ioAnimation.setCycleCount(Animation.INDEFINITE);
+		ioAnimation.play();
+	}
+
+	private class IoSamplingAnimationHandler implements EventHandler<ActionEvent> {
+
+		private static final double BYTES_TO_MEGABYTES_FACTOR = IO_SAMPLING_INTERVAL / 1024.0 / 1024.0;
+		private final CryptorIOSampling sampler;
+		private final Series<Number, Number> decryptedBytes;
+		private final Series<Number, Number> encryptedBytes;
+		private int step = 0;
+
+		public IoSamplingAnimationHandler(CryptorIOSampling sampler, Series<Number, Number> decryptedBytes, Series<Number, Number> encryptedBytes) {
+			this.sampler = sampler;
+			this.decryptedBytes = decryptedBytes;
+			this.encryptedBytes = encryptedBytes;
+		}
+
+		@Override
+		public void handle(ActionEvent event) {
+			step++;
+
+			final double decryptedMb = sampler.pollDecryptedBytes(true) * BYTES_TO_MEGABYTES_FACTOR;
+			decryptedBytes.getData().add(new Data<Number, Number>(step, decryptedMb));
+			if (decryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
+				decryptedBytes.getData().remove(0);
+			}
+
+			final double encrypteddMb = sampler.pollEncryptedBytes(true) * BYTES_TO_MEGABYTES_FACTOR;
+			encryptedBytes.getData().add(new Data<Number, Number>(step, encrypteddMb));
+			if (encryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
+				encryptedBytes.getData().remove(0);
+			}
+
+			xAxis.setLowerBound(step - IO_SAMPLING_STEPS);
+			xAxis.setUpperBound(step);
+		}
+	}
+
 	/* Getter/Setter */
 
 	public Directory getDirectory() {
@@ -53,6 +125,12 @@ public class UnlockedController implements Initializable {
 		this.directory = directory;
 		final String msg = String.format(rb.getString("unlocked.messageLabel.runningOnPort"), directory.getServer().getPort());
 		messageLabel.setText(msg);
+
+		if (directory.getCryptor() instanceof CryptorIOSampling) {
+			startIoSampling((CryptorIOSampling) directory.getCryptor());
+		} else {
+			ioGraph.setVisible(false);
+		}
 	}
 
 	public LockListener getListener() {

+ 4 - 2
main/ui/src/main/java/org/cryptomator/ui/model/Directory.java

@@ -6,6 +6,8 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 
 import org.apache.commons.lang3.StringUtils;
+import org.cryptomator.crypto.Cryptor;
+import org.cryptomator.crypto.SamplingDecorator;
 import org.cryptomator.crypto.aes256.Aes256Cryptor;
 import org.cryptomator.ui.MainApplication;
 import org.cryptomator.ui.util.MasterKeyFilter;
@@ -26,7 +28,7 @@ public class Directory implements Serializable {
 	private static final Logger LOG = LoggerFactory.getLogger(Directory.class);
 
 	private final WebDAVServer server = new WebDAVServer();
-	private final Aes256Cryptor cryptor = new Aes256Cryptor();
+	private final Cryptor cryptor = SamplingDecorator.decorate(new Aes256Cryptor());
 	private final Path path;
 	private boolean unlocked;
 	private String unmountCommand;
@@ -97,7 +99,7 @@ public class Directory implements Serializable {
 		return path.getFileName().toString();
 	}
 
-	public Aes256Cryptor getCryptor() {
+	public Cryptor getCryptor() {
 		return cryptor;
 	}
 

+ 1 - 0
main/ui/src/main/resources/localization.properties

@@ -36,6 +36,7 @@ unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
 # unlocked.fxml
 unlocked.messageLabel.runningOnPort=Vault is accessible via WebDAV on local port %d.
 unlocked.button.lock=Lock vault
+unlocked.ioGraph.yAxis.label=Throughput (MiB/s)
 
 
 # tray icon

+ 8 - 0
main/ui/src/main/resources/unlocked.fxml

@@ -14,6 +14,8 @@
 <?import javafx.scene.text.*?>
 <?import java.lang.String?>
 <?import javafx.scene.control.Label?>
+<?import javafx.scene.chart.LineChart?>
+<?import javafx.scene.chart.NumberAxis?>
 
 
 <GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.UnlockedController" xmlns:fx="http://javafx.com/fxml">
@@ -32,6 +34,12 @@
 		
 		<!-- Row 1 -->
 		<Button text="%unlocked.button.lock" defaultButton="true" GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#closeVault" focusTraversable="false"/>
+		
+		<!-- Row 2 -->
+		<LineChart fx:id="ioGraph" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" animated="false" createSymbols="false" prefHeight="300.0" legendVisible="true" legendSide="BOTTOM" verticalZeroLineVisible="false" verticalGridLinesVisible="false" horizontalGridLinesVisible="true">
+			<xAxis><NumberAxis fx:id="xAxis" forceZeroInRange="false" tickMarkVisible="false" minorTickVisible="false" tickLabelsVisible="false" autoRanging="false"/></xAxis>
+        	<yAxis><NumberAxis label="%unlocked.ioGraph.yAxis.label" autoRanging="true" forceZeroInRange="true" /></yAxis>
+		</LineChart>
 	</children>
 </GridPane>