blob: ceea3dbb9c7bffdd58716497957d90cdafc8855f [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.document;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
import org.apache.jackrabbit.oak.spi.commit.CommitHook;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.stats.Clock;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import static org.apache.jackrabbit.oak.plugins.document.TestUtils.merge;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class DocumentNodeStoreStatsCollectorIT {
@Rule
public DocumentMKBuilderProvider builderProvider = new DocumentMKBuilderProvider();
private DocumentNodeStoreStatsCollector statsCollector = mock(DocumentNodeStoreStatsCollector.class);
private Clock clock;
private DocumentStore store;
private DocumentNodeStore nodeStore;
@Before
public void setUp() throws Exception {
clock = new Clock.Virtual();
clock.waitUntil(System.currentTimeMillis());
Revision.setClock(clock);
ClusterNodeInfo.setClock(clock);
store = new MemoryDocumentStore();
nodeStore = builderProvider.newBuilder()
.clock(clock)
.setDocumentStore(store)
.setAsyncDelay(0)
.setNodeStoreStatsCollector(statsCollector)
.setUpdateLimit(10)
.getNodeStore();
// do not retry failed merges
nodeStore.setMaxBackOffMillis(0);
}
@AfterClass
public static void reset() {
Revision.resetClockToDefault();
ClusterNodeInfo.resetClockToDefault();
}
@Test
public void doneBackgroundRead() {
nodeStore.runBackgroundReadOperations();
verify(statsCollector).doneBackgroundRead(any(BackgroundReadStats.class));
}
@Test
public void doneBackgroundUpdate() {
nodeStore.runBackgroundUpdateOperations();
verify(statsCollector).doneBackgroundUpdate(any(BackgroundWriteStats.class));
}
@Test
public void doneMerge() throws Exception {
NodeBuilder nb = nodeStore.getRoot().builder();
nb.child("a");
nodeStore.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
verify(statsCollector).doneMerge(eq(0), anyLong(), eq(false), eq(false));
}
@Test
public void failedMerge() {
CommitHook failingHook = new CommitHook() {
@Override
public NodeState processCommit(NodeState before, NodeState after,
CommitInfo info) throws CommitFailedException {
throw new CommitFailedException(CommitFailedException.MERGE, 0 , "");
}
};
NodeBuilder nb1 = nodeStore.getRoot().builder();
nb1.child("a");
try {
nodeStore.merge(nb1, failingHook, CommitInfo.EMPTY);
fail();
} catch (CommitFailedException ignore){
}
verify(statsCollector).failedMerge(anyInt(), anyLong(), eq(false), eq(false));
//Should be called once more with exclusive lock
verify(statsCollector).failedMerge(anyInt(), anyLong(), eq(false), eq(true));
}
@Test
public void branchCommit() throws Exception {
int updateLimit = nodeStore.getUpdateLimit();
NodeBuilder nb = nodeStore.getRoot().builder();
for (int i = 0; i < updateLimit; i++) {
nb.child("node-" + i).setProperty("p", "v");
}
merge(nodeStore, nb);
verify(statsCollector, times(2)).doneBranchCommit();
verify(statsCollector).doneMergeBranch(2);
}
@Test
public void leaseUpdate() throws Exception {
clock.waitUntil(clock.getTime() + ClusterNodeInfo.DEFAULT_LEASE_UPDATE_INTERVAL_MILLIS * 2);
assertTrue(nodeStore.renewClusterIdLease());
verify(statsCollector).doneLeaseUpdate(anyLong());
}
@Test
public void externalChangesLag() throws Exception {
// start a second cluster node
DocumentNodeStore ns2 = builderProvider.newBuilder()
.setDocumentStore(store)
.clock(clock)
.setClusterId(2)
.setAsyncDelay(0)
.getNodeStore();
ns2.runBackgroundOperations();
nodeStore.runBackgroundOperations();
NodeBuilder nb = ns2.getRoot().builder();
nb.child("test");
merge(ns2, nb);
ns2.runBackgroundOperations();
Mockito.reset(statsCollector);
long lag = 2000;
// wait two seconds
clock.waitUntil(clock.getTime() + lag);
// then run background read
nodeStore.runBackgroundReadOperations();
verify(statsCollector).doneBackgroundRead(argThat(
// a bit more than 2000 ms
stats -> stats.externalChangesLag > lag
&& stats.externalChangesLag - lag < 100
));
}
}