/**
* Copyright 2011-2012 Akiban Technologies, Inc.
*
* 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 com.persistit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import com.persistit.util.Util;
/**
* <p>
* Collection of <code>AtomicLong</code> counters representing interesting
* statistics in a {@link Tree}. The {@link #store} and {@link #load} methods
* are designed to serialize and deserialize evolving versions of this class,
* therefore it is possible to add and remove new counters while remaining
* compatible with prior versions.
* </p>
* <p>
* Currently implemented counters include:
* <ul>
* <li>Fetch</li>
* <li>Traverse</li>
* <li>Store</li>
* <li>Remove</li>
* </ul>
* </p>
*
* @author peter
*/
public class TreeStatistics {
final static int MAX_SERIALIZED_SIZE = 54;
private final AtomicLong _fetchCounter = new AtomicLong();
private final AtomicLong _traverseCounter = new AtomicLong();
private final AtomicLong _storeCounter = new AtomicLong();
private final AtomicLong _removeCounter = new AtomicLong();
private final AtomicBoolean _dirty = new AtomicBoolean();
/*
* Array of AtomicLong instances currently used in serializing and
* deserializing statistics. To ensure correct evolution of serialized
* values, (a) the length of this array is limited to 32, and (b) if a field
* becomes obsolete, replace it in this array by null rather than reusing
* its position.
*/
private final AtomicLong[] _statsArray = new AtomicLong[] { _fetchCounter, _traverseCounter, _storeCounter,
_removeCounter };
private final static String[] _statsArrayNames = new String[] { "fetchCounter", "traverseCounter", "storeCounter",
"removeCounter" };
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (int index = 0; index < _statsArrayNames.length; index++) {
final String name = _statsArrayNames[index];
final AtomicLong value = _statsArray[index];
if (name != null) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(name);
sb.append("=");
sb.append(value == null ? "" : String.format("%,d", value.get()));
}
}
return sb.toString();
}
/**
* @return the count of {@link Exchange#fetch} operations, including
* {@link Exchange#fetchAndStore} and
* {@link Exchange#fetchAndRemove} operations
*/
public long getFetchCounter() {
return _fetchCounter.get();
}
/**
* @return the count of {@link Exchange#traverse} operations including
* {@link Exchange#next} and {@link Exchange#previous}
*/
public long getTraverseCounter() {
return _traverseCounter.get();
}
/**
* @return the count of {@link Exchange#store} operations, including
* {@link Exchange#fetchAndStore}
*/
public long getStoreCounter() {
return _storeCounter.get();
}
/**
* @return the count of {@link Exchange#remove} operations, including
* {@link Exchange#fetchAndRemove} operations
*/
public long getRemoveCounter() {
return _removeCounter.get();
}
boolean isDirty() {
return _dirty.get();
}
void setDirty(final boolean dirty) {
_dirty.set(dirty);
}
/**
* @return Approximate size in bytes of the overhead space consumed in this
* <code>Tree</code> by multi-version-values
*/
void reset() {
_fetchCounter.set(0);
_traverseCounter.set(0);
_storeCounter.set(0);
_removeCounter.set(0);
setDirty(true);
}
void bumpFetchCounter() {
_fetchCounter.incrementAndGet();
setDirty(true);
}
void bumpTraverseCounter() {
_traverseCounter.incrementAndGet();
setDirty(true);
}
void bumpStoreCounter() {
_storeCounter.incrementAndGet();
setDirty(true);
}
void bumpRemoveCounter() {
_removeCounter.incrementAndGet();
setDirty(true);
}
/**
* <p>
* Serialize the statistics value in a variable-length byte array. The
* format is designed to allow more fields to be added and existing fields
* to be removed. Up to 64 field could eventually be allocated. The first
* eight bytes of the serialized form include a bit map; a bit set in the
* bit map indicates that the corresponding field is present in the
* remaining bytes of the serialized form. Each field is stored as an 8-byte
* long value.
* </p>
*
* @param bytes
* byte array into which statistics are serialized
* @param index
* at which serialization starts in byte array
* @return length of serialized statistics
*/
int store(final byte[] bytes, final int index) {
long bits = 0;
int offset = index + 8;
int field = 0;
for (final AtomicLong a : _statsArray) {
if (a != null) {
final long v = a.get();
bits |= 1 << field;
Util.putLong(bytes, offset, v);
offset += 8;
}
field++;
}
Util.putLong(bytes, index, bits);
return offset - index;
}
/**
* Deserialize stored statistics values from the supplied byte array
* starting at <code>index</code>. The first eight bytes contain a bit map
* described in {@link #save(byte[], int)}.
*
* @param bytes
* serialized statistics
* @param index
* at which serialized statistics start in the byte array
*/
int load(final byte[] bytes, final int index, final int length) {
int offset = index + 8;
final int end = index + length;
final long bits = Util.getLong(bytes, index);
for (int field = 0; field < 64; field++) {
final AtomicLong a = field < _statsArray.length ? _statsArray[field] : null;
if ((bits & (1 << field)) != 0) {
if (a != null) {
checkEnd(offset + 8, end);
a.set(Util.getLong(bytes, offset));
}
offset += 8;
}
}
return length;
}
private void checkEnd(final int index, final int end) {
if (index > end) {
throw new IllegalStateException("TreeStatistics record is too short at offset " + index);
}
}
}