blob: d2a984a274faf31af9a1f281946e313344767bf6 [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.index;
import java.util.Calendar;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
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.Stopwatch;
import com.google.common.base.Throwables;
import com.google.common.base.Ticker;
import com.google.common.collect.Maps;
import org.apache.jackrabbit.oak.stats.Clock;
import org.apache.jackrabbit.oak.stats.MeterStats;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
public class TrackingCorruptIndexHandler implements CorruptIndexHandler {
static final String CORRUPT_INDEX_METER_NAME = "corrupt-index";
private final Logger log = LoggerFactory.getLogger(getClass());
private Clock clock = Clock.SIMPLE;
private long errorWarnIntervalMillis = TimeUnit.MINUTES.toMillis(15);
private long indexerCycleCount;
private long corruptIntervalMillis = TimeUnit.MINUTES.toMillis(30);
private final Map<String, CorruptIndexInfo> indexes = Maps.newConcurrentMap();
private MeterStats meter;
void setMeterStats(MeterStats meter) {
this.meter = meter;
}
public Map<String, CorruptIndexInfo> getCorruptIndexData(String asyncName){
if (corruptIntervalMillis <= 0){
return Collections.emptyMap();
}
Map<String, CorruptIndexInfo> result = Maps.newHashMap();
for (CorruptIndexInfo info : indexes.values()){
if (asyncName.equals(info.asyncName) && info.isFailingSinceLongTime()){
result.put(info.path, info);
}
}
return result;
}
public Map<String, CorruptIndexInfo> getFailingIndexData(String asyncName){
Map<String, CorruptIndexInfo> result = Maps.newHashMap();
for (CorruptIndexInfo info : indexes.values()){
if (asyncName.equals(info.asyncName)){
result.put(info.path, info);
}
}
return result;
}
public void markWorkingIndexes(Set<String> updatedIndexPaths) {
indexerCycleCount++;
for (String indexPath : updatedIndexPaths){
CorruptIndexInfo info = indexes.remove(indexPath);
if (info != null){
log.info("Index at [{}] which was so far failing {} is now working again.", info.path, info.getStats());
}
}
if (meter != null) {
meter.mark(indexes.size());
}
}
public boolean isFailing(String asyncName) {
return !getFailingIndexData(asyncName).isEmpty();
}
//~--------------------------------< CorruptIndexHandler >
@Override
public boolean skippingCorruptIndex(String async, String indexPath, Calendar corruptSince) {
CorruptIndexInfo info = getOrCreateInfo(async, indexPath);
if (info.skippedIndexing(checkNotNull(corruptSince))) {
log.warn("Ignoring corrupt index [{}] which has been marked as corrupt [{}]. This index " +
"MUST be reindexed for indexing to work properly", indexPath,
info.getStats());
return true;
}
return false;
}
@Override
public void indexUpdateFailed(String async, String indexPath, Exception e) {
if (meter != null) {
meter.mark();
}
getOrCreateInfo(async, indexPath).addFailure(e);
}
//~---------------------------------< Setters >
public void setCorruptInterval(long interval, TimeUnit unit) {
this.corruptIntervalMillis = unit.toMillis(interval);
}
public void setErrorWarnInterval(long errorWarnInterval, TimeUnit unit) {
this.errorWarnIntervalMillis = unit.toMillis(errorWarnInterval);
}
void setClock(Clock clock) {
this.clock = clock;
}
long getCorruptIntervalMillis() {
return corruptIntervalMillis;
}
long getErrorWarnIntervalMillis() {
return errorWarnIntervalMillis;
}
private long getTime(){
return clock.getTime();
}
private synchronized CorruptIndexInfo getOrCreateInfo(String asyncName, String indexPath) {
CorruptIndexInfo info = indexes.get(indexPath);
if (info == null){
info = new CorruptIndexInfo(asyncName, indexPath);
indexes.put(indexPath, info);
}
return info;
}
public class CorruptIndexInfo {
private final String asyncName;
private final String path;
private final long lastIndexerCycleCount = indexerCycleCount;
private final Stopwatch watch = Stopwatch.createStarted(new ClockTicker(clock));
private String exception = "";
private int failureCount;
private int skippedCount;
private long corruptSince;
CorruptIndexInfo(String asyncName, String path) {
this.asyncName = asyncName;
this.path = path;
this.corruptSince = getTime();
}
void addFailure(Exception e){
exception = Throwables.getStackTraceAsString(e);
failureCount++;
}
boolean skippedIndexing(Calendar corruptSince){
skippedCount++;
this.corruptSince = corruptSince.getTimeInMillis();
if (watch.elapsed(TimeUnit.MILLISECONDS) > errorWarnIntervalMillis) {
watch.reset().start();
return true;
}
return false;
}
public boolean isFailingSinceLongTime() {
return getTime() - corruptSince > corruptIntervalMillis;
}
public String getStats() {
return String.format("since %tc ,%d indexing cycles, failed %d times, skipped %d time",
corruptSince, getCycleCount(), failureCount, skippedCount);
}
public Calendar getCorruptSinceAsCal() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(corruptSince);
return cal;
}
public String getLastException() {
return exception;
}
public boolean isMarkedAsCorrupt(){
return skippedCount > 0;
}
public int getSkippedCount() {
return skippedCount;
}
public String getPath() {
return path;
}
private long getCycleCount() {
return indexerCycleCount - lastIndexerCycleCount;
}
}
//~-----------------------------------------------------< MBean Support >
public TabularData getFailingIndexStats(String asyncName) {
TabularDataSupport tds;
try {
TabularType tt = new TabularType(TrackingCorruptIndexHandler.class.getName(),
"Failing Index Stats", FailingIndexStats.TYPE, new String[]{"path"});
tds = new TabularDataSupport(tt);
Map<String, CorruptIndexInfo> infos = getFailingIndexData(asyncName);
for (Map.Entry<String, CorruptIndexInfo> e : infos.entrySet()) {
FailingIndexStats stats = new FailingIndexStats(e.getValue());
tds.put(stats.toCompositeData());
}
} catch (OpenDataException e) {
throw new IllegalStateException(e);
}
return tds;
}
private static class FailingIndexStats {
static final String[] FIELD_NAMES = new String[]{
"path",
"stats",
"markedCorrupt",
"failingSince",
"exception"
};
static final String[] FIELD_DESCRIPTIONS = new String[]{
"Path",
"Failure stats",
"Marked as corrupt",
"Failure start time",
"Exception"
};
@SuppressWarnings("rawtypes")
static final OpenType[] FIELD_TYPES = new OpenType[]{
SimpleType.STRING,
SimpleType.STRING,
SimpleType.BOOLEAN,
SimpleType.STRING,
SimpleType.STRING,
};
static final CompositeType TYPE = createCompositeType();
static CompositeType createCompositeType() {
try {
return new CompositeType(
FailingIndexStats.class.getName(),
"Composite data type for Failing Index statistics",
FailingIndexStats.FIELD_NAMES,
FailingIndexStats.FIELD_DESCRIPTIONS,
FailingIndexStats.FIELD_TYPES);
} catch (OpenDataException e) {
throw new IllegalStateException(e);
}
}
private final CorruptIndexInfo info;
public FailingIndexStats(CorruptIndexInfo info){
this.info = info;
}
CompositeDataSupport toCompositeData() {
Object[] values = new Object[]{
info.path,
info.getStats(),
info.isMarkedAsCorrupt(),
String.format("%tc", info.getCorruptSinceAsCal().getTimeInMillis()),
info.getLastException(),
};
try {
return new CompositeDataSupport(TYPE, FIELD_NAMES, values);
} catch (OpenDataException e) {
throw new IllegalStateException(e);
}
}
}
private static class ClockTicker extends Ticker {
private final Clock clock;
public ClockTicker(Clock clock) {
this.clock = clock;
}
@Override
public long read() {
return TimeUnit.MILLISECONDS.toNanos(clock.getTime());
}
}
}