/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.mongodb.commands.signatures.diagnostic;
import com.eightkdata.mongowp.bson.BsonDocument;
import com.eightkdata.mongowp.bson.utils.DefaultBsonValues;
import com.eightkdata.mongowp.exceptions.BadValueException;
import com.eightkdata.mongowp.exceptions.NoSuchKeyException;
import com.eightkdata.mongowp.exceptions.TypesMismatchException;
import com.eightkdata.mongowp.fields.BooleanField;
import com.eightkdata.mongowp.fields.DocField;
import com.eightkdata.mongowp.fields.IntField;
import com.eightkdata.mongowp.fields.NumberField;
import com.eightkdata.mongowp.fields.StringField;
import com.eightkdata.mongowp.server.api.impl.AbstractNotAliasableCommand;
import com.eightkdata.mongowp.utils.BsonDocumentBuilder;
import com.eightkdata.mongowp.utils.BsonReaderTool;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.torodb.mongodb.commands.signatures.diagnostic.CollStatsCommand.CollStatsArgument;
import com.torodb.mongodb.commands.signatures.diagnostic.CollStatsCommand.CollStatsReply;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
*
*/
public class CollStatsCommand
extends AbstractNotAliasableCommand<CollStatsArgument, CollStatsReply> {
public static final CollStatsCommand INSTANCE = new CollStatsCommand();
private CollStatsCommand() {
super("collStats");
}
@Override
public boolean isSlaveOk() {
return true;
}
@Override
public Class<? extends CollStatsArgument> getArgClass() {
return CollStatsArgument.class;
}
@Override
public CollStatsArgument unmarshallArg(BsonDocument requestDoc)
throws TypesMismatchException, BadValueException, NoSuchKeyException {
return CollStatsArgument.unmarshall(requestDoc);
}
@Override
public BsonDocument marshallArg(CollStatsArgument request) {
throw new UnsupportedOperationException("Not supported yet."); //TODO
}
@Override
public Class<? extends CollStatsReply> getResultClass() {
return CollStatsReply.class;
}
@Override
public BsonDocument marshallResult(CollStatsReply reply) {
return reply.marshall();
}
@Override
public CollStatsReply unmarshallResult(BsonDocument resultDoc) throws
BadValueException, TypesMismatchException, NoSuchKeyException {
throw new UnsupportedOperationException("Not supported yet."); //TODO
}
@Immutable
public static class CollStatsArgument {
private static final StringField COLLECTION_FIELD = new StringField("collStats");
private static final StringField LOWERCASE_COLLECTION_FIELD = new StringField(COLLECTION_FIELD
.getFieldName().toLowerCase(Locale.ENGLISH));
private static final NumberField SCALE_FIELD = new NumberField("scale");
private static final BooleanField VERBOSE_FIELD = new BooleanField("verbose");
private final String collection;
private final int scale;
private final boolean verbose;
public CollStatsArgument(
@Nonnull String collection,
@Nonnegative int scale,
boolean verbose) {
this.collection = collection;
this.scale = scale;
this.verbose = verbose;
}
@Nonnegative
public int getScale() {
return scale;
}
public boolean isVerbose() {
return verbose;
}
protected static CollStatsArgument unmarshall(BsonDocument doc)
throws TypesMismatchException, BadValueException, NoSuchKeyException {
String collection = null;
try {
collection = BsonReaderTool.getString(doc, COLLECTION_FIELD);
} catch (NoSuchKeyException noSuchKeyException) {
try {
collection = BsonReaderTool.getString(doc, LOWERCASE_COLLECTION_FIELD);
} catch (NoSuchKeyException noSuchLowercaseKeyException) {
throw noSuchKeyException;
}
}
int scale;
try {
scale = BsonReaderTool.getNumeric(doc, SCALE_FIELD, DefaultBsonValues.INT32_ONE).intValue();
} catch (TypesMismatchException typesMismatchException) {
scale = 1;
}
if (scale <= 0) {
throw new BadValueException("Scale must be a value >= 1");
}
boolean verbose = BsonReaderTool.getBooleanOrNumeric(doc, VERBOSE_FIELD, false);
return new CollStatsArgument(collection, scale, verbose);
}
public String getCollection() {
return collection;
}
}
//TODO(gortiz): This reply is not prepared to respond on error cases!
public static class CollStatsReply {
private static final StringField NS_FIELD = new StringField("ns");
private static final NumberField<?> COUNT_FIELD = new NumberField<>("count");
private static final NumberField<?> SIZE_FIELD = new NumberField<>("size");
private static final NumberField<?> AVG_OBJ_SIZE_FIELD = new NumberField<>("avgObjSize");
private static final NumberField<?> STORAGE_SIZE_FIELD = new NumberField<>("storageSize");
private static final IntField N_INDEXES_FIELD = new IntField("nindexes");
private static final DocField INDEX_DETAILS_FIELD = new DocField("indexDetails");
@SuppressWarnings("checkstyle:LineLength")
private static final NumberField<?> TOTAL_INDEX_SIZE_FIELD = new NumberField<>("totalIndexSize");
private static final DocField INDEX_SIZES_FIELD = new DocField("indexSizes");
private static final BooleanField CAPPED_FIELD = new BooleanField("capped");
private static final NumberField<?> MAX_FIELD = new NumberField<>("max");
private final int scale;
@Nonnull
private final String database;
@Nonnull
private final String collection;
@Nonnull
private final Number count;
@Nonnull
private final Number size;
@Nonnull
private final Number storageSize;
@Nullable
private final BsonDocument customStorageStats;
private final boolean capped;
@Nullable
private final Number maxIfCapped;
@Nonnull
private final BsonDocument indexDetails;
@Nonnull
private final ImmutableMap<String, ? extends Number> sizeByIndex;
public CollStatsReply(
@Nonnegative int scale,
String database,
String collection,
Number count,
Number size,
Number storageSize,
@Nullable BsonDocument customStorageStats,
boolean capped,
Number maxIfCapped,
BsonDocument indexDetails,
ImmutableMap<String, ? extends Number> sizeByIndex) {
this.scale = scale;
this.database = database;
this.collection = collection;
this.count = count;
this.size = size;
this.storageSize = storageSize;
this.customStorageStats = customStorageStats;
this.capped = capped;
this.maxIfCapped = maxIfCapped;
this.indexDetails = indexDetails;
this.sizeByIndex = sizeByIndex;
}
private BsonDocument marshall() {
BsonDocumentBuilder builder = new BsonDocumentBuilder();
builder.append(NS_FIELD, database + '.' + collection);
builder.appendNumber(COUNT_FIELD, count);
builder.appendNumber(SIZE_FIELD, size);
if (count.longValue() != 0) {
Number avgObjSize = scale * size.longValue() / count.longValue();
builder.appendNumber(AVG_OBJ_SIZE_FIELD, avgObjSize);
}
builder.appendNumber(STORAGE_SIZE_FIELD, storageSize);
builder.append(N_INDEXES_FIELD, sizeByIndex.size());
builder.append(INDEX_DETAILS_FIELD, indexDetails);
builder.appendNumber(TOTAL_INDEX_SIZE_FIELD, getTotalIndexSize());
builder.append(INDEX_SIZES_FIELD, marshallSizeByIndex(sizeByIndex));
builder.append(CAPPED_FIELD, capped);
if (maxIfCapped != null) {
builder.appendNumber(MAX_FIELD, maxIfCapped);
}
return builder.build();
}
private Number getTotalIndexSize() {
long totalSize = 0;
for (Number indexSize : sizeByIndex.values()) {
totalSize += indexSize.longValue();
}
return totalSize;
}
private BsonDocument marshallSizeByIndex(ImmutableMap<String, ? extends Number> sizeByIndex) {
BsonDocumentBuilder builder = new BsonDocumentBuilder();
for (Entry<String, ? extends Number> entry : sizeByIndex.entrySet()) {
builder.appendNumber(new NumberField(entry.getKey()), entry.getValue());
}
return builder.build();
}
public static class Builder {
private int scale;
@Nonnull
private final String database;
@Nonnull
private final String collection;
private Number count;
private Number size;
private Number storageSize;
@Nullable
private BsonDocument customStorageStats;
private boolean capped;
@Nullable
private Number maxIfCapped;
private BsonDocument indexDetails;
private Map<String, ? extends Number> sizeByIndex;
public Builder(@Nonnull String database, @Nonnull String collection) {
this.database = database;
this.collection = collection;
}
public int getScale() {
return scale;
}
public Builder setScale(int scale) {
Preconditions.checkArgument(scale > 0, "Scale must be a positive number");
this.scale = scale;
return this;
}
public Number getCount() {
return count;
}
/**
*
* @param count The number of objects or documents in this collection.
* @return
*/
public Builder setCount(@Nonnull @Nonnegative Number count) {
this.count = count;
return this;
}
public Number getSize() {
return size;
}
/**
* The total size of all records in a collection. This value does not include the record
* header, which is 16 bytes per record, but does include the record’s padding. Additionally
* size does not include the size of any indexes associated with the collection.
* <p>
* The scale argument affects this value.
* <p>
* @param size
* @return
*/
public Builder setSize(@Nonnull @Nonnegative Number size) {
this.size = size;
return this;
}
public Number getStorageSize() {
return storageSize;
}
/**
* The total amount of storage allocated to this collection for document storage. The scale
* argument affects this value. The storageSize does not decrease as you remove or shrink
* documents.
* <p>
* @param storageSize
* @return
*/
public Builder setStorageSize(@Nonnull @Nonnegative Number storageSize) {
this.storageSize = storageSize;
return this;
}
public boolean isCapped() {
return capped;
}
/**
* This field will be “true” if the collection is capped.
* <p>
* @param capped
* @return
*/
public Builder setCapped(boolean capped) {
this.capped = capped;
return this;
}
public Number getMaxIfCapped() {
return maxIfCapped;
}
/**
* Shows the maximum number of documents that may be present in a capped collection.
* <p>
* @param maxIfCapped
* @return
*/
public Builder setMaxIfCapped(@Nullable @Nonnegative Number maxIfCapped) {
this.maxIfCapped = maxIfCapped;
return this;
}
public Map<String, ? extends Number> getSizeByIndex() {
return sizeByIndex;
}
/**
* This field specifies the key and size of every existing index on the collection. The scale
* argument affects this value.
* <p>
* @param sizeByIndex
* @return
*/
public Builder setSizeByIndex(@Nonnull Map<String, ? extends Number> sizeByIndex) {
this.sizeByIndex = sizeByIndex;
return this;
}
public BsonDocument getCustomStorageStats() {
return customStorageStats;
}
public Builder setCustomStorageStats(@Nullable BsonDocument customStorageStats) {
this.customStorageStats = customStorageStats;
return this;
}
public BsonDocument getIndexDetails() {
return indexDetails;
}
public Builder setIndexDetails(@Nonnull BsonDocument indexDetails) {
this.indexDetails = indexDetails;
return this;
}
public CollStatsReply build() {
assert scale > 0;
assert database != null;
assert collection != null;
assert count != null;
assert size != null;
assert storageSize != null;
assert indexDetails != null;
assert sizeByIndex != null;
return new CollStatsReply(
scale,
database,
collection,
count,
size,
storageSize,
customStorageStats,
capped,
maxIfCapped,
indexDetails,
ImmutableMap.copyOf(sizeByIndex)
);
}
}
}
}