DELTASPIKE-1316 add @InvocationMonitored

wip, still misses tests


Project: http://git-wip-us.apache.org/repos/asf/deltaspike/repo
Commit: http://git-wip-us.apache.org/repos/asf/deltaspike/commit/9423d746
Tree: http://git-wip-us.apache.org/repos/asf/deltaspike/tree/9423d746
Diff: http://git-wip-us.apache.org/repos/asf/deltaspike/diff/9423d746

Branch: refs/heads/master
Commit: 9423d7466d9507bc243ca3e7bea5a97986bd3bb6
Parents: 8cc2a7f
Author: Mark Struberg <strub...@apache.org>
Authored: Fri Feb 9 20:43:19 2018 +0100
Committer: Mark Struberg <strub...@apache.org>
Committed: Fri Feb 9 20:43:19 2018 +0100

----------------------------------------------------------------------
 .../api/monitoring/InvocationMonitored.java     |  43 +++++
 .../core/api/monitoring/MonitorResultEvent.java |  81 ++++++++++
 .../InvocationMonitorInterceptor.java           |  47 ++++++
 .../impl/monitoring/InvocationResultLogger.java | 160 +++++++++++++++++++
 .../monitoring/RequestInvocationCounter.java    | 105 ++++++++++++
 5 files changed, 436 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/deltaspike/blob/9423d746/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/monitoring/InvocationMonitored.java
----------------------------------------------------------------------
diff --git 
a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/monitoring/InvocationMonitored.java
 
b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/monitoring/InvocationMonitored.java
new file mode 100644
index 0000000..48170c8
--- /dev/null
+++ 
b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/monitoring/InvocationMonitored.java
@@ -0,0 +1,43 @@
+/*
+ * 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.deltaspike.core.api.monitoring;
+
+import javax.interceptor.InterceptorBinding;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this annotation to mark a bean as being monitored.
+ * This will activate a CDI Interceptor which will track
+ * all method invocations on that class and measure how
+ * often they got invoked and how much time they consume.
+ *
+ * At the end of a request the final times will get sent out
+ * as {@link MonitorResultEvent}.
+ */
+@Inherited
+@InterceptorBinding
+@Target( { ElementType.TYPE, ElementType.METHOD } )
+@Retention(RetentionPolicy.RUNTIME)
+public @interface InvocationMonitored
+{
+}

http://git-wip-us.apache.org/repos/asf/deltaspike/blob/9423d746/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/monitoring/MonitorResultEvent.java
----------------------------------------------------------------------
diff --git 
a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/monitoring/MonitorResultEvent.java
 
b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/monitoring/MonitorResultEvent.java
new file mode 100644
index 0000000..7c4db7b
--- /dev/null
+++ 
b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/monitoring/MonitorResultEvent.java
@@ -0,0 +1,81 @@
+/*
+ * 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.deltaspike.core.api.monitoring;
+
+import javax.enterprise.inject.Typed;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * This class will be used as event to transport final monitor values
+ *
+ * @see InvocationMonitored
+ */
+@Typed()
+public class MonitorResultEvent
+{
+    private Map<String, AtomicInteger> methodInvocations = new HashMap<String, 
AtomicInteger>();
+
+    private Map<String, AtomicInteger> classInvocations  = new HashMap<String, 
AtomicInteger>();
+
+    private Map<String, AtomicLong> methodDurations = new HashMap<String, 
AtomicLong>();
+
+    public MonitorResultEvent(Map<String, AtomicInteger> methodInvocations,
+                              Map<String, AtomicInteger> classInvocations,
+                              Map<String, AtomicLong> methodDurations)
+    {
+        this.methodInvocations = methodInvocations;
+        this.classInvocations = classInvocations;
+        this.methodDurations = methodDurations;
+    }
+
+
+    /**
+     * @return Map with Counters for all method invocations
+     * key = fully qualified method name (includes class)
+     * value = AtomicInteger with invocation count value
+     */
+    public Map<String, AtomicInteger> getMethodInvocations()
+    {
+        return methodInvocations;
+    }
+
+    /**
+     * @return Map with Counter for all class invocations
+     * key = fully qualified class name
+     * value = AtomicInteger with invocation count value
+     */
+    public Map<String, AtomicInteger> getClassInvocations()
+    {
+        return classInvocations;
+    }
+
+
+    /**
+     * @return Map with duration for all method invocations
+     * key = fully qualified method name (includes class)
+     * value = AtomicLong with duration nanos
+     */
+    public Map<String, AtomicLong> getMethodDurations()
+    {
+        return methodDurations;
+    }
+}

