/** * Copyright 2016 LinkedIn Corp. All rights reserved. * * 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. */ package com.github.ambry.messageformat; import com.github.ambry.network.Send; import com.github.ambry.store.MessageReadSet; import com.github.ambry.store.StoreKey; import com.github.ambry.store.StoreKeyFactory; import com.github.ambry.utils.ByteBufferOutputStream; import com.github.ambry.utils.SystemTime; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A send object for the message format to send data from the underlying store * to the network channel */ public class MessageFormatSend implements Send { private MessageReadSet readSet; private MessageFormatFlags flag; private ArrayList<SendInfo> infoList; private long totalSizeToWrite; private long sizeWritten; private int currentWriteIndex; private long sizeWrittenFromCurrentIndex; private StoreKeyFactory storeKeyFactory; private Logger logger = LoggerFactory.getLogger(getClass()); private class SendInfo { private long relativeOffset; private long sizeToSend; public SendInfo(long relativeOffset, long sizeToSend) { this.relativeOffset = relativeOffset; this.sizeToSend = sizeToSend; } public long relativeOffset() { return relativeOffset; } public long sizetoSend() { return sizeToSend; } } public MessageFormatSend(MessageReadSet readSet, MessageFormatFlags flag, MessageFormatMetrics metrics, StoreKeyFactory storeKeyFactory) throws IOException, MessageFormatException { this.readSet = readSet; this.flag = flag; this.storeKeyFactory = storeKeyFactory; totalSizeToWrite = 0; long startTime = SystemTime.getInstance().milliseconds(); calculateOffsets(); metrics.calculateOffsetMessageFormatSendTime.update(SystemTime.getInstance().milliseconds() - startTime); sizeWritten = 0; currentWriteIndex = 0; sizeWrittenFromCurrentIndex = 0; } // calculates the offsets from the MessageReadSet that needs to be sent over the network // based on the type of data requested as indicated by the flags private void calculateOffsets() throws IOException, MessageFormatException { try { // get size int messageCount = readSet.count(); // for each message, determine the offset and size that needs to be sent based on the flag infoList = new ArrayList<SendInfo>(messageCount); logger.trace("Calculate offsets of messages for one partition, MessageFormatFlag : {} number of messages : {}", flag, messageCount); for (int i = 0; i < messageCount; i++) { if (flag == MessageFormatFlags.All) { // just copy over the total size and use relative offset to be 0 // We do not have to check any version in this case as we dont // have to read any data to deserialize anything. infoList.add(i, new SendInfo(0, readSet.sizeInBytes(i))); totalSizeToWrite += readSet.sizeInBytes(i); } else { // read header version long startTime = SystemTime.getInstance().milliseconds(); ByteBuffer headerVersion = ByteBuffer.allocate(MessageFormatRecord.Version_Field_Size_In_Bytes); readSet.writeTo(i, Channels.newChannel(new ByteBufferOutputStream(headerVersion)), 0, MessageFormatRecord.Version_Field_Size_In_Bytes); logger.trace("Calculate offsets, read header version time: {}", SystemTime.getInstance().milliseconds() - startTime); headerVersion.flip(); short version = headerVersion.getShort(); switch (version) { case MessageFormatRecord.Message_Header_Version_V1: // read the header startTime = SystemTime.getInstance().milliseconds(); ByteBuffer header = ByteBuffer.allocate(MessageFormatRecord.MessageHeader_Format_V1.getHeaderSize()); headerVersion.clear(); header.putShort(headerVersion.getShort()); readSet.writeTo(i, Channels.newChannel(new ByteBufferOutputStream(header)), MessageFormatRecord.Version_Field_Size_In_Bytes, MessageFormatRecord.MessageHeader_Format_V1.getHeaderSize() - MessageFormatRecord.Version_Field_Size_In_Bytes); logger.trace("Calculate offsets, read header time: {}", SystemTime.getInstance().milliseconds() - startTime); startTime = SystemTime.getInstance().milliseconds(); header.flip(); MessageFormatRecord.MessageHeader_Format_V1 headerFormat = new MessageFormatRecord.MessageHeader_Format_V1(header); headerFormat.verifyHeader(); StoreKey storeKey = storeKeyFactory.getStoreKey( new DataInputStream(new MessageReadSetIndexInputStream(readSet, i, header.capacity()))); if (storeKey.compareTo(readSet.getKeyAt(i)) != 0) { throw new MessageFormatException( "Id mismatch between metadata and store - metadataId " + readSet.getKeyAt(i) + " storeId " + storeKey, MessageFormatErrorCodes.Store_Key_Id_MisMatch); } logger.trace("Calculate offsets, verify header time: {}", SystemTime.getInstance().milliseconds() - startTime); startTime = SystemTime.getInstance().milliseconds(); if (flag == MessageFormatFlags.BlobProperties) { int blobPropertiesRecordSize = headerFormat.getUserMetadataRecordRelativeOffset() - headerFormat.getBlobPropertiesRecordRelativeOffset(); infoList.add(i, new SendInfo(headerFormat.getBlobPropertiesRecordRelativeOffset(), blobPropertiesRecordSize)); totalSizeToWrite += blobPropertiesRecordSize; logger.trace("Calculate offsets, get total size of blob properties time: {}", SystemTime.getInstance().milliseconds() - startTime); logger.trace("Sending blob properties for message relativeOffset : {} size : {}", infoList.get(i).relativeOffset(), infoList.get(i).sizetoSend()); } else if (flag == MessageFormatFlags.BlobUserMetadata) { int userMetadataRecordSize = headerFormat.getBlobRecordRelativeOffset() - headerFormat.getUserMetadataRecordRelativeOffset(); infoList.add(i, new SendInfo(headerFormat.getUserMetadataRecordRelativeOffset(), userMetadataRecordSize)); totalSizeToWrite += userMetadataRecordSize; logger.trace("Calculate offsets, get total size of user metadata time: {}", SystemTime.getInstance().milliseconds() - startTime); logger.trace("Sending user metadata for message relativeOffset : {} size : {}", infoList.get(i).relativeOffset(), infoList.get(i).sizetoSend()); } else if (flag == MessageFormatFlags.BlobInfo) { int blobPropertiesRecordPlusUserMetadataRecordSize = headerFormat.getBlobRecordRelativeOffset() - headerFormat.getBlobPropertiesRecordRelativeOffset(); infoList.add(i, new SendInfo(headerFormat.getBlobPropertiesRecordRelativeOffset(), blobPropertiesRecordPlusUserMetadataRecordSize)); totalSizeToWrite += blobPropertiesRecordPlusUserMetadataRecordSize; logger.trace("Calculate offsets, get total size of blob info time: {}", SystemTime.getInstance().milliseconds() - startTime); logger.trace("Sending blob info (blob properties + user metadata) for message relativeOffset : {} " + "size : {}", infoList.get(i).relativeOffset(), infoList.get(i).sizetoSend()); } else if (flag == MessageFormatFlags.Blob) { long blobRecordSize = headerFormat.getMessageSize() - (headerFormat.getBlobRecordRelativeOffset() - headerFormat.getBlobPropertiesRecordRelativeOffset()); infoList.add(i, new SendInfo(headerFormat.getBlobRecordRelativeOffset(), blobRecordSize)); totalSizeToWrite += blobRecordSize; logger.trace("Calculate offsets, get total size of blob time: {}", SystemTime.getInstance().milliseconds() - startTime); logger.trace("Sending data for message relativeOffset : {} size : {}", infoList.get(i).relativeOffset(), infoList.get(i).sizetoSend()); } else { //just return the header int messageHeaderSize = MessageFormatRecord.MessageHeader_Format_V1.getHeaderSize() + MessageFormatRecord.Version_Field_Size_In_Bytes; infoList.add(i, new SendInfo(0, messageHeaderSize)); totalSizeToWrite += messageHeaderSize; logger.trace("Calculate offsets, get total size of header time: {}", SystemTime.getInstance().milliseconds() - startTime); logger.trace("Sending message header relativeOffset : {} size : {}", infoList.get(i).relativeOffset(), infoList.get(i).sizetoSend()); } break; default: String message = "Version not known while reading message - version " + version + ", StoreKey " + readSet.getKeyAt(i); throw new MessageFormatException(message, MessageFormatErrorCodes.Unknown_Format_Version); } } } } catch (IOException e) { logger.trace("IOError when calculating offsets"); throw new MessageFormatException("IOError when calculating offsets ", e, MessageFormatErrorCodes.IO_Error); } } @Override public long writeTo(WritableByteChannel channel) throws IOException { long written = 0; if (!isSendComplete()) { written = readSet.writeTo(currentWriteIndex, channel, infoList.get(currentWriteIndex).relativeOffset() + sizeWrittenFromCurrentIndex, infoList.get(currentWriteIndex).sizetoSend() - sizeWrittenFromCurrentIndex); logger.trace("writeindex {} relativeOffset {} maxSize {} written {}", currentWriteIndex, infoList.get(currentWriteIndex).relativeOffset() + sizeWrittenFromCurrentIndex, infoList.get(currentWriteIndex).sizetoSend() - sizeWrittenFromCurrentIndex, written); sizeWritten += written; sizeWrittenFromCurrentIndex += written; logger.trace("size written in this loop : {} size written till now : {}", written, sizeWritten); if (sizeWrittenFromCurrentIndex == infoList.get(currentWriteIndex).sizetoSend()) { currentWriteIndex++; sizeWrittenFromCurrentIndex = 0; } } return written; } @Override public boolean isSendComplete() { return totalSizeToWrite == sizeWritten; } @Override public long sizeInBytes() { return totalSizeToWrite; } } /** * A stream representation of the message read set. This helps to * read the read set as a sequential stream even though the underlying * representation can be random */ class MessageReadSetIndexInputStream extends InputStream { private final MessageReadSet messageReadSet; private final int indexToRead; private int currentOffset; public MessageReadSetIndexInputStream(MessageReadSet messageReadSet, int indexToRead, int startingOffset) { this.messageReadSet = messageReadSet; if (indexToRead >= messageReadSet.count()) { throw new IllegalArgumentException( "The index provided " + indexToRead + " " + "is outside the bounds of the number of messages in the read set " + messageReadSet.count()); } this.indexToRead = indexToRead; this.currentOffset = startingOffset; } @Override public int read() throws IOException { if (currentOffset == messageReadSet.sizeInBytes(indexToRead)) { throw new IOException("Reached end of stream of message read set"); } ByteBuffer buf = ByteBuffer.allocate(1); ByteBufferOutputStream bufferStream = new ByteBufferOutputStream(buf); long bytesRead = messageReadSet.writeTo(indexToRead, Channels.newChannel(bufferStream), currentOffset, 1); if (bytesRead != 1) { throw new IllegalStateException("Number of bytes read for read from messageReadSet should be 1"); } currentOffset++; buf.flip(); return buf.get() & 0xFF; } @Override public int read(byte b[], int off, int len) throws IOException { if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } if (currentOffset == messageReadSet.sizeInBytes(indexToRead)) { throw new IOException("Reached end of stream of message read set"); } ByteBuffer buf = ByteBuffer.wrap(b); ByteBufferOutputStream bufferStream = new ByteBufferOutputStream(buf); long sizeToRead = Math.min(len - off, messageReadSet.sizeInBytes(indexToRead) - currentOffset); long bytesWritten = messageReadSet.writeTo(indexToRead, Channels.newChannel(bufferStream), currentOffset, sizeToRead); currentOffset += bytesWritten; return (int) bytesWritten; } }