TINKERPOP-1644 Added metrics to GremlinGroovyScriptEngine

Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo
Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/8ffa5af6
Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/8ffa5af6
Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/8ffa5af6

Branch: refs/heads/TINKERPOP-1642
Commit: 8ffa5af6ff56933c1955e88e8aa42c06cb69162b
Parents: a06072b
Author: Stephen Mallette <sp...@genoprime.com>
Authored: Thu Mar 9 10:30:17 2017 -0500
Committer: Stephen Mallette <sp...@genoprime.com>
Committed: Fri Mar 10 11:13:03 2017 -0500

----------------------------------------------------------------------
 CHANGELOG.asciidoc                              |   2 +
 .../jsr223/GremlinGroovyScriptEngine.java       | 122 ++++++++++++++++++-
 ...roovyScriptEngineCompilationOptionsTest.java |   8 +-
 .../jsr223/GremlinGroovyScriptEngineTest.java   |  70 ++++++++++-
 4 files changed, 193 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/8ffa5af6/CHANGELOG.asciidoc
----------------------------------------------------------------------
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 9c3313e..3729271 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -26,7 +26,9 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 TinkerPop 3.2.5 (Release Date: NOT OFFICIALLY RELEASED YET)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+* Added various metrics to the `GremlinGroovyScriptEngine` around script 
compilation.
 * Moved the `caffeine` dependency down to `gremlin-groovy` and out of 
