/** * Copyright 2013 Netflix, Inc. * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.netflix.servo.monitor; import com.netflix.servo.tag.BasicTagList; import com.netflix.servo.tag.TagList; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * This class creates a {@link java.lang.reflect.Proxy} monitor that tracks all calls to methods * of an interface. * <p/> * <pre> * IDummy dummy = TimedInterface.newProxy(IDummy.class, new DummyImpl(), "id"); * DefaultMonitorRegistry.getInstance().register((CompositeMonitor)dummy); * </pre> * <p/> * <p> * All calls to methods implemented by IDummy would have an associated BasicTimer with them. The * name for the {@link CompositeMonitor} is the name of the method. Additional tags are added: * <ul> * <li><code>interface</code> interface being implemented. * <li><code>class</code> simple name of the concrete class implementing the interface. * <li><code>id</code> (Optional) An identifier for this particular instance. * </ul> * </p> */ public final class TimedInterface { static final String TIMED_INTERFACE = "TimedInterface"; static final String INTERFACE_TAG = "interface"; static final String CLASS_TAG = "class"; static final String ID_TAG = "id"; private static class TimedHandler<T> implements InvocationHandler, CompositeMonitor<Long> { private final T concrete; private final Map<String, Timer> timers; private final MonitorConfig baseConfig; private final TagList baseTagList; /** * {@inheritDoc} */ @Override public List<Monitor<?>> getMonitors() { return timers.values().stream().collect(Collectors.toList()); } @Override public Long getValue(int pollerIdx) { return (long) timers.size(); } @Override public Long getValue() { return getValue(0); } /** * {@inheritDoc} */ @Override public MonitorConfig getConfig() { return baseConfig; } TimedHandler(Class<T> ctype, T concrete, String id) { this.concrete = concrete; BasicTagList tagList = BasicTagList.of( INTERFACE_TAG, ctype.getSimpleName(), CLASS_TAG, concrete.getClass().getSimpleName()); if (id != null) { tagList = tagList.copy(ID_TAG, id); } baseTagList = tagList; baseConfig = MonitorConfig.builder(TIMED_INTERFACE).withTags(baseTagList).build(); timers = new HashMap<>(); for (Method method : ctype.getMethods()) { final MonitorConfig config = MonitorConfig.builder(method.getName()) .withTags(baseTagList) .build(); timers.put(method.getName(), new BasicTimer(config)); } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // if the method is one of the CompositeMonitor interface final Class<?> declaringClass = method.getDeclaringClass(); if (declaringClass.isAssignableFrom(CompositeMonitor.class)) { return method.invoke(this, args); } final String methodName = method.getName(); final Timer timer = timers.get(methodName); final Stopwatch stopwatch = timer.start(); try { return method.invoke(concrete, args); } finally { stopwatch.stop(); } } } private TimedInterface() { } /** * Creates a new TimedInterface for a given interface <code>ctype</code> with a concrete class * <code>concrete</code> and a specific id. The id can be used to distinguish among multiple * objects with the same concrete class. */ @SuppressWarnings("unchecked") public static <T> T newProxy(Class<T> ctype, T concrete, String id) { final InvocationHandler handler = new TimedHandler<>(ctype, concrete, id); final Class<?>[] types = new Class[]{ctype, CompositeMonitor.class}; return (T) Proxy.newProxyInstance(ctype.getClassLoader(), types, handler); } /** * Creates a new TimedInterface for a given interface <code>ctype</code> with a concrete class * <code>concrete</code>. */ public static <T> T newProxy(Class<T> ctype, T concrete) { return newProxy(ctype, concrete, null); } }