JUNEAU-175 Execution statistics for REST methods.
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/testutils/TestUtils.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/testutils/TestUtils.java
index 9e480e8..0cc99d6 100644
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/testutils/TestUtils.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/testutils/TestUtils.java
@@ -385,6 +385,18 @@
Assert.assertEquals(s, ws.toString(o));
}
+ public static final void assertObjectMatches(String s, Object o) {
+ assertObjectMatches(s, o, js2);
+ }
+
+ public static final void assertObjectMatches(String s, Object o, WriterSerializer ws) {
+ if ("xxx".equals(s))
+ System.err.println(ws.toString(o).replaceAll("\\\\", "\\\\\\\\")); // NOT DEBUG
+ String o2 = ws.toString(o);
+ if (! StringUtils.getMatchPattern(s).matcher(o2).matches())
+ throw new ComparisonFailure(null, s, o2);
+ }
+
/**
* Replaces all newlines with pipes, then compares the strings.
*/
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/MethodInvokerTest.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/MethodInvokerTest.java
new file mode 100644
index 0000000..541f9d7
--- /dev/null
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/MethodInvokerTest.java
@@ -0,0 +1,98 @@
+// ***************************************************************************************************************************
+// * 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.juneau.utils;
+
+import static org.apache.juneau.testutils.TestUtils.*;
+import static org.junit.Assert.assertEquals;
+
+import java.lang.reflect.*;
+
+import org.junit.*;
+
+public class MethodInvokerTest {
+
+ public static class A {
+ public int foo() { return 0; }
+ public int bar() { throw new RuntimeException("bar"); }
+ public void baz(int x) { }
+ }
+
+ @Test
+ public void testBasic() throws Exception {
+ Method m = A.class.getMethod("foo");
+
+ MethodExecStats mes = new MethodExecStats(m);
+ MethodInvoker mi = new MethodInvoker(m, mes);
+
+ A a = new A();
+ mi.invoke(a);
+ mi.invoke(a);
+ mi.invoke(a);
+
+ assertObjectMatches("{method:'A.foo',runs:3,running:0,errors:0,avgTime:*,totalTime:*,exceptions:[]}", mes);
+ }
+
+ @Test
+ public void testException() throws Exception {
+ Method m = A.class.getMethod("bar");
+
+ MethodExecStats mes = new MethodExecStats(m);
+ MethodInvoker mi = new MethodInvoker(m, mes);
+
+ A a = new A();
+ try {
+ mi.invoke(a);
+ } catch (Exception e) {}
+ try {
+ mi.invoke(a);
+ } catch (Exception e) {}
+ try {
+ mi.invoke(a);
+ } catch (Exception e) {}
+
+ assertObjectMatches("{method:'A.bar',runs:3,running:0,errors:3,avgTime:0,totalTime:*,exceptions:[{exception:'RuntimeException',hash:'*',count:3}]}", mes);
+ }
+
+ @Test
+ public void testIllegalArgument() throws Exception {
+ Method m = A.class.getMethod("baz", int.class);
+
+ MethodExecStats mes = new MethodExecStats(m);
+ MethodInvoker mi = new MethodInvoker(m, mes);
+
+ A a = new A();
+ try {
+ mi.invoke(a, "x");
+ } catch (Exception e) {}
+ try {
+ mi.invoke(a);
+ } catch (Exception e) {}
+ try {
+ mi.invoke(a, 1, "x");
+ } catch (Exception e) {}
+
+ assertObjectMatches("{method:'A.baz',runs:3,running:0,errors:3,avgTime:0,totalTime:*,exceptions:[{exception:'IllegalArgumentException',hash:'*',count:3}]}", mes);
+ }
+
+ @Test
+ public void testOtherMethods() throws Exception {
+ Method m = A.class.getMethod("foo");
+ MethodExecStats mes = new MethodExecStats(m);
+ MethodInvoker mi = new MethodInvoker(m, mes);
+
+ assertEquals(m, mi.inner());
+ assertEquals("A", mi.getDeclaringClass().getSimpleName());
+ assertEquals("foo", mi.getName());
+ }
+
+}
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/StackTraceDatabaseTest.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/StackTraceDatabaseTest.java
index 967e989..1ed653e 100644
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/StackTraceDatabaseTest.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/StackTraceDatabaseTest.java
@@ -13,9 +13,13 @@
package org.apache.juneau.utils;
import static org.junit.Assert.*;
+import static org.apache.juneau.testutils.TestUtils.*;
+
+import java.util.*;
import org.junit.*;
+@SuppressWarnings("serial")
public class StackTraceDatabaseTest {
@Test
@@ -26,31 +30,208 @@
t2.fillInStackTrace();
StackTraceDatabase db = new StackTraceDatabase();
- StackTraceInfo t1a = db.getStackTraceInfo(t1, Integer.MAX_VALUE);
- StackTraceInfo t1b = db.getStackTraceInfo(t1, Integer.MAX_VALUE);
- StackTraceInfo t2a = db.getStackTraceInfo(t2, Integer.MAX_VALUE);
+ db.add(t1);
+ StackTraceInfo t1a = db.getStackTraceInfo(t1);
+ db.add(t1);
+ StackTraceInfo t1b = db.getStackTraceInfo(t1);
+ db.add(t2);
+ StackTraceInfo t2a = db.getStackTraceInfo(t2);
+
assertEquals(t1a.getHash(), t1b.getHash());
assertNotEquals(t1a.getHash(), t2a.getHash());
+
assertEquals(1, t1a.getCount());
assertEquals(2, t1b.getCount());
assertEquals(1, t2a.getCount());
}
@Test
+ public void testGetClonedStackTraceInfos() {
+ Throwable t1 = new Throwable();
+ t1.fillInStackTrace();
+ Throwable t2 = new Throwable();
+ t2.fillInStackTrace();
+
+ StackTraceDatabase db = new StackTraceDatabase();
+ db.add(t1);
+ db.add(t1);
+ db.add(t2);
+
+ List<StackTraceInfo> l = db.getClonedStackTraceInfos();
+ db.add(t1);
+
+ assertObjectMatches("[{exception:'Throwable',hash:'*',count:2},{exception:'Throwable',hash:'*',count:1}]", l);
+ }
+
+ @Test
public void testTimeout() {
Throwable t1 = new Throwable();
t1.fillInStackTrace();
Throwable t2 = new Throwable();
t2.fillInStackTrace();
- StackTraceDatabase db = new StackTraceDatabase();
- StackTraceInfo t1a = db.getStackTraceInfo(t1, -1);
- StackTraceInfo t1b = db.getStackTraceInfo(t1, -1);
- StackTraceInfo t2a = db.getStackTraceInfo(t2, -1);
+ StackTraceDatabase db = new StackTraceDatabase(-2, null);
+ db.add(t1);
+ StackTraceInfo t1a = db.getStackTraceInfo(t1);
+ db.add(t1);
+ StackTraceInfo t1b = db.getStackTraceInfo(t1);
+ db.add(t2);
+ StackTraceInfo t2a = db.getStackTraceInfo(t2);
+
assertEquals(t1a.getHash(), t1b.getHash());
assertNotEquals(t1a.getHash(), t2a.getHash());
+
+ assertEquals(0, t1a.getCount());
+ assertEquals(0, t1b.getCount());
+ assertEquals(0, t2a.getCount());
+ }
+
+ @Test
+ public void testReset() {
+ Throwable t1 = new Throwable();
+ t1.fillInStackTrace();
+
+ StackTraceDatabase db = new StackTraceDatabase();
+ db.add(t1);
+ StackTraceInfo t1a = db.getStackTraceInfo(t1);
assertEquals(1, t1a.getCount());
- assertEquals(1, t1b.getCount());
- assertEquals(1, t2a.getCount());
+
+ db.reset();
+ t1a = db.getStackTraceInfo(t1);
+ assertEquals(0, t1a.getCount());
+ }
+
+ @Test
+ public void testNullException() {
+ StackTraceDatabase db = new StackTraceDatabase();
+ db.add(null).add(null);
+ StackTraceInfo t1a = db.getStackTraceInfo(null);
+ assertEquals(2, t1a.getCount());
+ }
+
+ @Test
+ public void testSameStackTraces() {
+ StackTraceDatabase db = new StackTraceDatabase();
+
+ Throwable t1 = new Throwable() {
+ @Override
+ public StackTraceElement[] getStackTrace() {
+ return new StackTraceElement[] {
+ new StackTraceElement("Foo", "bar", "Foo.class", 1),
+ new StackTraceElement("Foo", "baz", "Foo.class", 2),
+ new StackTraceElement("Stop", "baz", "Stop.class", 3),
+ new StackTraceElement("Object", "baz", "Object.class", 6)
+ };
+ }
+ };
+ Throwable t2 = new Throwable() {
+ @Override
+ public StackTraceElement[] getStackTrace() {
+ return new StackTraceElement[] {
+ new StackTraceElement("Foo", "bar", "Foo.class", 1),
+ new StackTraceElement("Foo", "baz", "Foo.class", 2),
+ new StackTraceElement("Stop", "baz", "Stop.class", 3),
+ new StackTraceElement("Object", "baz", "Object.class", 6)
+ };
+ }
+ };
+ StackTraceInfo sti1 = db.getStackTraceInfo(t1);
+ StackTraceInfo sti2 = db.getStackTraceInfo(t2);
+ assertEquals(sti1.getHash(), sti2.getHash());
+ }
+
+ @Test
+ public void testSlightlyDifferentStackTraces() {
+ StackTraceDatabase db = new StackTraceDatabase();
+
+ Throwable t1 = new Throwable() {
+ @Override
+ public StackTraceElement[] getStackTrace() {
+ return new StackTraceElement[] {
+ new StackTraceElement("Foo", "bar", "Foo.class", 1),
+ new StackTraceElement("Foo", "baz", "Foo.class", 2),
+ new StackTraceElement("Stop", "baz", "Stop.class", 3),
+ new StackTraceElement("Object", "baz", "Object.class", 6)
+ };
+ }
+ };
+ Throwable t2 = new Throwable() {
+ @Override
+ public StackTraceElement[] getStackTrace() {
+ return new StackTraceElement[] {
+ new StackTraceElement("Foo", "bar", "Foo.class", 1),
+ new StackTraceElement("Foo", "baz", "Foo.class", 2),
+ new StackTraceElement("Stop", "baz", "Stop.class", 3),
+ new StackTraceElement("Object", "baz", "Object.class", 7)
+ };
+ }
+ };
+ StackTraceInfo sti1 = db.getStackTraceInfo(t1);
+ StackTraceInfo sti2 = db.getStackTraceInfo(t2);
+ assertNotEquals(sti1.getHash(), sti2.getHash());
+ }
+
+ @Test
+ public void testStopClass() {
+ StackTraceDatabase db = new StackTraceDatabase(-1, StopClass.class);
+
+ Throwable t1 = new Throwable() {
+ @Override
+ public StackTraceElement[] getStackTrace() {
+ return new StackTraceElement[] {
+ new StackTraceElement("Foo", "bar", "Foo.class", 1),
+ new StackTraceElement("Foo", "baz", "Foo.class", 2),
+ new StackTraceElement(StopClass.class.getName(), "baz", "Stop.class", 3),
+ new StackTraceElement("Object", "baz", "Object.class", 6)
+ };
+ }
+ };
+ Throwable t2 = new Throwable() {
+ @Override
+ public StackTraceElement[] getStackTrace() {
+ return new StackTraceElement[] {
+ new StackTraceElement("Foo", "bar", "Foo.class", 1),
+ new StackTraceElement("Foo", "baz", "Foo.class", 2),
+ new StackTraceElement(StopClass.class.getName(), "baz", "Stop.class", 4),
+ new StackTraceElement("Object", "baz", "Object.class", 7)
+ };
+ }
+ };
+ StackTraceInfo sti1 = db.getStackTraceInfo(t1);
+ StackTraceInfo sti2 = db.getStackTraceInfo(t2);
+ assertEquals(sti1.getHash(), sti2.getHash());
+ }
+
+ private static final class StopClass {}
+
+ @Test
+ public void testProxyElements() {
+ StackTraceDatabase db = new StackTraceDatabase();
+
+ Throwable t1 = new Throwable() {
+ @Override
+ public StackTraceElement[] getStackTrace() {
+ return new StackTraceElement[] {
+ new StackTraceElement("Foo", "bar", "Foo.class", 1),
+ new StackTraceElement("Foo", "baz", "Foo.class", 2),
+ new StackTraceElement("Stop$1", "baz", "Stop.class", 5),
+ new StackTraceElement("Object", "baz", "Object.class", 6)
+ };
+ }
+ };
+ Throwable t2 = new Throwable() {
+ @Override
+ public StackTraceElement[] getStackTrace() {
+ return new StackTraceElement[] {
+ new StackTraceElement("Foo", "bar", "Foo.class", 1),
+ new StackTraceElement("Foo", "baz", "Foo.class", 2),
+ new StackTraceElement("Stop$2", "baz", "Stop.class", 6),
+ new StackTraceElement("Object", "baz", "Object.class", 6)
+ };
+ }
+ };
+ StackTraceInfo sti1 = db.getStackTraceInfo(t1);
+ StackTraceInfo sti2 = db.getStackTraceInfo(t2);
+ assertEquals(sti1.getHash(), sti2.getHash());
}
}
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/WeightedAverageTest.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/WeightedAverageTest.java
new file mode 100644
index 0000000..7c4e99b
--- /dev/null
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/WeightedAverageTest.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.juneau.utils;
+
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+public class WeightedAverageTest {
+
+ @Test
+ public void testEmpty() {
+ WeightedAverage w = new WeightedAverage();
+ assertEquals(0f, w.getValue(), 0.01);
+ }
+
+ @Test
+ public void testBasic() {
+ WeightedAverage w = new WeightedAverage();
+ w.add(0,100).add(1,1).add(1,2).add(1,3).add(0,100);
+ assertEquals(2f, w.getValue(), 0.01);
+ }
+
+ @Test
+ public void testBasicWithNullValue() {
+ WeightedAverage w = new WeightedAverage();
+ w.add(1,1).add(1,null).add(1,3);
+ assertEquals(2f, w.getValue(), 0.01);
+ }
+
+ @Test
+ public void testDifferingWeights() {
+ WeightedAverage w = new WeightedAverage();
+ w.add(10,1).add(20,3);
+ assertEquals(2.33f, w.getValue(), 0.01);
+ }
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
index 5945ada..71cf6a1 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
@@ -404,7 +404,7 @@
// Check for missing properties.
for (String fp : fixedBeanProps)
if (! normalProps.containsKey(fp))
- throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Bean(bpi=X) annotation but was not found on the class definition.", fp);
+ throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Bean(bpi=X) annotation of class ''{1}'' but was not found on the class definition.", fp, ci.getSimpleName());
// Mark constructor arg properties.
for (String fp : constructorArgs) {
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
index 5f5fdb0..f289ca2 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
@@ -45,7 +45,7 @@
* properties={
* <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_title</jsf>, value=<js>"$L{title}"</js>),
* <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_description</jsf>, value=<js>"$L{description}"</js>),
- * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_navlinks</jsf>, value=<js>"{options:'?method=OPTIONS',doc:'doc'}"</js>)
+ * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_navlinks</jsf>, value=<js>"{options:'servlet:/?method=OPTIONS',doc:'doc'}"</js>)
* }
* )
* <jk>public class</jk> AddressBookResource <jk>extends</jk> BasicRestServletJena {
@@ -60,7 +60,7 @@
* description=<js>"$L{description}"</js>,
* htmldoc=<ja>@HtmlDoc</ja>(
* navlinks={
- * <js>"options: ?method=OPTIONS"</js>,
+ * <js>"options: servlet:/?method=OPTIONS"</js>,
* <js>"doc: doc"</js>
* }
* )
@@ -333,7 +333,7 @@
* <ja>@Rest</ja>(
* properties={
* <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_navlinks</jsf>,
- * value=<js>"['options: ?method=OPTIONS', 'doc: doc']"</js>)
+ * value=<js>"['options: servlet:/?method=OPTIONS', 'doc: doc']"</js>)
* }
* )
* <jk>public class</jk> AddressBookResource <jk>extends</jk> BasicRestServletJena {
@@ -345,7 +345,7 @@
* <ja>@Rest</ja>(
* htmldoc=@HtmlDoc(
* navlinks={
- * <js>"options: ?method=OPTIONS"</js>,
+ * <js>"options: servlet:/?method=OPTIONS"</js>,
* <js>"doc: doc"</js>
* }
* )
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerBuilder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerBuilder.java
index 1d473b0..eea5aac 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerBuilder.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerBuilder.java
@@ -222,7 +222,8 @@
* <p>
* <ja>@HtmlDocConfig</ja>(
* navlinks={
- * <js>"options: ?method=OPTIONS"</js>,
+ * <js>"options: servlet:/?method=OPTIONS"</js>,
+ * <js>"stats: servlet:/stats"</js>,
* <js>"doc: doc"</js>
* }
* )
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/annotation/HtmlDocConfig.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/annotation/HtmlDocConfig.java
index beee09a..81cb4e3 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/annotation/HtmlDocConfig.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/annotation/HtmlDocConfig.java
@@ -294,7 +294,8 @@
* <p class='bcode w800'>
* <ja>@HtmlDocConfig</ja>(
* navlinks={
- * <js>"options: ?method=OPTIONS"</js>,
+ * <js>"options: servlet:/?method=OPTIONS"</js>,
+ * <js>"stats: servlet:/stats"</js>,
* <js>"doc: doc"</js>
* }
* )
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java
index 08d84f1..fb6d494 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java
@@ -740,13 +740,9 @@
}
/**
- * Returns the simple name of the underlying class.
+ * Returns the simple name of the underlying method.
*
- * <p>
- * Returns either {@link Class#getSimpleName()} or {@link Type#getTypeName()} depending on whether
- * this is a class or type.
- *
- * @return The simple name of the underlying class;
+ * @return The simple name of the underlying method;
*/
public final String getSimpleName() {
return isConstructor ? e.getDeclaringClass().getSimpleName() : e.getName();
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/MethodExecStats.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/MethodExecStats.java
new file mode 100644
index 0000000..831181e
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/MethodExecStats.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.juneau.utils;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.marshall.*;
+
+/**
+ * Basic timing information.
+ *
+ * Keeps track of number of starts/finishes on tasks and keeps an average run time.
+ */
+@Bean(bpi="method,runs,running,errors,avgTime,totalTime,exceptions")
+public class MethodExecStats implements Comparable<MethodExecStats> {
+ private String method;
+ private WeightedAverage avgTime = new WeightedAverage();
+
+ private AtomicInteger
+ starts = new AtomicInteger(),
+ finishes = new AtomicInteger(),
+ errors = new AtomicInteger();
+
+ private AtomicLong
+ totalTime = new AtomicLong();
+
+ private StackTraceDatabase stackTraceDb;
+
+ /**
+ * Constructor.
+ *
+ * @param method Arbitrary label. Should be kept to less than 50 characters.
+ * @param stackTraceStopClass Don't calculate stack traces when this class is encountered.
+ */
+ public MethodExecStats(Method method, Class<?> stackTraceStopClass) {
+ this.method = method.getDeclaringClass().getSimpleName() + "." + method.getName();
+ this.stackTraceDb = new StackTraceDatabase(-1, stackTraceStopClass);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param method Arbitrary label. Should be kept to less than 50 characters.
+ */
+ public MethodExecStats(Method method) {
+ this(method, MethodInvoker.class);
+ }
+
+ /**
+ * Call when task is started.
+ */
+ public void started() {
+ starts.incrementAndGet();
+ }
+
+ /**
+ * Call when task is finished.
+ * @param time The execution time of the task.
+ */
+ public void finished(long time) {
+ finishes.incrementAndGet();
+ totalTime.addAndGet(time);
+ avgTime.add(1, time);
+ }
+
+ /**
+ * Call when an error occurs.
+ * @param e The exception thrown. Can be <jk>null</jk>.
+ */
+ public void error(Throwable e) {
+ errors.incrementAndGet();
+ stackTraceDb.add(e);
+ }
+
+ /**
+ * Returns the method name of these stats.
+ *
+ * @return The method name of these stats.
+ */
+ public String getMethod() {
+ return method;
+ }
+
+ /**
+ * Returns the number of times the {@link #started()} method was called.
+ *
+ * @return The number of times the {@link #started()} method was called.
+ */
+ public int getRuns() {
+ return starts.get();
+ }
+
+ /**
+ * Returns the number currently running method invocations.
+ *
+ * @return The number of currently running method invocations.
+ */
+ public int getRunning() {
+ return starts.get() - finishes.get();
+ }
+
+ /**
+ * Returns the number of times the {@link #error(Throwable)} method was called.
+ *
+ * @return The number of times the {@link #error(Throwable)} method was called.
+ */
+ public int getErrors() {
+ return errors.get();
+ }
+
+ /**
+ * Returns the average execution time.
+ *
+ * @return The average execution time in milliseconds.
+ */
+ public int getAvgTime() {
+ return (int)avgTime.getValue();
+ }
+
+ /**
+ * Returns the total execution time.
+ *
+ * @return The total execution time in milliseconds.
+ */
+ public long getTotalTime() {
+ return totalTime.get();
+ }
+
+ /**
+ * Returns information on all stack traces of all exceptions encountered.
+ *
+ * @return Information on all stack traces of all exceptions encountered.
+ */
+ public List<StackTraceInfo> getExceptions() {
+ return stackTraceDb.getClonedStackTraceInfos();
+ }
+
+ @Override /* Object */
+ public String toString() {
+ return SimpleJson.DEFAULT.toString(this);
+ }
+
+ @Override /* Comparable */
+ public int compareTo(MethodExecStats o) {
+ return Long.compare(o.getTotalTime(), getTotalTime());
+ }
+}
\ No newline at end of file
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/MethodInvoker.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/MethodInvoker.java
new file mode 100644
index 0000000..4290f6a
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/MethodInvoker.java
@@ -0,0 +1,87 @@
+// ***************************************************************************************************************************
+// * 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.juneau.utils;
+
+import java.lang.reflect.*;
+
+/**
+ * A wrapper around a {@link Method#invoke(Object, Object...)} method that allows for basic instrumentation.
+ */
+public class MethodInvoker {
+ private final Method m;
+ private final MethodExecStats stats;
+
+ /**
+ * Constructor.
+ *
+ * @param m The method being wrapped.
+ * @param stats The instrumentor.
+ */
+ public MethodInvoker(Method m, MethodExecStats stats) {
+ this.m = m;
+ this.stats = stats;
+ }
+
+ /**
+ * Returns the inner method.
+ *
+ * @return The inner method.
+ */
+ public Method inner() {
+ return m;
+ }
+
+ /**
+ * Invokes the underlying method.
+ *
+ * @param o The object the underlying method is invoked from.
+ * @param args The arguments used for the method call.
+ * @return The result of dispatching the method represented by this object on {@code obj} with parameters {@code args}
+ * @throws IllegalAccessException Thrown from underlying method.
+ * @throws IllegalArgumentException Thrown from underlying method.
+ * @throws InvocationTargetException Thrown from underlying method.
+ */
+ public Object invoke(Object o, Object...args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+ long startTime = System.currentTimeMillis();
+ stats.started();
+ try {
+ return m.invoke(o, args);
+ } catch (IllegalAccessException|IllegalArgumentException e) {
+ stats.error(e);
+ throw e;
+ } catch (InvocationTargetException e) {
+ stats.error(e.getTargetException());
+ throw e;
+ } finally {
+ stats.finished(System.currentTimeMillis() - startTime);
+ }
+ }
+
+ /**
+ * Convenience method for calling <c>inner().getDeclaringClass()</c>
+ *
+ * @return The declaring class of the method.
+ */
+ public Class<?> getDeclaringClass() {
+ return m.getDeclaringClass();
+ }
+
+ /**
+ * Convenience method for calling <c>inner().getName()</c>
+ *
+ * @return The name of the method.
+ */
+ public String getName() {
+ return m.getName();
+ }
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceDatabase.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceDatabase.java
index 0f8891c..2e54e22 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceDatabase.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceDatabase.java
@@ -12,7 +12,11 @@
// ***************************************************************************************************************************
package org.apache.juneau.utils;
+import static org.apache.juneau.utils.StackTraceUtils.*;
+
+import java.util.*;
import java.util.concurrent.*;
+import java.util.stream.*;
/**
* An in-memory cache of stack traces.
@@ -24,56 +28,49 @@
private final ConcurrentHashMap<Integer,StackTraceInfo> db = new ConcurrentHashMap<>();
private final String stopClass;
+ private final long cacheTimeout;
/**
* Constructor.
*/
public StackTraceDatabase() {
- this.stopClass = null;
+ this(-1, null);
}
/**
* Constructor.
*
- * @param stopClass When this class is encountered in a stack trace, stop calculating the hash.
+ * @param cacheTimeout
+ * The amount of time in milliseconds to cache stack trace info in this database before discarding.
+ * <br>If <c>-1</c>, never discard.
+ * @param stopClass
+ * When this class is encountered in a stack trace, stop calculating the hash.
+ * <br>Can be <jk>null</jk>.
*/
- public StackTraceDatabase(Class<?> stopClass) {
+ public StackTraceDatabase(long cacheTimeout, Class<?> stopClass) {
this.stopClass = stopClass == null ? "" : stopClass.getName();
+ this.cacheTimeout = cacheTimeout;
+ }
+
+ /**
+ * Adds the specified throwable to this database.
+ *
+ * @param e The exception to add.
+ * @return This object (for method chaining).
+ */
+ public StackTraceDatabase add(Throwable e) {
+ find(e).increment();
+ return this;
}
/**
* Retrieves the stack trace information for the specified exception.
*
* @param e The exception.
- * @param timeout The timeout in milliseconds to cache the hash for this stack trace.
- * @return The stack trace info, never <jk>null</jk>.
+ * @return A clone of the stack trace info, never <jk>null</jk>.
*/
- public StackTraceInfo getStackTraceInfo(Throwable e, int timeout) {
- int hash = hash(e);
- StackTraceInfo stc = db.get(hash);
- if (stc != null && stc.timeout > System.currentTimeMillis()) {
- stc.incrementAndClone();
- return stc.clone();
- }
- synchronized (db) {
- stc = new StackTraceInfo(timeout, hash);
- db.put(hash, stc);
- return stc.clone();
- }
- }
-
- private int hash(Throwable t) {
- int i = 0;
- while (t != null) {
- for (StackTraceElement e : t.getStackTrace()) {
- if (e.getClassName().equals(stopClass))
- break;
- if (e.getClassName().indexOf('$') == -1)
- i ^= e.hashCode();
- }
- t = t.getCause();
- }
- return i;
+ public StackTraceInfo getStackTraceInfo(Throwable e) {
+ return find(e).clone();
}
/**
@@ -82,4 +79,31 @@
public void reset() {
db.clear();
}
+
+ /**
+ * Returns the list of all stack traces in this database.
+ *
+ * @return The list of all stack traces in this database, cloned and sorted by count descending.
+ */
+ public List<StackTraceInfo> getClonedStackTraceInfos() {
+ return db.values().stream().map(x -> x.clone()).sorted().collect(Collectors.toList());
+ }
+
+ private StackTraceInfo find(Throwable e) {
+ int hash = hash(e, stopClass);
+ StackTraceInfo stc = db.get(hash);
+ long time = System.currentTimeMillis();
+
+ if (stc != null && stc.timeout > time) {
+ return stc;
+ }
+
+ String n = e == null ? null : e.getClass().getSimpleName();
+ long t = cacheTimeout == -1 ? Long.MAX_VALUE : time + cacheTimeout;
+ stc = new StackTraceInfo(n, t, hash);
+
+ db.put(hash, stc);
+
+ return db.get(hash);
+ }
}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceInfo.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceInfo.java
index 9d14436..6c42b27 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceInfo.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceInfo.java
@@ -14,29 +14,37 @@
import java.util.concurrent.atomic.*;
+import org.apache.juneau.annotation.*;
+
/**
* Represents an entry in {@link StackTraceDatabase}.
*/
-public class StackTraceInfo {
- AtomicInteger count;
- long timeout;
- String hash;
+@Bean(bpi="exception,hash,count")
+public class StackTraceInfo implements Comparable<StackTraceInfo> {
+ final AtomicInteger count;
+ final long timeout;
+ final String hash;
+ final String exception;
- StackTraceInfo(long timeout, int hash) {
- this.count = new AtomicInteger(1);
- this.timeout = System.currentTimeMillis() + timeout;
+ /**
+ * Constructor.
+ *
+ * @param exception The simple name of the exception class. Can be <jk>null</jk>.
+ * @param timeout The time in UTC milliseconds at which point this stack trace info should be discarded from caches.
+ * @param hash The hash id of this stack trace.
+ */
+ public StackTraceInfo(String exception, long timeout, int hash) {
+ this.count = new AtomicInteger(0);
+ this.timeout = timeout;
this.hash = Integer.toHexString(hash);
+ this.exception = exception;
}
- private StackTraceInfo(int count, long timeout, String hash) {
+ private StackTraceInfo(String exception, int count, long timeout, String hash) {
this.count = new AtomicInteger(count);
this.timeout = timeout;
this.hash = hash;
- }
-
- @Override
- public StackTraceInfo clone() {
- return new StackTraceInfo(count.intValue(), timeout, hash);
+ this.exception = exception;
}
/**
@@ -57,7 +65,32 @@
return hash;
}
- StackTraceInfo incrementAndClone() {
- return new StackTraceInfo(count.incrementAndGet(), timeout, hash);
+ /**
+ * Returns the simple class name of the exception.
+ *
+ * @return The simple class name of the exception, or <jk>null</jk> if not specified.
+ */
+ public String getException() {
+ return exception;
+ }
+
+ /**
+ * Increments the occurrence count of this exception.
+ *
+ * @return This object (for method chaining).
+ */
+ public StackTraceInfo increment() {
+ count.incrementAndGet();
+ return this;
+ }
+
+ @Override /* Comparable */
+ public int compareTo(StackTraceInfo o) {
+ return Integer.compare(o.getCount(), getCount());
+ }
+
+ @Override /* Object */
+ public StackTraceInfo clone() {
+ return new StackTraceInfo(exception, count.intValue(), timeout, hash);
}
}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceUtils.java
new file mode 100644
index 0000000..62f25b2
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StackTraceUtils.java
@@ -0,0 +1,40 @@
+// ***************************************************************************************************************************
+// * 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.juneau.utils;
+
+/**
+ * Stack trace utility methods.
+ */
+public class StackTraceUtils {
+
+ /**
+ * Calculates a 16-bit hash for the specified throwable based on it's stack trace.
+ *
+ * @param t The throwable to calculate the stack trace on.
+ * @param stopClass Optional stop class on which to stop calculation of a stack trace beyond when found.
+ * @return A calculated hash.
+ */
+ public static int hash(Throwable t, String stopClass) {
+ int i = 0;
+ while (t != null) {
+ for (StackTraceElement e : t.getStackTrace()) {
+ if (e.getClassName().equals(stopClass))
+ break;
+ if (e.getClassName().indexOf('$') == -1)
+ i ^= e.hashCode();
+ }
+ t = t.getCause();
+ }
+ return i;
+ }
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/WeightedAverage.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/WeightedAverage.java
new file mode 100644
index 0000000..7355d01
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/WeightedAverage.java
@@ -0,0 +1,52 @@
+// ***************************************************************************************************************************
+// * 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.juneau.utils;
+
+/**
+ * A simple weighted average of numbers.
+ */
+public class WeightedAverage {
+ private Float value = 0f;
+ private int weight = 0;
+
+ /**
+ * Add a number with a weight to this average.
+ *
+ * @param w The weight of the new value.
+ * @param v The new value.
+ * @return This object (for method chaining).
+ */
+ public WeightedAverage add(int w, Number v) {
+ if (v != null) {
+ try {
+ float w1 = weight, w2 = w;
+ weight = Math.addExact(weight, w);
+ if (weight != 0) {
+ value = (value * (w1/weight)) + (v.floatValue() * (w2/weight));
+ }
+ } catch (ArithmeticException ae) {
+ throw new ArithmeticException("Weight overflow.");
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Returns the weighted average of all numbers.
+ *
+ * @return The weighted average of all numbers.
+ */
+ public float getValue() {
+ return value;
+ }
+}
diff --git a/juneau-doc/docs/ReleaseNotes/8.1.3.html b/juneau-doc/docs/ReleaseNotes/8.1.3.html
index 0426a00..836424b 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.3.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.3.html
@@ -63,7 +63,8 @@
<li><ja>@RestMethod(attrs)</ja> --> {@link oajr.annotation.RestMethod#reqAttrs() @RestMethod(reqAttrs)}
<li><ja>@RestMethod(defaultRequestHeaders)</ja> --> {@link oajr.annotation.RestMethod#reqHeaders() @RestMethod(reqHeaders)}
</ul>
-
+ <li>
+ New auto-generated REST method execution statistics: {@doc juneau-rest-server.ExecutionStatistics}
</ul>
<h5 class='topic w800'>juneau-rest-client</h5>
diff --git a/juneau-doc/docs/Topics/06.juneau-rest-server/30.ExecutionStatistics.html b/juneau-doc/docs/Topics/06.juneau-rest-server/30.ExecutionStatistics.html
new file mode 100644
index 0000000..4d47ad3
--- /dev/null
+++ b/juneau-doc/docs/Topics/06.juneau-rest-server/30.ExecutionStatistics.html
@@ -0,0 +1,45 @@
+<!--
+/***************************************************************************************************************************
+ * 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.
+ ***************************************************************************************************************************/
+ -->
+
+{new] REST method execution statistics
+
+<p>
+ <ja>@RestResource</ja> annotated classes get automated timing and error statistic information for all <ja>@RestMethod</ja>
+ and <ja>@RestHook</ja> annotated methods on the class.
+</p>
+<p>
+ The statistics are made available via the subpath <c>/stats</c> as shown below in the <c>HelloWorldResource</c> example:
+</p>
+<img class='bordered w900' src='doc-files/juneau-rest-server.ExecutionStatistics.1.png'>
+<p>
+ The default REST configuration provides a link to the stats in the navlinks section of the page:
+</p>
+<img class='bordered w900' src='doc-files/juneau-rest-server.ExecutionStatistics.2.png'>
+<p>
+ The exception hash shown is the same hash that is shown in the log file and provides a quick way of locating
+ the exception in the logs.
+</p>
+<p>
+ Programmatic access to the statistics are provided via the following methods:
+</p>
+<ul class='javatree'>
+ <li class='jc'>{@link oajr.RestContext}
+ <ul>
+ <li class='jm'>{@link oajr.RestContext#getStats() getStats()}
+ <li class='jm'>{@link oajr.RestContext#getMethodExecStats() getMethodExecStats()}
+ <li class='jm'>{@link oajr.RestContext#getMethodExecStatsReport() getMethodExecStatsReport()}
+ </ul>
+</ul>
+
\ No newline at end of file
diff --git a/juneau-doc/docs/Topics/06.juneau-rest-server/doc-files/juneau-rest-server.ExecutionStatistics.1.png b/juneau-doc/docs/Topics/06.juneau-rest-server/doc-files/juneau-rest-server.ExecutionStatistics.1.png
new file mode 100644
index 0000000..7efbb1e
--- /dev/null
+++ b/juneau-doc/docs/Topics/06.juneau-rest-server/doc-files/juneau-rest-server.ExecutionStatistics.1.png
Binary files differ
diff --git a/juneau-doc/docs/Topics/06.juneau-rest-server/doc-files/juneau-rest-server.ExeuctionStatistics.2.png b/juneau-doc/docs/Topics/06.juneau-rest-server/doc-files/juneau-rest-server.ExeuctionStatistics.2.png
new file mode 100644
index 0000000..3e195b1
--- /dev/null
+++ b/juneau-doc/docs/Topics/06.juneau-rest-server/doc-files/juneau-rest-server.ExeuctionStatistics.2.png
Binary files differ
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java
index 82ad529..9d9658b 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java
@@ -16,6 +16,7 @@
import org.apache.juneau.dto.swagger.*;
import org.apache.juneau.html.annotation.*;
+import org.apache.juneau.http.exception.*;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.annotation.*;
@@ -51,11 +52,26 @@
return "Hello world!";
}
- @Override
+ /**
+ * Make a request to /helloWorld/badRequest to trigger a 400 Bad Request.
+ *
+ * @throws BadRequest A bad request.
+ */
+ @RestMethod
+ public void getBadRequest() throws BadRequest {
+ throw new BadRequest("example");
+ }
+
+ @Override /* BasicRestConfig */
public Swagger getOptions(RestRequest req) {
return req.getSwagger();
}
- @Override
+ @Override /* BasicRestConfig */
public void error() {}
+
+ @Override /* BasicRestConfig */
+ public RestContextStats getStats(RestRequest req) {
+ return req.getContext().getStats();
+ }
}
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java
index 2f290e1..fecced1 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java
@@ -52,6 +52,7 @@
navlinks={
"up: request:/..",
"options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats",
"$W{ContentTypeMenuItem}",
"$W{ThemeMenuItem}",
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RootResources.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RootResources.java
index 59152f4..3e6231d 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RootResources.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RootResources.java
@@ -45,7 +45,8 @@
ThemeMenuItem.class
},
navlinks={
- "options: ?method=OPTIONS",
+ "options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats",
"$W{ContentTypeMenuItem}",
"$W{ThemeMenuItem}",
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/AtomFeedResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/AtomFeedResource.java
index 5e0a80d..08fb8df 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/AtomFeedResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/AtomFeedResource.java
@@ -57,6 +57,7 @@
navlinks={
"up: request:/..",
"options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats",
"$W{ContentTypeMenuItem}",
"$W{ThemeMenuItem}",
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/dto/$R{servletClassSimple}.java"
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/DtoExamples.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/DtoExamples.java
index d282a34..26b7c79 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/DtoExamples.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/DtoExamples.java
@@ -41,7 +41,8 @@
},
navlinks={
"up: request:/..",
- "options: ?method=OPTIONS",
+ "options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats",
"$W{ContentTypeMenuItem}",
"$W{ThemeMenuItem}",
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/dto/$R{servletClassSimple}.java"
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/JsonSchemaResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/JsonSchemaResource.java
index d1eb728..6bd236c 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/JsonSchemaResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/dto/JsonSchemaResource.java
@@ -50,6 +50,7 @@
navlinks={
"up: request:/..",
"options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats",
"$W{ContentTypeMenuItem}",
"$W{ThemeMenuItem}",
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/dto/$R{servletClassSimple}.java"
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
index 246798c..00e95d9 100755
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
+++ b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
@@ -42,6 +42,7 @@
navlinks={
"up: request:/..",
"options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats",
"edit: servlet:/edit"
}
)
diff --git a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/resources/DebugResource.java b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/resources/DebugResource.java
index 37021a3..9a260a3 100644
--- a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/resources/DebugResource.java
+++ b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/resources/DebugResource.java
@@ -36,7 +36,8 @@
navlinks={
"up: request:/..",
"jetty-thread-dump: servlet:/jetty/dump?method=POST",
- "options: servlet:/?method=OPTIONS"
+ "options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats"
}
)
@SuppressWarnings("javadoc")
diff --git a/juneau-microservice/juneau-my-jetty-microservice/src/main/java/org/apache/juneau/microservice/jetty/template/RootResources.java b/juneau-microservice/juneau-my-jetty-microservice/src/main/java/org/apache/juneau/microservice/jetty/template/RootResources.java
index a8a5b32..bded6ec 100755
--- a/juneau-microservice/juneau-my-jetty-microservice/src/main/java/org/apache/juneau/microservice/jetty/template/RootResources.java
+++ b/juneau-microservice/juneau-my-jetty-microservice/src/main/java/org/apache/juneau/microservice/jetty/template/RootResources.java
@@ -41,7 +41,8 @@
ThemeMenuItem.class
},
navlinks={
- "options: servlet:/?method=OPTIONS"
+ "options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats"
}
)
public class RootResources extends BasicRestServletGroup {
diff --git a/juneau-microservice/juneau-my-springboot-microservice/src/main/java/org/apache/juneau/microservice/springboot/template/RootResources.java b/juneau-microservice/juneau-my-springboot-microservice/src/main/java/org/apache/juneau/microservice/springboot/template/RootResources.java
index 4ee7677..56db9ad 100755
--- a/juneau-microservice/juneau-my-springboot-microservice/src/main/java/org/apache/juneau/microservice/springboot/template/RootResources.java
+++ b/juneau-microservice/juneau-my-springboot-microservice/src/main/java/org/apache/juneau/microservice/springboot/template/RootResources.java
@@ -39,7 +39,8 @@
ThemeMenuItem.class
},
navlinks={
- "options: servlet:/?method=OPTIONS"
+ "options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats"
}
)
public class RootResources extends BasicRestServletGroup {
diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestCallLoggerTest.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestCallLoggerTest.java
index 21e5d37..0f87f83 100644
--- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestCallLoggerTest.java
+++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestCallLoggerTest.java
@@ -21,6 +21,7 @@
import org.apache.juneau.internal.*;
import org.apache.juneau.rest.mock2.*;
+import org.apache.juneau.utils.*;
import org.junit.*;
import org.junit.runners.*;
@@ -74,8 +75,12 @@
return RestCallLoggerConfig.create().parent(config).build();
}
+ private BasicRestCallLogger logger(Logger l, StackTraceDatabase std) {
+ return new BasicRestCallLogger(l, std);
+ }
+
private BasicRestCallLogger logger(Logger l) {
- return new BasicRestCallLogger(null, l);
+ return new BasicRestCallLogger(l, new StackTraceDatabase());
}
private RestCallLoggerRule.Builder rule() {
@@ -349,12 +354,11 @@
rule().codes("*").req(SHORT).res(SHORT).build()
)
.useStackTraceHashing()
- .stackTraceHashingTimeout(100000)
.build();
RestCallLoggerConfig lcw = wrapped(lc);
TestLogger tc = new TestLogger();
- BasicRestCallLogger cl = logger(tc).resetStackTraces();
+ BasicRestCallLogger cl = logger(tc, new StackTraceDatabase(100000, null)).resetStackTraces();
Exception e = new StringIndexOutOfBoundsException();
MockServletRequest req = req().uri("/foo").query("bar", "baz").attribute("Exception", e);
MockServletResponse res = res(200);
@@ -380,27 +384,26 @@
rule().codes("*").req(SHORT).res(SHORT).build()
)
.useStackTraceHashing()
- .stackTraceHashingTimeout(-1)
.build();
RestCallLoggerConfig lcw = wrapped(lc);
TestLogger tc = new TestLogger();
- BasicRestCallLogger cl = logger(tc).resetStackTraces();
+ BasicRestCallLogger cl = logger(tc, new StackTraceDatabase(-2, null)).resetStackTraces();
Exception e = new StringIndexOutOfBoundsException();
MockServletRequest req = req().uri("/foo").query("bar", "baz").attribute("Exception", e);
MockServletResponse res = res(200);
cl.log(lc, req, res);
- tc.check(INFO, "[200,*.1] HTTP GET /foo", true);
+ tc.check(INFO, "[200,*.0] HTTP GET /foo", true);
cl.log(lc, req, res);
- tc.check(INFO, "[200,*.1] HTTP GET /foo", true);
+ tc.check(INFO, "[200,*.0] HTTP GET /foo", true);
cl.resetStackTraces();
cl.log(lcw, req, res);
- tc.check(INFO, "[200,*.1] HTTP GET /foo", true);
+ tc.check(INFO, "[200,*.0] HTTP GET /foo", true);
cl.log(lcw, req, res);
- tc.check(INFO, "[200,*.1] HTTP GET /foo", true);
+ tc.check(INFO, "[200,*.0] HTTP GET /foo", true);
}
//------------------------------------------------------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRest.java
index 842cfb3..f7f5c7f 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRest.java
@@ -38,7 +38,8 @@
// Basic page navigation links.
navlinks={
"up: request:/..",
- "options: servlet:/?method=OPTIONS"
+ "options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats"
}
)
public abstract class BasicRest implements BasicRestConfig {
@@ -82,6 +83,18 @@
@Override /* BasicRestConfig */
public void error() {}
+ /**
+ * [GET /stats] - Timing statistics.
+ *
+ * <p>
+ * Timing statistics for method invocations on this resource.
+ *
+ * @return A collection of timing statistics for each annotated method on this resource.
+ */
+ @Override /* BasicRestConfig */
+ public RestContextStats getStats(RestRequest req) {
+ return req.getContext().getStats();
+ }
//-----------------------------------------------------------------------------------------------------------------
// Context methods.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java
index b73828d..d7fa58b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java
@@ -38,10 +38,10 @@
*/
public class BasicRestCallLogger implements RestCallLogger {
- private final StackTraceDatabase stackTraceDb = new StackTraceDatabase(RestMethodContext.class);
-
+ private final String loggerName;
private final Logger logger;
private final RestContext context;
+ private final StackTraceDatabase stackTraceDb;
/**
* Constructor.
@@ -50,18 +50,22 @@
*/
public BasicRestCallLogger(RestContext context) {
this.context = context;
+ this.loggerName = context.getResource().getClass().getName();
this.logger = Logger.getLogger(getLoggerName());
+ this.stackTraceDb = context.getStackTraceDb();
}
/**
* Constructor.
*
- * @param context The context of the resource object.
* @param logger The logger to use for logging.
+ * @param stackTraceDb The stack trace database for maintaining stack traces.
*/
- public BasicRestCallLogger(RestContext context, Logger logger) {
- this.context = context;
+ protected BasicRestCallLogger(Logger logger, StackTraceDatabase stackTraceDb) {
+ this.context = null;
+ this.loggerName = getClass().getName();
this.logger = logger;
+ this.stackTraceDb = stackTraceDb;
}
/**
@@ -76,7 +80,7 @@
* @return The logger name.
*/
protected String getLoggerName() {
- return context == null ? getClass().getName() : context.getResource().getClass().getName();
+ return loggerName;
}
/**
@@ -243,6 +247,7 @@
private StackTraceInfo getStackTraceInfo(RestCallLoggerConfig config, Throwable e) {
if (e == null || ! config.isUseStackTraceHashing())
return null;
- return stackTraceDb.getStackTraceInfo(e, config.getStackTraceHashingTimeout());
+ stackTraceDb.add(e);
+ return stackTraceDb.getStackTraceInfo(e);
}
}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
index fa613ee..6d0f9aa 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
@@ -104,7 +104,8 @@
// Basic page navigation links.
navlinks={
"up: request:/..",
- "options: servlet:/?method=OPTIONS"
+ "options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats"
},
// Default stylesheet to use for the page.
@@ -123,6 +124,26 @@
// By default, table cell contents should not wrap.
nowrap="true"
)
+@JsonSchemaConfig(
+ // Add descriptions to the following types when not specified:
+ addDescriptionsTo="bean,collection,array,map,enum",
+ // Add x-example to the following types:
+ addExamplesTo="bean,collection,array,map",
+ // Don't generate schema information on the Swagger bean itself or HTML beans.
+ ignoreTypes="Swagger,org.apache.juneau.dto.html5.*",
+ // Use $ref references for bean definitions to reduce duplication in Swagger.
+ useBeanDefs="true"
+)
+@BeanConfig(
+ // When parsing generated beans, ignore unknown properties that may only exist as getters and not setters.
+ ignoreUnknownBeanProperties="true",
+ // POJO swaps to apply to all serializers/parsers on this method.
+ pojoSwaps={
+ // Use the SwaggerUI swap when rendering Swagger beans.
+ // This is a per-media-type swap that only applies to text/html requests.
+ SwaggerUI.class
+ }
+)
public interface BasicRestConfig {
/**
@@ -135,26 +156,6 @@
summary="Swagger documentation",
description="Swagger documentation for this resource."
)
- @JsonSchemaConfig(
- // Add descriptions to the following types when not specified:
- addDescriptionsTo="bean,collection,array,map,enum",
- // Add x-example to the following types:
- addExamplesTo="bean,collection,array,map",
- // Don't generate schema information on the Swagger bean itself or HTML beans.
- ignoreTypes="Swagger,org.apache.juneau.dto.html5.*",
- // Use $ref references for bean definitions to reduce duplication in Swagger.
- useBeanDefs="true"
- )
- @BeanConfig(
- // When parsing generated beans, ignore unknown properties that may only exist as getters and not setters.
- ignoreUnknownBeanProperties="true",
- // POJO swaps to apply to all serializers/parsers on this method.
- pojoSwaps={
- // Use the SwaggerUI swap when rendering Swagger beans.
- // This is a per-media-type swap that only applies to text/html requests.
- SwaggerUI.class
- }
- )
@HtmlDocConfig(
// Should override config annotations defined on class.
rank=10,
@@ -184,4 +185,30 @@
description="An error occurred during handling of the request."
)
public void error();
+
+ /**
+ * [GET /stats] - Timing statistics.
+ *
+ * <p>
+ * Timing statistics for method invocations on this resource.
+ *
+ * @param req The HTTP request.
+ * @return A collection of timing statistics for each annotated method on this resource.
+ */
+ @RestMethod(name=GET, path="/stats",
+ summary="Timing statistics",
+ description="Timing statistics for method invocations on this resource."
+ )
+ @HtmlDocConfig(
+ // Should override config annotations defined on class.
+ rank=10,
+ // Override the nav links for the swagger page.
+ navlinks={
+ "back: servlet:/",
+ "json: servlet:/stats?Accept=text/json&plainText=true"
+ },
+ // Never show aside contents of page inherited from class.
+ aside="NONE"
+ )
+ public RestContextStats getStats(RestRequest req);
}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java
index 5163601..66871cf 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java
@@ -120,7 +120,7 @@
return swagger;
// Wasn't cached...need to create one.
- swagger = new SwaggerGenerator(req).getSwagger();
+ swagger = new SwaggerGenerator(req.getContext(), req.getVarResolverSession(), req.getLocale()).getSwagger();
swaggers.get(locale).put(hashCode, swagger);
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServlet.java
index ef90c95..af76bae 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServlet.java
@@ -165,7 +165,8 @@
// Basic page navigation links.
navlinks={
"up: request:/..",
- "options: servlet:/?method=OPTIONS"
+ "options: servlet:/?method=OPTIONS",
+ "stats: servlet:/stats"
}
)
public abstract class BasicRestServlet extends RestServlet implements BasicRestConfig {
@@ -196,4 +197,17 @@
*/
@Override /* BasicRestConfig */
public void error() {}
+
+ /**
+ * [GET /stats] - Timing statistics.
+ *
+ * <p>
+ * Timing statistics for method invocations on this resource.
+ *
+ * @return A collection of timing statistics for each annotated method on this resource.
+ */
+ @Override /* BasicRestConfig */
+ public RestContextStats getStats(RestRequest req) {
+ return req.getContext().getStats();
+ }
}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index fa7b931..cbf42b1 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -23,9 +23,11 @@
import java.io.*;
import java.lang.reflect.Method;
import java.nio.charset.*;
+import java.time.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
+import java.util.stream.*;
import javax.activation.*;
import javax.servlet.*;
@@ -3447,6 +3449,7 @@
@SuppressWarnings("deprecation") private final RestLogger logger;
private final RestCallLogger callLogger;
private final RestCallLoggerConfig callLoggerConfig;
+ private final StackTraceDatabase stackTraceDb;
private final RestCallHandler callHandler;
private final RestInfoProvider infoProvider;
private final HttpException initException;
@@ -3454,9 +3457,11 @@
private final RestResourceResolver resourceResolver;
private final UriResolution uriResolution;
private final UriRelativity uriRelativity;
+ private final ConcurrentHashMap<String,MethodExecStats> methodExecStats = new ConcurrentHashMap<>();
+ private final Instant startTime;
// Lifecycle methods
- private final Method[]
+ private final MethodInvoker[]
postInitMethods,
postInitChildFirstMethods,
preCallMethods,
@@ -3517,6 +3522,8 @@
RestContext(RestContextBuilder builder) throws Exception {
super(builder.getPropertyStore());
+ startTime = Instant.now();
+
HttpException _initException = null;
try {
@@ -3585,7 +3592,6 @@
staticFileResponseHeaders = getMapProperty(REST_staticFileResponseHeaders, Object.class);
logger = getInstanceProperty(REST_logger, resource, RestLogger.class, NoOpRestLogger.class, resourceResolver, this);
- callLogger = getInstanceProperty(REST_callLogger, resource, RestCallLogger.class, BasicRestCallLogger.class, resourceResolver, this);
if (debug == Enablement.TRUE) {
this.callLoggerConfig = RestCallLoggerConfig.DEFAULT_DEBUG;
@@ -3599,6 +3605,10 @@
this.callLoggerConfig = RestCallLoggerConfig.DEFAULT;
}
+ this.stackTraceDb = new StackTraceDatabase(callLoggerConfig.getStackTraceHashingTimeout(), RestMethodContext.class);
+
+ callLogger = getInstanceProperty(REST_callLogger, resource, RestCallLogger.class, BasicRestCallLogger.class, resourceResolver, this);
+
properties = builder.properties;
serializers =
SerializerGroup
@@ -3852,13 +3862,13 @@
}
this.callMethods = unmodifiableMap(_javaRestMethods);
- this.preCallMethods = _preCallMethods.values().toArray(new Method[_preCallMethods.size()]);
- this.postCallMethods = _postCallMethods.values().toArray(new Method[_postCallMethods.size()]);
- this.startCallMethods = _startCallMethods.values().toArray(new Method[_startCallMethods.size()]);
- this.endCallMethods = _endCallMethods.values().toArray(new Method[_endCallMethods.size()]);
- this.postInitMethods = _postInitMethods.values().toArray(new Method[_postInitMethods.size()]);
- this.postInitChildFirstMethods = _postInitChildFirstMethods.values().toArray(new Method[_postInitChildFirstMethods.size()]);
- this.destroyMethods = _destroyMethods.values().toArray(new Method[_destroyMethods.size()]);
+ this.preCallMethods = _preCallMethods.values().stream().map(x->new MethodInvoker(x, getMethodExecStats(x))).collect(Collectors.toList()).toArray(new MethodInvoker[_preCallMethods.size()]);
+ this.postCallMethods = _postCallMethods.values().stream().map(x->new MethodInvoker(x, getMethodExecStats(x))).collect(Collectors.toList()).toArray(new MethodInvoker[_postCallMethods.size()]);
+ this.startCallMethods = _startCallMethods.values().stream().map(x->new MethodInvoker(x, getMethodExecStats(x))).collect(Collectors.toList()).toArray(new MethodInvoker[_startCallMethods.size()]);
+ this.endCallMethods = _endCallMethods.values().stream().map(x->new MethodInvoker(x, getMethodExecStats(x))).collect(Collectors.toList()).toArray(new MethodInvoker[_endCallMethods.size()]);
+ this.postInitMethods = _postInitMethods.values().stream().map(x->new MethodInvoker(x, getMethodExecStats(x))).collect(Collectors.toList()).toArray(new MethodInvoker[_postInitMethods.size()]);
+ this.postInitChildFirstMethods = _postInitChildFirstMethods.values().stream().map(x->new MethodInvoker(x, getMethodExecStats(x))).collect(Collectors.toList()).toArray(new MethodInvoker[_postInitChildFirstMethods.size()]);
+ this.destroyMethods = _destroyMethods.values().stream().map(x->new MethodInvoker(x, getMethodExecStats(x))).collect(Collectors.toList()).toArray(new MethodInvoker[_destroyMethods.size()]);
this.preCallMethodParams = _preCallMethodParams.toArray(new RestMethodParam[_preCallMethodParams.size()][]);
this.postCallMethodParams = _postCallMethodParams.toArray(new RestMethodParam[_postCallMethodParams.size()][]);
this.startCallMethodParams = _startCallMethodParams.toArray(new Class[_startCallMethodParams.size()][]);
@@ -3949,6 +3959,22 @@
}
/**
+ * Returns the time statistics gatherer for the specified method.
+ *
+ * @param m The method to get statistics for.
+ * @return The cached time-stats object.
+ */
+ protected MethodExecStats getMethodExecStats(Method m) {
+ String n = MethodInfo.of(m).getSimpleName();
+ MethodExecStats ts = methodExecStats.get(n);
+ if (ts == null) {
+ methodExecStats.putIfAbsent(n, new MethodExecStats(m));
+ ts = methodExecStats.get(n);
+ }
+ return ts;
+ }
+
+ /**
* Returns the variable resolver for this servlet.
*
* <p>
@@ -3982,6 +4008,7 @@
* navlinks={
* <js>"up: $R{requestParentURI}"</js>,
* <js>"options: servlet:/?method=OPTIONS"</js>,
+ * <js>"stats: servlet:/stats"</js>,
* <js>"editLevel: servlet:/editLevel?logger=$A{attribute.name, OFF}"</js>
* }
* header={
@@ -4940,6 +4967,52 @@
}
/**
+ * Gives access to the internal stack trace database.
+ *
+ * @return The stack trace database.
+ */
+ public StackTraceDatabase getStackTraceDb() {
+ return stackTraceDb;
+ }
+
+ /**
+ * Returns timing information on all method executions on this class.
+ *
+ * <p>
+ * Timing information is maintained for any <ja>@RestResource</ja>-annotated and hook methods.
+ *
+ * @return A list of timing statistics ordered by average execution time descending.
+ */
+ public List<MethodExecStats> getMethodExecStats() {
+ return methodExecStats.values().stream().sorted().collect(Collectors.toList());
+ }
+
+ /**
+ * Gives access to the internal stack trace database.
+ *
+ * @return The stack trace database.
+ */
+ public RestContextStats getStats() {
+ return new RestContextStats(startTime, getMethodExecStats());
+ }
+
+ /**
+ * Returns the timing information returned by {@link #getMethodExecStats()} in a readable format.
+ *
+ * @return A report of all method execution times ordered by .
+ */
+ public String getMethodExecStatsReport() {
+ StringBuilder sb = new StringBuilder()
+ .append(" Method Runs Running Errors Avg Total \n")
+ .append("------------------------------ --------- --------- -------- ------------ -----------\n");
+ getMethodExecStats()
+ .stream()
+ .sorted(Comparator.comparingDouble(MethodExecStats::getTotalTime).reversed())
+ .forEach(x -> sb.append(String.format("%30s %9d %9d %9d %10dms %10dms\n", x.getMethod(), x.getRuns(), x.getRunning(), x.getErrors(), x.getAvgTime(), x.getTotalTime())));
+ return sb.toString();
+ }
+
+ /**
* Finds the {@link RestMethodParam} instances to handle resolving objects on the calls to the specified Java method.
*
* @param mi The Java method being called.
@@ -5017,7 +5090,7 @@
preOrPost(resource, postCallMethods[i], postCallMethodParams[i], req, res);
}
- private static void preOrPost(Object resource, Method m, RestMethodParam[] mp, RestRequest req, RestResponse res) throws HttpException {
+ private static void preOrPost(Object resource, MethodInvoker m, RestMethodParam[] mp, RestRequest req, RestResponse res) throws HttpException {
if (m != null) {
Object[] args = new Object[mp.length];
for (int i = 0; i < mp.length; i++) {
@@ -5051,7 +5124,7 @@
startOrFinish(resource, endCallMethods[i], endCallMethodParams[i], call.getRequest(), call.getResponse());
}
- private static void startOrFinish(Object resource, Method m, Class<?>[] p, HttpServletRequest req, HttpServletResponse res) throws HttpException, InternalServerError {
+ private static void startOrFinish(Object resource, MethodInvoker m, Class<?>[] p, HttpServletRequest req, HttpServletResponse res) throws HttpException, InternalServerError {
if (m != null) {
Object[] args = new Object[p.length];
for (int i = 0; i < p.length; i++) {
@@ -5096,7 +5169,7 @@
return this;
}
- private void postInitOrDestroy(Object r, Method m, Class<?>[] p) {
+ private void postInitOrDestroy(Object r, MethodInvoker m, Class<?>[] p) {
if (m != null) {
Object[] args = new Object[p.length];
for (int i = 0; i < p.length; i++) {
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextStats.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextStats.java
new file mode 100644
index 0000000..1f0664c
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextStats.java
@@ -0,0 +1,63 @@
+// ***************************************************************************************************************************
+// * 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.juneau.rest;
+
+import java.time.*;
+import java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.transforms.*;
+import org.apache.juneau.utils.*;
+
+/**
+ * A snapshot of execution statistics for REST resource classes.
+ */
+@Bean(bpi="startTime,upTime,methodStats")
+public class RestContextStats {
+ private final Instant startTime;
+ private final List<MethodExecStats> methodStats;
+
+ RestContextStats(Instant startTime, List<MethodExecStats> methodStats) {
+ this.startTime = startTime;
+ this.methodStats = methodStats;
+ }
+
+ /**
+ * Returns the time this REST resource class was started.
+ *
+ * @return The time this REST resource class was started.
+ */
+ @Swap(TemporalSwap.IsoInstant.class)
+ public Instant getStartTime() {
+ return startTime;
+ }
+
+ /**
+ * Returns the time in milliseconds that this REST resource class has been running.
+ *
+ * @return The time in milliseconds that this REST resource class has been running.
+ */
+ public String getUpTime() {
+ long s = Duration.between(startTime, Instant.now()).getSeconds();
+ return String.format("%dh:%02dm:%02ds", s / 3600, (s % 3600) / 60, (s % 60));
+ }
+
+ /**
+ * Returns statistics on all method executions.
+ *
+ * @return Statistics on all method executions.
+ */
+ public Collection<MethodExecStats> getMethodStats() {
+ return methodStats;
+ }
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
index f787979..fc20866 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
@@ -52,6 +52,7 @@
import org.apache.juneau.rest.widget.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.svl.*;
+import org.apache.juneau.utils.*;
/**
* Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}.
@@ -552,6 +553,7 @@
private final Integer priority;
private final RestContext context;
final java.lang.reflect.Method method;
+ final MethodInvoker methodInvoker;
final MethodInfo mi;
final SerializerGroup serializers;
final ParserGroup parsers;
@@ -585,6 +587,7 @@
this.context = b.context;
this.method = b.method;
+ this.methodInvoker = new MethodInvoker(method, context.getMethodExecStats(method));
this.mi = MethodInfo.of(method);
// Need this to access methods in anonymous inner classes.
@@ -901,7 +904,7 @@
Object output;
try {
- output = method.invoke(context.getResource(), args);
+ output = methodInvoker.invoke(context.getResource(), args);
if (res.getStatus() == 0)
res.setStatus(200);
if (! method.getReturnType().equals(Void.TYPE)) {
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerGenerator.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerGenerator.java
index 8011678..a24d9a6 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerGenerator.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerGenerator.java
@@ -61,7 +61,7 @@
*/
final class SwaggerGenerator {
- private final RestRequest req;
+// private final RestRequest req;
private final VarResolverSession vr;
private final Locale locale;
private final RestContext context;
@@ -73,14 +73,16 @@
/**
* Constructor.
- * @param req The HTTP request.
+ *
+ * @param context The REST class context.
+ * @param vr The variable resolver.
+ * @param locale The locale.
*/
- public SwaggerGenerator(RestRequest req) {
- this.req = req;
- this.vr = req.getVarResolverSession();
- this.locale = req.getLocale();
- this.context = req.getContext();
- this.js = req.getJsonSchemaGenerator().createSession();
+ public SwaggerGenerator(RestContext context, VarResolverSession vr, Locale locale) {
+ this.vr = vr;
+ this.locale = locale;
+ this.context = context;
+ this.js = context.getJsonSchemaGenerator().createSession();
this.c = context.getResource().getClass();
this.resource = context.getResource();
this.mb = context.getMessages();
@@ -273,10 +275,6 @@
// Iterate through all the @RestMethod methods.
for (RestMethodContext sm : context.getCallMethods().values()) {
- // Skip it if user doesn't have access.
- if (! sm.isRequestAllowed(req))
- continue;
-
BeanSession bs = sm.createBeanSession();
Method m = sm.method;
@@ -862,13 +860,10 @@
SerializerSessionArgs args =
SerializerSessionArgs
.create()
- .javaMethod(req.getJavaMethod())
- .locale(req.getLocale())
+ .locale(locale)
.mediaType(mt)
- .debug(req.isDebug() ? true : null)
- .uriContext(req.getUriContext())
.useWhitespace(true)
- .resolver(req.getVarResolverSession());
+ ;
try {
String eVal = s2.createSession(args).serializeToString(example);
examples.put(s2.getPrimaryMediaType().toString(), eVal);
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/HtmlDoc.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/HtmlDoc.java
index 8be7be4..37f5f7f 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/HtmlDoc.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/HtmlDoc.java
@@ -34,7 +34,7 @@
* <jc>// Defined via properties.</jc>
* <ja>@RestResource</ja>(
* properties={
- * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_navlinks</jsf>, value=<js>"{options:'?method=OPTIONS',doc:'doc'}"</js>)
+ * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_navlinks</jsf>, value=<js>"{options:'servlet:/?method=OPTIONS',stats:'servlet:/stats',doc:'doc'}"</js>)
* }
* )
*
@@ -42,7 +42,8 @@
* <ja>@RestResource</ja>(
* htmldoc=<ja>@HtmlDoc</ja>(
* navlinks={
- * <js>"options: ?method=OPTIONS"</js>,
+ * <js>"options: servlet:/?method=OPTIONS"</js>,
+ * <js>"stats: servlet:/stats"</js>,
* <js>"doc: doc"</js>
* }
* )
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java
index af2b561..5d48a47 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java
@@ -472,6 +472,7 @@
* navlinks={
* <js>"up: request:/.."</js>,
* <js>"options: servlet:/?method=OPTIONS"</js>,
+ * <js>"stats: servlet:/stats"</js>,
* <js>"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java"</js>,
* },
* aside={