`gremlin-server`.
+* Improved script compilation in `GremlinGroovyScriptEngine to use better 
caching, log long compile times and prevent failed compilations from 
recompiling on future requests.
 * Script compilation is synchronised.
 * Script compilation times are placed in to logs.
 * Failed scripts will not be recompiled.

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/8ffa5af6/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
----------------------------------------------------------------------
diff --git 
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
 
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
index 3bcb06c..0ed8d84 100644
--- 
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
+++ 
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
@@ -164,7 +164,10 @@ public class GremlinGroovyScriptEngine extends 
GroovyScriptEngineImpl
     /**
      * Script to generated Class map.
      */
-    private final LoadingCache<String, Future<Class>> classMap = 
Caffeine.newBuilder().softValues().build(new CacheLoader<String, 
Future<Class>>() {
+    private final LoadingCache<String, Future<Class>> classMap = 
Caffeine.newBuilder().
+            softValues().
+            recordStats().
+            build(new CacheLoader<String, Future<Class>>() {
         @Override
         public Future<Class> load(final String script) throws Exception {
             final long start = System.currentTimeMillis();
@@ -175,6 +178,7 @@ public class GremlinGroovyScriptEngine extends 
GroovyScriptEngineImpl
                 } catch (CompilationFailedException e) {
                     final long finish = System.currentTimeMillis();
                     log.error("Script compilation FAILED {} took {}ms {}", 
script, finish - start, e);
+                    failedCompilationCount.incrementAndGet();
                     throw e;
                 } finally {
                     final long time = System.currentTimeMillis() - start;
@@ -208,6 +212,13 @@ public class GremlinGroovyScriptEngine extends 
GroovyScriptEngineImpl
     private final AtomicLong longRunCompilationCount = new AtomicLong(0L);
 
     /**
+     * A counter for the instance that tracks the number of failed 
compilations. Note that the failures need to be
+     * tracked outside of cache failure load stats because the loading 
mechanism will always successfully return
+     * a future and won't trigger a failure.
+     */
+    private final AtomicLong failedCompilationCount = new AtomicLong(0L);
+
+    /**
      * The list of loaded plugins for the console.
      */
     private final Set<String> loadedPlugins = new HashSet<>();
@@ -589,10 +600,117 @@ public class GremlinGroovyScriptEngine extends 
GroovyScriptEngineImpl
     /**
      * Gets the number of compilations that extended beyond the {@link 
#expectedCompilationTime}.
      */
-    public long getLongRunCompilationCount() {
+    public long getClassCacheLongRunCompilationCount() {
         return longRunCompilationCount.longValue();
     }
 
+    /**
+     * Gets the estimated size of the class cache for compiled scripts.
+     */
+    public long getClassCacheEstimatedSize() {
+        return classMap.estimatedSize();
+    }
+
+    /**
+     * Gets the average time spent compiling new scripts.
+     */
+    public double getClassCacheAverageLoadPenalty() {
+        return classMap.stats().averageLoadPenalty();
+    }
+
+    /**
+     * Gets the number of times a script compiled to a class has been evicted 
from the cache.
+     */
+    public long getClassCacheEvictionCount() {
+        return classMap.stats().evictionCount();
+    }
+
+    /**
+     * Gets the sum of the weights of evicted entries from the class cache.
+     */
+    public long getClassCacheEvictionWeight() {
+        return classMap.stats().evictionWeight();
+    }
+
+    /**
+     * Gets the number of times cache look up for a compiled script returned a 
cached value.
+     */
+    public long getClassCacheHitCount() {
+        return classMap.stats().hitCount();
+    }
+
+    /**
+     * Gets the hit rate of the class cache.
+     */
+    public double getClassCacheHitRate() {
+        return classMap.stats().hitRate();
+    }
+
+    /**
+     * Gets the total number of times the cache lookup method attempted to 
compile new scripts.
+     */
+    public long getClassCacheLoadCount() {
+        return classMap.stats().loadCount();
+    }
+
+    /**
+     * Gets the total number of times the cache lookup method failed to 
compile a new script.
+     */
+    public long getClassCacheLoadFailureCount() {
+        // don't use classMap.stats().loadFailureCount() because the load 
mechanism always succeeds with a future
+        // that will in turn contain the success or failure
+        return failedCompilationCount.longValue();
+    }
+
+    /**
+     * Gets the ratio of script compilation attempts that failed.
+     */
+    public double getClassCacheLoadFailureRate() {
+        // don't use classMap.stats().loadFailureRate() because the load 
mechanism always succeeds with a future
+        // that will in turn contain the success or failure
+        long totalLoadCount = classMap.stats().loadCount();
+        return (totalLoadCount == 0)
+                ? 0.0
+                : (double) failedCompilationCount.longValue() / totalLoadCount;
+    }
+
+    /**
+     * Gets the total number of times the cache lookup method succeeded to 
compile a new script.
+     */
+    public long getClassCacheLoadSuccessCount() {
+        // don't use classMap.stats().loadSuccessCount() because the load 
mechanism always succeeds with a future
+        // that will in turn contain the success or failure
+        return classMap.stats().loadCount() - 
failedCompilationCount.longValue();
+    }
+
+    /**
+     * Gets the total number of times the cache lookup method returned a newly 
compiled script.
+     */
+    public long getClassCacheMissCount() {
+        return classMap.stats().missCount();
+    }
+
+    /**
+     * Gets the ratio of script compilation attempts that were misses.
+     */
+    public double getClassCacheMissRate() {
+        return classMap.stats().missRate();
+    }
+
+    /**
+     * Gets the total number of times the cache lookup method returned a 
cached or uncached value.
+     */
+    public long getClassCacheRequestCount() {
+        return classMap.stats().missCount();
+    }
+
+    /**
+     * Gets the total number of nanoseconds that the cache spent compiling 
scripts.
+     */
+    public long getClassCacheTotalLoadTime() {
+        return classMap.stats().totalLoadTime();
+    }
+
     Class getScriptClass(final String script) throws 
CompilationFailedException {
         try {
             return classMap.get(script).get();

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/8ffa5af6/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineCompilationOptionsTest.java
----------------------------------------------------------------------
diff --git 
a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineCompilationOptionsTest.java
 
b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineCompilationOptionsTest.java
index f5dd9c5..b87d0ac 100644
--- 
a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineCompilationOptionsTest.java
+++ 
b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineCompilationOptionsTest.java
@@ -47,11 +47,11 @@ public class 
GremlinGroovyScriptEngineCompilationOptionsTest {
             b.put("x" + ix, ix);
         }
 
-        assertEquals(0, engine.getLongRunCompilationCount());
+        assertEquals(0, engine.getClassCacheLongRunCompilationCount());
 
         engine.eval(script, b);
 
-        assertEquals(1, engine.getLongRunCompilationCount());
+        assertEquals(1, engine.getClassCacheLongRunCompilationCount());
     }
 
     @Test
@@ -71,10 +71,10 @@ public class 
GremlinGroovyScriptEngineCompilationOptionsTest {
             b.put("x" + ix, ix);
         }
 
-        assertEquals(0, engine.getLongRunCompilationCount());
+        assertEquals(0, engine.getClassCacheLongRunCompilationCount());
 
         engine.eval(script, b);
 
-        assertEquals(1, engine.getLongRunCompilationCount());
+        assertEquals(1, engine.getClassCacheLongRunCompilationCount());
     }
 }

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/8ffa5af6/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
----------------------------------------------------------------------
diff --git 
a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
 
b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
index eb0a44b..0606721 100644
--- 
a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
+++ 
b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
@@ -497,7 +497,7 @@ public class GremlinGroovyScriptEngineTest {
     }
 
     @Test
-    public void testInvokeFunctionRedirectsOutputToContextOut() throws 
Exception {
+    public void shouldInvokeFunctionRedirectsOutputToContextOut() throws 
Exception {
         final GremlinGroovyScriptEngine  engine = new 
GremlinGroovyScriptEngine();
         StringWriter writer = new StringWriter();
         final StringWriter unusedWriter = new StringWriter();
@@ -520,7 +520,7 @@ public class GremlinGroovyScriptEngineTest {
     }
 
     @Test
-    public void testEngineContextAccessibleToScript() throws Exception {
+    public void shouldEnableEngineContextAccessibleToScript() throws Exception 
{
         final GremlinGroovyScriptEngine  engine = new 
GremlinGroovyScriptEngine();
         final ScriptContext engineContext = engine.getContext();
         engine.put("theEngineContext", engineContext);
@@ -529,7 +529,7 @@ public class GremlinGroovyScriptEngineTest {
     }
 
     @Test
-    public void testContextBindingOverridesEngineContext() throws Exception {
+    public void shouldEnableContextBindingOverridesEngineContext() throws 
Exception {
         final GremlinGroovyScriptEngine  engine = new 
GremlinGroovyScriptEngine();
         final ScriptContext engineContext = engine.getContext();
         final Map<String,Object> otherContext = new HashMap<>();
@@ -539,4 +539,68 @@ public class GremlinGroovyScriptEngineTest {
         final String code = "[answer: context.is(theEngineContext) ? \"wrong\" 
: context.foo]";
         assertEquals("bar", ((Map) engine.eval(code)).get("answer"));
     }
+
+    @Test
+    public void shouldGetClassMapCacheBasicStats() throws Exception {
+        final GremlinGroovyScriptEngine  engine = new 
GremlinGroovyScriptEngine();
+        assertEquals(0, engine.getClassCacheEstimatedSize());
+        assertEquals(0, engine.getClassCacheHitCount());
+        assertEquals(0, engine.getClassCacheLoadCount());
+        assertEquals(0, engine.getClassCacheLoadFailureCount());
+        assertEquals(0, engine.getClassCacheLoadSuccessCount());
+
+        engine.eval("1+1");
+
+        assertEquals(1, engine.getClassCacheEstimatedSize());
+        assertEquals(0, engine.getClassCacheHitCount());
+        assertEquals(1, engine.getClassCacheLoadCount());
+        assertEquals(0, engine.getClassCacheLoadFailureCount());
+        assertEquals(1, engine.getClassCacheLoadSuccessCount());
+
+        for (int ix = 0; ix < 100; ix++) {
+            engine.eval("1+1");
+        }
+
+        assertEquals(1, engine.getClassCacheEstimatedSize());
+        assertEquals(100, engine.getClassCacheHitCount());
+        assertEquals(1, engine.getClassCacheLoadCount());
+        assertEquals(0, engine.getClassCacheLoadFailureCount());
+        assertEquals(1, engine.getClassCacheLoadSuccessCount());
+
+        for (int ix = 0; ix < 100; ix++) {
+            engine.eval("1+" + ix);
+        }
+
+        assertEquals(100, engine.getClassCacheEstimatedSize());
+        assertEquals(101, engine.getClassCacheHitCount());
+        assertEquals(100, engine.getClassCacheLoadCount());
+        assertEquals(0, engine.getClassCacheLoadFailureCount());
+        assertEquals(100, engine.getClassCacheLoadSuccessCount());
+
+        try {
+            engine.eval("(me broken");
+            fail("Should have tanked with compilation error");
+        } catch (Exception ex) {
+            assertThat(ex, instanceOf(ScriptException.class));
+        }
+
+        assertEquals(101, engine.getClassCacheEstimatedSize());
+        assertEquals(101, engine.getClassCacheHitCount());
+        assertEquals(101, engine.getClassCacheLoadCount());
+        assertEquals(1, engine.getClassCacheLoadFailureCount());
+        assertEquals(100, engine.getClassCacheLoadSuccessCount());
+
+        try {
+            engine.eval("(me broken");
+            fail("Should have tanked with compilation error");
+        } catch (Exception ex) {
+            assertThat(ex, instanceOf(ScriptException.class));
+        }
+
+        assertEquals(101, engine.getClassCacheEstimatedSize());
+        assertEquals(102, engine.getClassCacheHitCount());
+        assertEquals(101, engine.getClassCacheLoadCount());
+        assertEquals(1, engine.getClassCacheLoadFailureCount());
+        assertEquals(100, engine.getClassCacheLoadSuccessCount());
+    }
 }
\ No newline at end of file

Reply via email to