/**
* 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 org.apache.aurora.scheduler.thrift.aop;
import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.matcher.Matchers;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.aurora.common.stats.Stats;
import org.apache.aurora.common.testing.easymock.EasyMockTest;
import org.apache.aurora.gen.GetJobsResult;
import org.apache.aurora.gen.JobConfiguration;
import org.apache.aurora.gen.Response;
import org.apache.aurora.gen.Result;
import org.apache.aurora.scheduler.thrift.auth.DecoratedThrift;
import org.junit.Before;
import org.junit.Test;
import static org.apache.aurora.gen.ResponseCode.OK;
import static org.apache.aurora.scheduler.thrift.Responses.error;
import static org.apache.aurora.scheduler.thrift.Responses.ok;
import static org.easymock.EasyMock.expect;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
public class ThriftStatsExporterInterceptorTest extends EasyMockTest {
private static final String ROLE = "bob";
private AnnotatedAuroraAdmin realThrift;
private AnnotatedAuroraAdmin decoratedThrift;
private ThriftStatsExporterInterceptor statsInterceptor;
@Before
public void setUp() {
statsInterceptor = new ThriftStatsExporterInterceptor();
realThrift = createMock(AnnotatedAuroraAdmin.class);
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
MockDecoratedThrift.bindForwardedMock(binder(), realThrift);
AopModule.bindThriftDecorator(
binder(),
Matchers.annotatedWith(DecoratedThrift.class),
statsInterceptor);
}
});
decoratedThrift = injector.getInstance(AnnotatedAuroraAdmin.class);
addTearDown(Stats::flush);
}
@Test
public void testIncrementStat() throws Exception {
Response response = new Response().setResponseCode(OK)
.setResult(Result.getJobsResult(new GetJobsResult()
.setConfigs(ImmutableSet.of())));
expect(realThrift.getJobs(ROLE)).andReturn(response);
control.replay();
assertSame(response, decoratedThrift.getJobs(ROLE));
String statName = timingStatName("getJobs");
assertEquals(1L, Stats.getVariable(statName + "_events").read());
assertNotNull(Stats.getVariable(statName + "_events_per_sec"));
assertNotNull(Stats.getVariable(statName + "_nanos_per_event"));
assertNotNull(Stats.getVariable(statName + "_nanos_total"));
assertNotNull(Stats.getVariable(statName + "_nanos_total_per_sec"));
}
@Test
public void testMeasuredMethod() throws Throwable {
MethodInvocation invocation = createMock(MethodInvocation.class);
expect(invocation.getMethod())
.andReturn(InterceptedClass.class.getDeclaredMethod("measuredMethod"));
expect(invocation.proceed()).andReturn(ok().setResult(Result.getJobsResult(
new GetJobsResult(ImmutableSet.of(new JobConfiguration())))));
control.replay();
new ThriftStatsExporterInterceptor().invoke(invocation);
assertEquals(1L, Stats.getVariable(workloadStatName("measuredMethod")).read());
}
@Test
public void testUnmeasuredMethod() throws Throwable {
MethodInvocation invocation = createMock(MethodInvocation.class);
expect(invocation.getMethod())
.andReturn(InterceptedClass.class.getDeclaredMethod("unmeasuredMethod"));
expect(invocation.proceed()).andReturn(ok().setResult(Result.getJobsResult(
new GetJobsResult(ImmutableSet.of(new JobConfiguration())))));
control.replay();
new ThriftStatsExporterInterceptor().invoke(invocation);
assertNull(Stats.getVariable(workloadStatName("unmeasuredMethod")));
}
@Test
public void testExceptionalMeasuredMethod() throws Throwable {
MethodInvocation invocation = createMock(MethodInvocation.class);
expect(invocation.getMethod())
.andReturn(InterceptedClass.class.getDeclaredMethod("measuredMethod"));
expect(invocation.proceed()).andThrow(new Exception());
control.replay();
try {
new ThriftStatsExporterInterceptor().invoke(invocation);
fail("Should not be reached");
} catch (Exception e) {
assertNull(Stats.getVariable(workloadStatName("measuredMethod")));
}
}
@Test
public void testMeasuredMethodWithErrorResponse() throws Throwable {
MethodInvocation invocation = createMock(MethodInvocation.class);
expect(invocation.getMethod())
.andReturn(InterceptedClass.class.getDeclaredMethod("measuredMethod"));
expect(invocation.proceed()).andReturn(error("ERROR"));
control.replay();
new ThriftStatsExporterInterceptor().invoke(invocation);
assertNull(Stats.getVariable(workloadStatName("measuredMethod")));
}
private static class InterceptedClass {
@ThriftWorkload
public Response measuredMethod() {
throw new UnsupportedOperationException("Should not be called.");
}
public Response unmeasuredMethod() {
throw new UnsupportedOperationException("Should not be called.");
}
}
private static String timingStatName(String methodName) {
return statName(ThriftStatsExporterInterceptor.TIMING_STATS_NAME_TEMPLATE, methodName);
}
private static String workloadStatName(String methodName) {
return statName(ThriftStatsExporterInterceptor.WORKLOAD_STATS_NAME_TEMPLATE, methodName);
}
private static String statName(String template, String methodName) {
return String.format(template, methodName);
}
}