http://git-wip-us.apache.org/repos/asf/deltaspike/blob/9423d746/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/InvocationMonitorInterceptor.java
----------------------------------------------------------------------
diff --git 
a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/InvocationMonitorInterceptor.java
 
b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/InvocationMonitorInterceptor.java
new file mode 100644
index 0000000..54b3b7d
--- /dev/null
+++ 
b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/InvocationMonitorInterceptor.java
@@ -0,0 +1,47 @@
+/*
+ * 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.deltaspike.core.impl.monitoring;
+
+import org.apache.deltaspike.core.api.monitoring.InvocationMonitored;
+
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+import java.io.Serializable;
+
+@Interceptor
+@InvocationMonitored
+public class InvocationMonitorInterceptor implements Serializable
+{
+    @Inject
+    private RequestInvocationCounter requestInvocationCounter;
+
+    @AroundInvoke
+    public Object track(InvocationContext ic) throws Exception
+    {
+        long start = System.nanoTime();
+        Object retVal = ic.proceed();
+        long end = System.nanoTime();
+        requestInvocationCounter.count(ic.getTarget().getClass().getName(), 
ic.getMethod().getName(), end - start);
+
+        return retVal;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/deltaspike/blob/9423d746/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/InvocationResultLogger.java
----------------------------------------------------------------------
diff --git 
a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/InvocationResultLogger.java
 
b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/InvocationResultLogger.java
new file mode 100644
index 0000000..52639e2
--- /dev/null
+++ 
b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/InvocationResultLogger.java
@@ -0,0 +1,160 @@
+/*
+ * 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.deltaspike.core.impl.monitoring;
+
+import org.apache.deltaspike.core.api.monitoring.MonitorResultEvent;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Logger;
+
+/**
+ * This class will observe all {@link MonitorResultEvent}s
+ * and log them accordingly
+ */
+@ApplicationScoped
+public class InvocationResultLogger
+{
+    private static final Logger logger = 
Logger.getLogger(InvocationResultLogger.class.getName());
+
+    private static final int DEFAULT_MAX_LOG_LINES = 8;
+    private static final String PROPERTY_MAX_LOG_LINES = "MAX_LOG_LINES";
+
+    private int maxLogLines = DEFAULT_MAX_LOG_LINES + 1;
+
+    @PostConstruct
+    private void init()
+    {
+        String maxLogLinesProp = System.getProperty(PROPERTY_MAX_LOG_LINES);
+        if (maxLogLinesProp != null)
+        {
+            maxLogLines = Integer.parseInt(maxLogLinesProp) + 1;
+        }
+
+        logger.info("Using MAX_LOG_LINE=" + maxLogLines);
+    }
+
+    public void logMonitorResultEvents(@Observes MonitorResultEvent mre)
+    {
+        // we copy them because we don't like to make the event data dirty.
+        // there might be other observers interested in the result...
+        List<ResultEntry> methodInvocations
+            = createMethodResultEntries(mre.getMethodInvocations(), 
mre.getMethodDurations());
+        List<ResultEntry> classInvocations
+            = createClassResultEntries(mre.getClassInvocations());
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("Top Class Invocations:\n");
+        for (int i = 1; i < maxLogLines && i <= classInvocations.size(); i++)
+        {
+            ResultEntry re = classInvocations.get(classInvocations.size() - i);
+            sb.append("  count: 
").append(re.getCount()).append("\t").append(re.getToken()).append("\n");
+        }
+        logger.info(sb.toString());
+
+        sb = new StringBuilder();
+        sb.append("Top Method Invocations:\n");
+        for (int i = 1; i < maxLogLines && i <= methodInvocations.size(); i++)
+        {
+            ResultEntry re = methodInvocations.get(methodInvocations.size() - 
i);
+            sb.append("  dur[ms]: ").append(re.getDuration() / 
1e6f).append("\tcount: ").
+                    
append(re.getCount()).append("\t").append(re.getToken()).append("\n");
+        }
+        logger.info(sb.toString());
+    }
+
+
+    private List<ResultEntry> createMethodResultEntries(Map<String, 
AtomicInteger> invocations,
+                                                        Map<String, 
AtomicLong> durations)
+    {
+        List<ResultEntry> resultEntries = new 
ArrayList<ResultEntry>(invocations.size());
+
+
+
+        for (Map.Entry<String, AtomicInteger> entry : invocations.entrySet())
+        {
+            long dur = durations.get(entry.getKey()).longValue();
+            resultEntries.add(new ResultEntry(entry.getValue().intValue(), 
entry.getKey(), dur));
+        }
+
+        Collections.sort(resultEntries);
+
+        return resultEntries;
+    }
+
+    private List<ResultEntry> createClassResultEntries(Map<String, 
AtomicInteger> invocations)
+    {
+        List<ResultEntry> resultEntries = new 
ArrayList<ResultEntry>(invocations.size());
+
+        for (Map.Entry<String, AtomicInteger> entry : invocations.entrySet())
+        {
+            resultEntries.add(new ResultEntry(entry.getValue().intValue(), 
entry.getKey(), 0L));
+        }
+
+        Collections.sort(resultEntries);
+
+        return resultEntries;
+    }
+
+    private static class ResultEntry implements Comparable<ResultEntry>
+    {
+        private Integer count;
+        private String token;
+        private long    duration;
+
+        private ResultEntry(Integer count, String token, long duration)
+        {
+            this.count = count;
+            this.token = token;
+            this.duration = duration;
+        }
+
+        public Integer getCount()
+        {
+            return count;
+        }
+
+        public String getToken()
+        {
+            return token;
+        }
+
+        public long getDuration()
+        {
+            return duration;
+        }
+
+        public int compareTo(ResultEntry o)
+        {
+            if (duration == 0 && o.duration == 0)
+            {
+                return count.compareTo(o.count);
+            }
+
+            return duration < o.duration ? -1 : (duration == o.duration ? 0 : 
1);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/deltaspike/blob/9423d746/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/RequestInvocationCounter.java
----------------------------------------------------------------------
diff --git 
a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/RequestInvocationCounter.java
 
b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/RequestInvocationCounter.java
new file mode 100644
index 0000000..8f46c37
--- /dev/null
+++ 
b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/monitoring/RequestInvocationCounter.java
@@ -0,0 +1,105 @@
+/*
+ * 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.deltaspike.core.impl.monitoring;
+
+import org.apache.deltaspike.core.api.monitoring.MonitorResultEvent;
+
+import javax.annotation.PreDestroy;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.event.Event;
+import javax.inject.Inject;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * This bean will get used to count invocations for a single request
+ */
+@RequestScoped
+public class RequestInvocationCounter
+{
+    @Inject
+    private Event<MonitorResultEvent> mre;
+
+
+    /**
+     * Counter for all method invocations
+     * key = fully qualified method name (includes class)
+     * value = Integer with value
+     */
+    private Map<String, AtomicInteger> methodInvocations = new HashMap<String, 
AtomicInteger>();
+
+    /**
+     * Duration of all method invocations
+     * key = fully qualified method name (includes class)
+     * value = Integer with value
+     */
+    private Map<String, AtomicLong> methodDurations = new HashMap<String, 
AtomicLong>();
+
+    /**
+     * Counter for all class invocations
+     * key = fully qualified class name
+     * value = Integer with value
+     */
+    private Map<String, AtomicInteger> classInvocations  = new HashMap<String, 
AtomicInteger>();
+
+
+    @PreDestroy
+    public void postUsage()
+    {
+        mre.fire(new MonitorResultEvent(methodInvocations, classInvocations, 
methodDurations));
+    }
+
+    /**
+     * increment the respective counters
+     * @param className the getName() of the class
+     * @param methodName the invoked methods name
+     * @param duration duration of the method invocation in nano time
+     */
+    public void count(String className, String methodName, long duration)
+    {
+        AtomicInteger classCount = classInvocations.get(className);
+        if (classCount == null)
+        {
+            classCount = new AtomicInteger(0);
+            classInvocations.put(className, classCount);
+        }
+        classCount.incrementAndGet();
+
+
+        String methodKey = className + "#" + methodName;
+
+        AtomicInteger methCount = methodInvocations.get(methodKey);
+        if (methCount == null)
+        {
+            methCount = new AtomicInteger(0);
+            methodInvocations.put(methodKey, methCount);
+        }
+        methCount.incrementAndGet();
+
+        AtomicLong methDur = methodDurations.get(methodKey);
+        if (methDur == null)
+        {
+            methDur = new AtomicLong(0);
+            methodDurations.put(methodKey, methDur);
+        }
+        methDur.addAndGet(duration);
+    }
+}

Reply via email to