/*
* Copyright 2017 Real Logic Ltd.
*
* 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.aeron.driver.reports;
import org.agrona.BitUtil;
import org.agrona.concurrent.AtomicBuffer;
import static org.agrona.BitUtil.CACHE_LINE_LENGTH;
import static org.agrona.BitUtil.SIZE_OF_INT;
import static org.agrona.BitUtil.SIZE_OF_LONG;
/**
* A report of loss events on a message stream.
*
* The provided {@link AtomicBuffer} can wrap a memory-mapped file so logging can be out of process. This provides
* the benefit that if a crash or lockup occurs then the log can be read externally without loss of data.
*
* <b>Note:</b>This class is NOT threadsafe to be used from multiple logging threads.
*
* The error records are recorded to the memory mapped buffer in the following format.
*
* <pre>
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |R| Observation Count |
* | |
* +-+-------------------------------------------------------------+
* |R| Total Bytes Lost |
* | |
* +---------------------------------------------------------------+
* | First Observation Timestamp |
* | |
* +---------------------------------------------------------------+
* | Last Observation Timestamp |
* | |
* +---------------------------------------------------------------+
* | Session ID |
* +---------------------------------------------------------------+
* | Stream ID |
* +---------------------------------------------------------------+
* | Channel encoded in US-ASCII ...
* ... |
* +---------------------------------------------------------------+
* | Source encoded in US-ASCII ...
* ... |
* +---------------------------------------------------------------+
* </pre>
*/
public class LossReport
{
/**
* Alignment to be applied for each entry offset.
*/
public static final int ENTRY_ALIGNMENT = CACHE_LINE_LENGTH;
/**
* Offset within an entry at which the observation count begins.
*/
public static final int OBSERVATION_COUNT_OFFSET = 0;
/**
* Offset within an entry at which the total bytes field begins.
*/
public static final int TOTAL_BYTES_LOST_OFFSET = OBSERVATION_COUNT_OFFSET + SIZE_OF_LONG;
/**
* Offset within an entry at which the first observation field begins.
*/
public static final int FIRST_OBSERVATION_OFFSET = TOTAL_BYTES_LOST_OFFSET + SIZE_OF_LONG;
/**
* Offset within an entry at which the last observation field begins.
*/
public static final int LAST_OBSERVATION_OFFSET = FIRST_OBSERVATION_OFFSET + SIZE_OF_LONG;
/**
* Offset within an entry at which the session id field begins.
*/
public static final int SESSION_ID_OFFSET = LAST_OBSERVATION_OFFSET + SIZE_OF_LONG;
/**
* Offset within an entry at which the stream id field begins.
*/
public static final int STREAM_ID_OFFSET = SESSION_ID_OFFSET + SIZE_OF_INT;
/**
* Offset within an entry at which the channel field begins.
*/
public static final int CHANNEL_OFFSET = STREAM_ID_OFFSET + SIZE_OF_INT;
private int nextRecordOffset = 0;
private final AtomicBuffer buffer;
/**
* Create a loss report which wraps a buffer which is ideally memory mapped so it can
* be read from another process.
*
* @param buffer to be wrapped.
*/
public LossReport(final AtomicBuffer buffer)
{
buffer.verifyAlignment();
this.buffer = buffer;
}
/**
* Create a new entry for recording loss on a given stream.
*
* If not space is remaining in the error report then null is returned.
*
* @param initialBytesLost on the stream.
* @param timestampMs at which the first loss was observed.
* @param sessionId for the stream.
* @param streamId for the stream.
* @param channel for the stream.
* @param source of the stream.
* @return a new record or null if the error log has insufficient space.
*/
public ReportEntry createEntry(
final long initialBytesLost,
final long timestampMs,
final int sessionId,
final int streamId,
final String channel,
final String source)
{
ReportEntry reportEntry = null;
final int requiredCapacity = CHANNEL_OFFSET + (SIZE_OF_INT * 2) + channel.length() + source.length();
if (requiredCapacity <= (buffer.capacity() - nextRecordOffset))
{
final int offset = nextRecordOffset;
buffer.putLong(offset + TOTAL_BYTES_LOST_OFFSET, initialBytesLost);
buffer.putLong(offset + FIRST_OBSERVATION_OFFSET, timestampMs);
buffer.putLong(offset + LAST_OBSERVATION_OFFSET, timestampMs);
buffer.putInt(offset + SESSION_ID_OFFSET, sessionId);
buffer.putInt(offset + STREAM_ID_OFFSET, streamId);
final int encodedChannelLength = buffer.putStringAscii(offset + CHANNEL_OFFSET, channel);
buffer.putStringAscii(offset + CHANNEL_OFFSET + encodedChannelLength, source);
buffer.putLongOrdered(offset + OBSERVATION_COUNT_OFFSET, 1);
reportEntry = new ReportEntry(buffer, offset);
nextRecordOffset += BitUtil.align(requiredCapacity, ENTRY_ALIGNMENT);
}
return reportEntry;
}
/**
* Report entry for a specific stream. Once an entry has been created it can then be used repeatably
* to capture the aggregate loss on a stream.
*/
public static class ReportEntry
{
private final AtomicBuffer buffer;
private final int offset;
ReportEntry(final AtomicBuffer buffer, final int offset)
{
this.buffer = buffer;
this.offset = offset;
}
/**
* Record a loss observation for a particular stream.
*
* @param bytesLost in this observation.
* @param timestampMs when this observation occurred.
*/
public void recordObservation(final long bytesLost, final long timestampMs)
{
buffer.putLong(offset + LAST_OBSERVATION_OFFSET, timestampMs);
buffer.getAndAddLong(offset + TOTAL_BYTES_LOST_OFFSET, bytesLost);
buffer.getAndAddLong(offset + OBSERVATION_COUNT_OFFSET, 1);
}
}
}