blob: 8f254303b8bd474f8ee47b080c5c3dea4c5c972c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.jackrabbit.oak.plugins.blob;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;
import com.google.common.base.Strings;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.jackrabbit.core.data.DataIdentifier;
import org.apache.jackrabbit.oak.api.Blob;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
import org.apache.jackrabbit.oak.api.jmx.ConsolidatedDataStoreCacheStatsMBean;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
import org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreBlobStore;
import org.apache.jackrabbit.oak.plugins.blob.datastore.InMemoryDataRecord;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.NodeStore;
import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
import org.osgi.framework.BundleContext;
import static com.google.common.collect.Lists.newArrayList;
import static org.apache.jackrabbit.oak.commons.IOUtils.humanReadableByteCount;
import static org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean;
/**
* Stats for caching data store.
*/
@Component
public class ConsolidatedDataStoreCacheStats implements ConsolidatedDataStoreCacheStatsMBean {
private final List<Registration> registrations = newArrayList();
private final List<DataStoreCacheStatsMBean> cacheStats = newArrayList();
@Reference public AbstractSharedCachingDataStore cachingDataStore;
@Reference public NodeStore nodeStore;
@Override
public TabularData getCacheStats() {
TabularDataSupport tds;
try {
TabularType tt = new TabularType(CacheStatsData.class.getName(),
"Consolidated DataStore Cache Stats", CacheStatsData.TYPE, new String[]{"name"});
tds = new TabularDataSupport(tt);
for(DataStoreCacheStatsMBean stats : cacheStats){
tds.put(new CacheStatsData(stats).toCompositeData());
}
} catch (OpenDataException e) {
throw new IllegalStateException(e);
}
return tds;
}
@Activate
private void activate(BundleContext context){
Whiteboard wb = new OsgiWhiteboard(context);
List<DataStoreCacheStatsMBean> allStats = cachingDataStore.getStats();
for (DataStoreCacheStatsMBean stat : allStats) {
registrations.add(
registerMBean(wb, CacheStatsMBean.class, stat, CacheStatsMBean.TYPE,
stat.getName()));
cacheStats.add(stat);
}
registrations.add(
registerMBean(wb, ConsolidatedDataStoreCacheStatsMBean.class, this,
ConsolidatedDataStoreCacheStatsMBean.TYPE,
"Consolidated DataStore Cache statistics"));
}
@Deactivate
private void deactivate(){
for (Registration r : registrations) {
r.unregister();
}
registrations.clear();
}
/**
* Determines whether a file-like entity with the given name
* has been "synced" (completely copied) to S3.
*
* Determination of "synced":
* - A nodeName of null or "" is always "not synced".
* - A nodeName that does not map to a valid node is always "not synced".
* - If the node for this nodeName does not have a binary property,
* this node is always "not synced" since such a node would never be
* copied to S3.
* - If the node for this nodeName is not in the nodeStore, this node is
* always "not synced".
* - Otherwise, the state is "synced" if the corresponding blob is
* completely stored in S3.
*
* @param nodePathName - Path to the entity to check. This is
* a node path, not an external file path.
* @return true if the file is synced to S3.
*/
@Override
public boolean isFileSynced(final String nodePathName) {
if (Strings.isNullOrEmpty(nodePathName)) {
return false;
}
if (null == nodeStore) {
return false;
}
final NodeState leafNode = findLeafNode(nodePathName);
if (!leafNode.exists()) {
return false;
}
boolean nodeHasBinaryProperties = false;
for (final PropertyState propertyState : leafNode.getProperties()) {
nodeHasBinaryProperties |= (propertyState.getType() == Type.BINARY || propertyState.getType() == Type.BINARIES);
try {
if (propertyState.getType() == Type.BINARY) {
final Blob blob = (Blob) propertyState.getValue(propertyState.getType());
if (null == blob || !haveRecordForBlob(blob)) {
return false;
}
} else if (propertyState.getType() == Type.BINARIES) {
final List<Blob> blobs = (List<Blob>) propertyState.getValue(propertyState.getType());
if (null == blobs) {
return false;
}
for (final Blob blob : blobs) {
if (!haveRecordForBlob(blob)) {
return false;
}
}
}
}
catch (ClassCastException e) {
return false;
}
}
// If we got here and nodeHasBinaryProperties is true,
// it means at least one binary property was found for
// the leaf node and that we were able to locate a
// records for binaries found.
return nodeHasBinaryProperties;
}
private NodeState findLeafNode(final String nodePathName) {
final Iterable<String> pathNodes = PathUtils.elements(PathUtils.getParentPath(nodePathName));
final String leafNodeName = PathUtils.getName(nodePathName);
NodeState currentNode = nodeStore.getRoot();
for (String pathNodeName : pathNodes) {
if (pathNodeName.length() > 0) {
NodeState childNode = currentNode.getChildNode(pathNodeName);
if (!childNode.exists()) {
break;
}
currentNode = childNode;
}
}
return currentNode.getChildNode(leafNodeName);
}
private boolean haveRecordForBlob(final Blob blob) {
final String fullBlobId = blob.getContentIdentity();
if (!Strings.isNullOrEmpty(fullBlobId)
&& !InMemoryDataRecord.isInstance(fullBlobId)) {
String blobId = DataStoreBlobStore.BlobId.of(fullBlobId).getBlobId();
return cachingDataStore.exists(new DataIdentifier(blobId));
}
return false;
}
private static class CacheStatsData {
static final String[] FIELD_NAMES = new String[]{
"name",
"requestCount",
"hitCount",
"hitRate",
"missCount",
"missRate",
"loadCount",
"loadSuccessCount",
"loadExceptionCount",
"totalLoadTime",
"averageLoadPenalty",
"evictionCount",
"elementCount",
"totalWeight",
"totalMemWeight",
"maxWeight",
};
static final String[] FIELD_DESCRIPTIONS = FIELD_NAMES;
@SuppressWarnings("rawtypes")
static final OpenType[] FIELD_TYPES = new OpenType[]{
SimpleType.STRING,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.BIGDECIMAL,
SimpleType.LONG,
SimpleType.BIGDECIMAL,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.STRING,
SimpleType.STRING,
SimpleType.LONG,
SimpleType.LONG,
SimpleType.STRING,
SimpleType.STRING,
SimpleType.STRING,
};
static final CompositeType TYPE = createCompositeType();
static CompositeType createCompositeType() {
try {
return new CompositeType(
CacheStatsData.class.getName(),
"Composite data type for Cache statistics",
CacheStatsData.FIELD_NAMES,
CacheStatsData.FIELD_DESCRIPTIONS,
CacheStatsData.FIELD_TYPES);
} catch (OpenDataException e) {
throw new IllegalStateException(e);
}
}
private final DataStoreCacheStatsMBean stats;
public CacheStatsData(DataStoreCacheStatsMBean stats){
this.stats = stats;
}
CompositeDataSupport toCompositeData() {
Object[] values = new Object[]{
stats.getName(),
stats.getRequestCount(),
stats.getHitCount(),
new BigDecimal(stats.getHitRate(),new MathContext(2)),
stats.getMissCount(),
new BigDecimal(stats.getMissRate(), new MathContext(2)),
stats.getLoadCount(),
stats.getLoadSuccessCount(),
stats.getLoadExceptionCount(),
timeInWords(stats.getTotalLoadTime()),
TimeUnit.NANOSECONDS.toMillis((long) stats.getAverageLoadPenalty()) + "ms",
stats.getEvictionCount(),
stats.getElementCount(),
humanReadableByteCount(stats.estimateCurrentWeight()),
humanReadableByteCount(stats.estimateCurrentMemoryWeight()),
humanReadableByteCount(stats.getMaxTotalWeight()),
};
try {
return new CompositeDataSupport(TYPE, FIELD_NAMES, values);
} catch (OpenDataException e) {
throw new IllegalStateException(e);
}
}
}
static String timeInWords(long nanos) {
long millis = TimeUnit.NANOSECONDS.toMillis(nanos);
return String.format("%d min, %d sec",
TimeUnit.MILLISECONDS.toMinutes(millis),
TimeUnit.MILLISECONDS.toSeconds(millis) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
);
}
}