All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datakernel.csp.process.ChannelLZ4Compressor Maven / Gradle / Ivy

Go to download

Communicating sequential process via channels, similar to Golang's channels. A channel could be imagine as a pipe which connects some processes.

The newest version!
/*
 * Copyright (C) 2015 SoftIndex LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.datakernel.csp.process;

import io.datakernel.bytebuf.ByteBuf;
import io.datakernel.bytebuf.ByteBufPool;
import io.datakernel.common.inspector.AbstractInspector;
import io.datakernel.common.inspector.BaseInspector;
import io.datakernel.csp.*;
import io.datakernel.csp.dsl.WithChannelTransformer;
import io.datakernel.eventloop.jmx.ValueStats;
import io.datakernel.jmx.api.JmxAttribute;
import net.jpountz.lz4.LZ4Compressor;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.xxhash.StreamingXXHash32;
import net.jpountz.xxhash.XXHashFactory;
import org.jetbrains.annotations.Nullable;

import java.time.Duration;

import static io.datakernel.common.Preconditions.checkArgument;
import static java.lang.Math.max;

public final class ChannelLZ4Compressor extends AbstractCommunicatingProcess
		implements WithChannelTransformer {
	public static final byte[] MAGIC = {'L', 'Z', '4', 'B', 'l', 'o', 'c', 'k'};
	public static final int MAGIC_LENGTH = MAGIC.length;

	public static final int HEADER_LENGTH =
			MAGIC_LENGTH    // magic bytes
					+ 1     // token
					+ 4     // compressed length
					+ 4     // decompressed length
					+ 4;    // checksum

	static final int COMPRESSION_LEVEL_BASE = 10;

	static final int COMPRESSION_METHOD_RAW = 0x10;
	static final int COMPRESSION_METHOD_LZ4 = 0x20;

	static final int DEFAULT_SEED = 0x9747b28c;

	private static final int MIN_BLOCK_SIZE = 64;

	private final LZ4Compressor compressor;
	private final StreamingXXHash32 checksum = XXHashFactory.fastestInstance().newStreamingHash32(DEFAULT_SEED);

	private ChannelSupplier input;
	private ChannelConsumer output;

	@Nullable
	private Inspector inspector;

	public interface Inspector extends BaseInspector {
		void onBuf(ByteBuf in, ByteBuf out);
	}

	public static class JmxInspector extends AbstractInspector implements Inspector {
		public static final Duration SMOOTHING_WINDOW = Duration.ofMinutes(1);

		private final ValueStats bytesIn = ValueStats.create(SMOOTHING_WINDOW);
		private final ValueStats bytesOut = ValueStats.create(SMOOTHING_WINDOW);

		@Override
		public void onBuf(ByteBuf in, ByteBuf out) {
			bytesIn.recordValue(in.readRemaining());
			bytesOut.recordValue(out.readRemaining());
		}

		@JmxAttribute
		public ValueStats getBytesIn() {
			return bytesIn;
		}

		@JmxAttribute
		public ValueStats getBytesOut() {
			return bytesOut;
		}
	}

	// region creators
	private ChannelLZ4Compressor(LZ4Compressor compressor) {
		this.compressor = compressor;
	}

	public static ChannelLZ4Compressor create(LZ4Compressor compressor) {
		return new ChannelLZ4Compressor(compressor);
	}

	public static ChannelLZ4Compressor create(int compressionLevel) {
		return compressionLevel == 0 ? createFastCompressor() : createHighCompressor(compressionLevel);
	}

	public static ChannelLZ4Compressor createFastCompressor() {
		return new ChannelLZ4Compressor(LZ4Factory.fastestInstance().fastCompressor());
	}

	public static ChannelLZ4Compressor createHighCompressor() {
		return new ChannelLZ4Compressor(LZ4Factory.fastestInstance().highCompressor());
	}

	public static ChannelLZ4Compressor createHighCompressor(int compressionLevel) {
		return new ChannelLZ4Compressor(LZ4Factory.fastestInstance().highCompressor(compressionLevel));
	}

	public ChannelLZ4Compressor withInspector(Inspector inspector) {
		this.inspector = inspector;
		return this;
	}

	//check input for clarity
	@Override
	public ChannelInput getInput() {
		return input -> {
			this.input = sanitize(input);
			//noinspection ConstantConditions
			if (this.input != null && this.output != null) startProcess();
			return getProcessCompletion();
		};
	}

	@SuppressWarnings("ConstantConditions") //check output for clarity
	@Override
	public ChannelOutput getOutput() {
		return output -> {
			this.output = sanitize(output);
			if (this.input != null && this.output != null) startProcess();
		};
	}

	@Override
	protected void doProcess() {
		input.get()
				.whenResult(buf -> {
					if (buf != null) {
						ByteBuf outputBuf = compressBlock(compressor, checksum, buf.array(), buf.head(), buf.readRemaining());
						if (inspector != null) inspector.onBuf(buf, outputBuf);
						buf.recycle();
						output.accept(outputBuf)
								.whenResult($ -> doProcess());
					} else {
						output.accept(createEndOfStreamBlock(), null)
								.whenResult($ -> completeProcess());
					}
				});
	}

	@Override
	protected void doClose(Throwable e) {
		input.close(e);
		output.close(e);
	}

	// endregion

	private static int compressionLevel(int blockSize) {
		int compressionLevel = 32 - Integer.numberOfLeadingZeros(blockSize - 1); // ceil of log2
		checkArgument((1 << compressionLevel) >= blockSize);
		checkArgument(blockSize * 2 > (1 << compressionLevel));
		compressionLevel = max(0, compressionLevel - COMPRESSION_LEVEL_BASE);
		checkArgument(compressionLevel <= 0x0F);
		return compressionLevel;
	}

	private static void writeIntLE(int i, byte[] buf, int off) {
		buf[off++] = (byte) i;
		buf[off++] = (byte) (i >>> 8);
		buf[off++] = (byte) (i >>> 16);
		buf[off] = (byte) (i >>> 24);
	}

	private static ByteBuf compressBlock(LZ4Compressor compressor, StreamingXXHash32 checksum, byte[] bytes, int off, int len) {
		checkArgument(len != 0);

		int compressionLevel = compressionLevel(max(len, MIN_BLOCK_SIZE));

		int outputBufMaxSize = HEADER_LENGTH + ((compressor == null) ? len : compressor.maxCompressedLength(len));
		ByteBuf outputBuf = ByteBufPool.allocate(outputBufMaxSize);
		outputBuf.put(MAGIC);

		byte[] outputBytes = outputBuf.array();

		checksum.reset();
		checksum.update(bytes, off, len);
		int check = checksum.getValue();

		int compressedLength = len;
		if (compressor != null) {
			compressedLength = compressor.compress(bytes, off, len, outputBytes, HEADER_LENGTH);
		}

		int compressMethod;
		if (compressor == null || compressedLength >= len) {
			compressMethod = COMPRESSION_METHOD_RAW;
			compressedLength = len;
			System.arraycopy(bytes, off, outputBytes, HEADER_LENGTH, len);
		} else {
			compressMethod = COMPRESSION_METHOD_LZ4;
		}

		outputBytes[MAGIC_LENGTH] = (byte) (compressMethod | compressionLevel);
		writeIntLE(compressedLength, outputBytes, MAGIC_LENGTH + 1);
		writeIntLE(len, outputBytes, MAGIC_LENGTH + 5);
		writeIntLE(check, outputBytes, MAGIC_LENGTH + 9);
		checkArgument(MAGIC_LENGTH + 13 == HEADER_LENGTH);

		outputBuf.tail(HEADER_LENGTH + compressedLength);

		return outputBuf;
	}

	private static ByteBuf createEndOfStreamBlock() {
		int compressionLevel = compressionLevel(MIN_BLOCK_SIZE);

		ByteBuf outputBuf = ByteBufPool.allocate(HEADER_LENGTH);
		byte[] outputBytes = outputBuf.array();
		System.arraycopy(MAGIC, 0, outputBytes, 0, MAGIC_LENGTH);

		outputBytes[MAGIC_LENGTH] = (byte) (COMPRESSION_METHOD_RAW | compressionLevel);
		writeIntLE(0, outputBytes, MAGIC_LENGTH + 1);
		writeIntLE(0, outputBytes, MAGIC_LENGTH + 5);
		writeIntLE(0, outputBytes, MAGIC_LENGTH + 9);

		outputBuf.tail(HEADER_LENGTH);
		return outputBuf;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy