/*
* Copyright 2016-present Facebook, 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.facebook.buck.counters;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertThat;
import com.facebook.buck.event.BuckEvent;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.DefaultBuckEventBus;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.testutil.FakeExecutor;
import com.facebook.buck.timing.FakeClock;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.eventbus.Subscribe;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class CounterRegistryImplTest {
private static final String CATEGORY = "Counter_Category";
private static final String NAME = "Counter_Name";
public static final ImmutableMap<String, String> TAGS =
ImmutableMap.of("My super Tag Key", "And the according value!");
private BuckEventBus eventBus;
private ScheduledThreadPoolExecutor executor;
private Capture<Runnable> flushCountersRunnable;
private Capture<BuckEvent> countersEvent;
// Apparently EasyMock does not deal very well with Generic Types using wildcard ?.
// Several workarounds can be found on StackOverflow this one being the list intrusive.
@SuppressWarnings("rawtypes")
private ScheduledFuture mockFuture;
private static class SnapshotEventListener {
public final List<CountersSnapshotEvent> snapshotEvents =
new ArrayList<CountersSnapshotEvent>();
@Subscribe
public void onCountersSnapshotEvent(CountersSnapshotEvent event) {
snapshotEvents.add(event);
}
}
@Before
@SuppressWarnings("unchecked")
public void setUp() {
this.countersEvent = EasyMock.newCapture();
this.mockFuture = EasyMock.createMock(ScheduledFuture.class);
this.flushCountersRunnable = EasyMock.newCapture();
this.eventBus = EasyMock.createNiceMock(BuckEventBus.class);
this.executor = EasyMock.createNiceMock(ScheduledThreadPoolExecutor.class);
this.eventBus.post(EasyMock.capture(countersEvent));
EasyMock.expectLastCall();
EasyMock.expect(
this.executor.scheduleAtFixedRate(
EasyMock.capture(flushCountersRunnable),
EasyMock.anyInt(),
EasyMock.anyInt(),
EasyMock.anyObject(TimeUnit.class)))
.andReturn(this.mockFuture);
EasyMock.expect(this.mockFuture.cancel(EasyMock.anyBoolean())).andReturn(true).once();
EasyMock.replay(eventBus, executor, mockFuture);
}
@Test
public void testCreatingCounters() throws IOException {
try (CounterRegistryImpl registry = new CounterRegistryImpl(executor, eventBus)) {
Assert.assertNotNull(registry.newIntegerCounter(CATEGORY, "counter1", TAGS));
Assert.assertNotNull(registry.newSamplingCounter(CATEGORY, "counter2", TAGS));
Assert.assertNotNull(registry.newTagSetCounter(CATEGORY, "counter3", TAGS));
}
}
@Test
public void testFlushingCounters() throws IOException {
try (CounterRegistryImpl registry = new CounterRegistryImpl(executor, eventBus)) {
IntegerCounter counter = registry.newIntegerCounter(CATEGORY, NAME, TAGS);
counter.inc(42);
TagSetCounter tagSetCounter = registry.newTagSetCounter(CATEGORY, "TagSet_Counter", TAGS);
tagSetCounter.add("value1");
tagSetCounter.addAll(ImmutableSet.of("value2", "value3"));
Assert.assertTrue(flushCountersRunnable.hasCaptured());
flushCountersRunnable.getValue().run();
Assert.assertTrue(countersEvent.hasCaptured());
CountersSnapshotEvent event = (CountersSnapshotEvent) countersEvent.getValue();
Assert.assertEquals(2, event.getSnapshots().size());
Assert.assertEquals(42, (long) event.getSnapshots().get(0).getValues().values().toArray()[0]);
Assert.assertEquals(
ImmutableSetMultimap.of(
"TagSet_Counter", "value1",
"TagSet_Counter", "value2",
"TagSet_Counter", "value3"),
event.getSnapshots().get(1).getTagSets());
}
}
@Test
public void noEventsFlushedIfNoCountersRegistered() throws IOException {
BuckEventBus fakeEventBus =
new DefaultBuckEventBus(new FakeClock(0), false, new BuildId("12345"), 1000);
SnapshotEventListener listener = new SnapshotEventListener();
fakeEventBus.register(listener);
FakeExecutor fakeExecutor = new FakeExecutor();
try (CounterRegistryImpl registry = new CounterRegistryImpl(fakeExecutor, fakeEventBus)) {
assertThat(
"No events should be flushed before timer fires", listener.snapshotEvents, empty());
fakeExecutor.drain();
assertThat("No events should be flushed after timer fires", listener.snapshotEvents, empty());
}
assertThat(
"No events should be flushed after registry closed", listener.snapshotEvents, empty());
}
@Test
public void noEventsFlushedIfCounterRegisteredButHasNoData() throws IOException {
BuckEventBus fakeEventBus =
new DefaultBuckEventBus(new FakeClock(0), false, new BuildId("12345"), 1000);
SnapshotEventListener listener = new SnapshotEventListener();
fakeEventBus.register(listener);
FakeExecutor fakeExecutor = new FakeExecutor();
try (CounterRegistryImpl registry = new CounterRegistryImpl(fakeExecutor, fakeEventBus)) {
registry.newIntegerCounter(CATEGORY, NAME, TAGS);
assertThat(
"No events should be flushed before timer fires", listener.snapshotEvents, empty());
fakeExecutor.drain();
assertThat(
"No events should be flushed after timer fires when counter has no data",
listener.snapshotEvents,
empty());
}
assertThat(
"No events should be flushed after registry closed", listener.snapshotEvents, empty());
}
@Test
public void eventIsFlushedIfCounterRegisteredWithData() throws IOException {
BuckEventBus fakeEventBus =
new DefaultBuckEventBus(new FakeClock(0), false, new BuildId("12345"), 1000);
SnapshotEventListener listener = new SnapshotEventListener();
fakeEventBus.register(listener);
FakeExecutor fakeExecutor = new FakeExecutor();
try (CounterRegistryImpl registry = new CounterRegistryImpl(fakeExecutor, fakeEventBus)) {
IntegerCounter counter = registry.newIntegerCounter(CATEGORY, NAME, TAGS);
counter.inc(42);
assertThat(
"No events should be flushed before timer fires", listener.snapshotEvents, empty());
fakeExecutor.drain();
assertThat(
"One event should be flushed after timer fires", listener.snapshotEvents, hasSize(1));
assertThat(
"Counter with expected value should be flushed after timer fires",
listener.snapshotEvents.get(0).getSnapshots(),
hasItem(
CounterSnapshot.builder()
.setCategory(CATEGORY)
.setTags(TAGS)
.putValues(NAME, 42)
.build()));
}
}
@Test
public void closingRegistryBeforeTimerFiresFlushesCounters() throws IOException {
BuckEventBus fakeEventBus =
new DefaultBuckEventBus(new FakeClock(0), false, new BuildId("12345"), 1000);
SnapshotEventListener listener = new SnapshotEventListener();
fakeEventBus.register(listener);
FakeExecutor fakeExecutor = new FakeExecutor();
try (CounterRegistryImpl registry = new CounterRegistryImpl(fakeExecutor, fakeEventBus)) {
IntegerCounter counter = registry.newIntegerCounter(CATEGORY, NAME, TAGS);
counter.inc(42);
assertThat(
"No events should be flushed before timer fires", listener.snapshotEvents, empty());
}
// We explicitly do not call fakeExecutor.drain() here, because we want to simulate what
// happens when the registry is closed before the executor fires.
assertThat(
"One snapshot event should be flushed when registry closed before timer fires",
listener.snapshotEvents,
hasSize(1));
assertThat(
"Expected snapshot should be flushed when registry closed before timer fires",
listener.snapshotEvents.get(0).getSnapshots(),
hasItem(
CounterSnapshot.builder()
.setCategory(CATEGORY)
.setTags(TAGS)
.putValues(NAME, 42)
.build()));
}
}