/** * 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.store; import com.codahale.metrics.Timer; import com.github.ambry.clustermap.ClusterMap; import com.github.ambry.commons.BlobId; import com.github.ambry.messageformat.BlobData; import com.github.ambry.messageformat.BlobProperties; import com.github.ambry.messageformat.MessageFormatErrorCodes; import com.github.ambry.messageformat.MessageFormatException; import com.github.ambry.messageformat.MessageFormatRecord; import com.github.ambry.utils.Utils; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.Channels; /** * Helper class to assist in dumping a single blob record from a log file */ class DumpDataHelper { /** * Fetches one blob record from the log * @param randomAccessFile {@link RandomAccessFile} referring to the log file * @param currentOffset the offset at which to read the record from * @param clusterMap the {@link ClusterMap} object to use to generate BlobId * @param currentTimeInMs current time in ms to determine expiration * @param metrics {@link StoreToolsMetrics} instance * @return the {@link LogBlobRecordInfo} containing the blob record info * @throws IOException * @throws MessageFormatException */ static LogBlobRecordInfo readSingleRecordFromLog(RandomAccessFile randomAccessFile, long currentOffset, ClusterMap clusterMap, long currentTimeInMs, StoreToolsMetrics metrics) throws IOException, MessageFormatException { String messageheader = null; BlobId blobId = null; String blobProperty = null; String usermetadata = null; String blobDataOutput = null; String deleteMsg = null; boolean isDeleted = false; boolean isExpired = false; long expiresAtMs = -1; int totalRecordSize = 0; final Timer.Context context = metrics.readSingleBlobRecordFromLogTimeMs.time(); try { randomAccessFile.seek(currentOffset); short version = randomAccessFile.readShort(); if (version == 1) { ByteBuffer buffer = ByteBuffer.allocate(MessageFormatRecord.MessageHeader_Format_V1.getHeaderSize()); buffer.putShort(version); randomAccessFile.read(buffer.array(), 2, buffer.capacity() - 2); buffer.clear(); MessageFormatRecord.MessageHeader_Format_V1 header = new MessageFormatRecord.MessageHeader_Format_V1(buffer); messageheader = " Header - version " + header.getVersion() + " messagesize " + header.getMessageSize() + " currentOffset " + currentOffset + " blobPropertiesRelativeOffset " + header.getBlobPropertiesRecordRelativeOffset() + " userMetadataRelativeOffset " + header.getUserMetadataRecordRelativeOffset() + " dataRelativeOffset " + header.getBlobRecordRelativeOffset() + " crc " + header.getCrc(); totalRecordSize += header.getMessageSize() + buffer.capacity(); // read blob id InputStream streamlog = Channels.newInputStream(randomAccessFile.getChannel()); blobId = new BlobId(new DataInputStream(streamlog), clusterMap); totalRecordSize += blobId.sizeInBytes(); if (header.getBlobPropertiesRecordRelativeOffset() != MessageFormatRecord.Message_Header_Invalid_Relative_Offset) { BlobProperties props = MessageFormatRecord.deserializeBlobProperties(streamlog); expiresAtMs = Utils.addSecondsToEpochTime(props.getCreationTimeInMs(), props.getTimeToLiveInSeconds()); isExpired = isExpired(expiresAtMs, currentTimeInMs); blobProperty = " Blob properties - blobSize " + props.getBlobSize() + " serviceId " + props.getServiceId() + ", isExpired " + isExpired; ByteBuffer metadata = MessageFormatRecord.deserializeUserMetadata(streamlog); usermetadata = " Metadata - size " + metadata.capacity(); BlobData blobData = MessageFormatRecord.deserializeBlob(streamlog); blobDataOutput = "Blob - size " + blobData.getSize(); } else { boolean deleteFlag = MessageFormatRecord.deserializeDeleteRecord(streamlog); isDeleted = true; deleteMsg = "delete change " + deleteFlag; } } else { throw new MessageFormatException("Header version not supported " + version, MessageFormatErrorCodes.IO_Error); } return new LogBlobRecordInfo(messageheader, blobId, blobProperty, usermetadata, blobDataOutput, deleteMsg, isDeleted, isExpired, expiresAtMs, totalRecordSize); } finally { context.stop(); } } /** * Holds information about a single blob record in the log */ static class LogBlobRecordInfo { final String messageHeader; final BlobId blobId; final String blobProperty; final String userMetadata; final String blobDataOutput; final String deleteMsg; final boolean isDeleted; final boolean isExpired; final long expiresAtMs; final int totalRecordSize; LogBlobRecordInfo(String messageHeader, BlobId blobId, String blobProperty, String userMetadata, String blobDataOutput, String deleteMsg, boolean isDeleted, boolean isExpired, long expiresAtMs, int totalRecordSize) { this.messageHeader = messageHeader; this.blobId = blobId; this.blobProperty = blobProperty; this.userMetadata = userMetadata; this.blobDataOutput = blobDataOutput; this.deleteMsg = deleteMsg; this.isDeleted = isDeleted; this.isExpired = isExpired; this.expiresAtMs = expiresAtMs; this.totalRecordSize = totalRecordSize; } } /** * Returns if the blob has been expired or not, based on {@code expiresAtMs}. * @param expiresAtMs time in milliseconds referring to the time at which the blob expires. * @param currentTimeInMs current time in ms to determine expiration * @return {@code true} if blob has expired, {@code false} otherwise */ static boolean isExpired(Long expiresAtMs, long currentTimeInMs) { return expiresAtMs != Utils.Infinite_Time && currentTimeInMs > expiresAtMs; } }