Программное отображение статистики вызовов с помощью JMX

Я хотел бы сделать следующее: Предоставление статистики звонков (в основном количество звонков и среднее время звонков) через JMX. Проблема в том, что я хотел бы сделать это с поддержкой AspectJ (чтобы автоматически вычислить его для моих классов реализации).

Я создал JmxBean:

public class JmxStatistics implements Serializable {

private final String name;
private long errorCount;
private long errorCallTime;
private long successCount;
private long successCallTime;

public JmxStatistics(String name) {
    this.name = name;
}

public synchronized long getCallCount() {
    return errorCount + successCount;
}

public synchronized long getAvgCallTimeInMillisecs() {
    return (errorCallTime + successCallTime) / getCallCount();
}

public synchronized long getAvgSuccessfulCallTimeInMillisecs() {
    return (errorCallTime + successCallTime) / successCount;
}

public synchronized long getAvgFailedCallTimeInMillisecs() {
    return (errorCallTime + successCallTime) / errorCount;
}

public synchronized void increaseErrorCallTime(long sumCallTime) {
    this.errorCallTime += sumCallTime;
}

public synchronized void increaseSuccessCallTime(long sumCallTime) {
    this.successCallTime += sumCallTime;
}

public synchronized long getErrorCount() {
    return errorCount;
}

public synchronized void incrementErrorCount() {
    this.errorCount++;
}

public synchronized long getSuccessCount() {
    return successCount;
}

public synchronized void incrementSuccessCount() {
    this.successCount++;
}

@Override
public String toString() {
    return name + "{" + "callCount=" + getCallCount()
            + " (s: " + successCount + ",e:" + errorCount + "); "
            + "avgCallTime=" + getAvgCallTimeInMillisecs() + "ms "
            + "(" + getAvgSuccessfulCallTimeInMillisecs() + "ms;e:" + getAvgFailedCallTimeInMillisecs() + "ms)" + "}";
}

Точка переноса АОП:

public Object wrap(ProceedingJoinPoint joinPoint) throws Throwable {
    JmxStatistics jmxs = store.getStatisticsBean(joinPoint);
    long start = System.currentTimeMillis();
    Object result;
    try {
        result = joinPoint.proceed();
    } catch (Throwable t) {
        long runtime = System.currentTimeMillis() - start;
        jmxs.incrementErrorCount();
        jmxs.increaseErrorCallTime(runtime);
        throw t;
    }
    long runtime = System.currentTimeMillis() - start;
    jmxs.incrementSuccessCount();
    jmxs.increaseSuccessCallTime(runtime);
    return result;
}

И хранилище для выставления всех (программно созданных) JMX-бинов:

public class JmxStatisticsStore {

private final HashMap<String, JmxStatistics> jmxBeans = new HashMap<>();

public HashMap<String, JmxStatistics> getJmxBeans() {
    return jmxBeans;
}

JmxStatistics getStatisticsBean(ProceedingJoinPoint joinPoint) {
    String id = joinPoint.getTarget().getClass().toString() + "." + joinPoint.getSignature().getName();
    if (!jmxBeans.containsKey(id)) {
        synchronized (jmxBeans) {
            if (!jmxBeans.containsKey(id)) {
                JmxStatistics jmxStatistics = new JmxStatistics(id);
                jmxBeans.put(id, jmxStatistics);
            }
        }
    }
    return jmxBeans.get(id);
}
}

Я бы хотел использовать для этого Spring AOP и MBeanExporter. Моя конфигурация следующая:

<bean id="jmxStatisticsStore" class="package.JmxStatisticsStore"/>
<bean id="jmxAspect" class="package.AspectJJmxCalculator" />
<aop:config>        
    <aop:aspect id="jmxAspectId" ref="jmxAspect">

        <aop:pointcut id="pointCutAround"
                      expression="execution(* jmx.tester..*.*(..))" />

        <aop:around method="wrap" pointcut-ref="pointCutAround"  />
    </aop:aspect>
</aop:config>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
    <property name="beans">
        <map>
            <entry key="bean:name=callStatistics" value-ref="jmxStatisticsStore"/>
        </map>
    </property>
</bean>

В настоящее время я пытаюсь запустить его в tomcat (в пакете jmx.tester есть компонент Spring, который я использую в веб-приложении). Веб-приложение работает правильно, и с помощью JConsole я могу подключиться к серверу. На сервере в разделе bean-компонентов я вижу bean-компонент callStatistics с атрибутом HashMap.

Пока я не сделаю вызов, хэш-карта пуста (что хорошо) (в поле «Значение» отображается {}). После того, как я совершу вызов (так что хеш-карта больше не пуста), значение становится недоступным, и если я попытаюсь вызвать метод getJmxBeans через JMX, я получу следующее исключение:

Problem invoking getJmxBeans: java.rmi.UnmarshalException: error unmarshalling return; nested exception is: 
java.lang.ClassNotFoundException: package.JmxStatistics (no security manager: RMI class loader disabled)

РЕДАКТИРОВАТЬ: Я хотел бы видеть, что вызовы для разных идентификаторов отображаются как отдельные атрибуты JMX.

Есть ли способ сделать то, что я хочу достичь? Спасибо


person SzaboAdam    schedule 30.09.2014    source источник


Ответы (2)


Удалось это решить. Изменен JmxStatisticsStore:

@Autowired
MBeanExporter exporter;

private static final Logger LOG = LogManager.getLogger();
private final HashMap<String, Object> jmxBeans = new HashMap();

JmxData getStatisticsBean(ProceedingJoinPoint joinPoint) {
    String id = "bean:name=" + joinPoint.getTarget().getClass().toString() + "." + joinPoint.getSignature().getName();
    if (!jmxBeans.containsKey(id)) {
        synchronized (jmxBeans) {
            if (!jmxBeans.containsKey(id)) {
                JmxData jmxStatistics = new JmxData(id);
                jmxBeans.put(id, jmxStatistics);
                try {
                    exporter.registerManagedResource(jmxStatistics, new ObjectName(id));
                } catch (MalformedObjectNameException ex) {
                    LOG.warn("Error while registering " + id, ex);
                }
            }
        }
    }
    return (JmxData) jmxBeans.get(id);
}

Таким образом, создаются новые MBean-компоненты во время выполнения.

person SzaboAdam    schedule 30.09.2014

Я рекомендую вам библиотеку показателей.

Он имеет Spring интеграцию и отчеты через JMX.

Использовать эту библиотеку в Spring очень просто с помощью аннотаций. Это пример;

public class Foo {

@Timed
public void bar() { /* … */ }

public void baz() {
    this.bar(); // doesn't pass through the proxy

    // fix: reengineer
    // workaround: enable `expose-proxy` and change to:
    ((Foo) AopContext.currentProxy()).bar(); // hideous, but it works
} 

}

person ajnavarro    schedule 30.09.2014
comment
Хммм ... проверим позже, если оно лучше, чем наше текущее решение - person SzaboAdam; 30.09.2014