Commit c5fa9332 authored by Woonsan Ko's avatar Woonsan Ko

HSTTWO-3498 HDC as more extensible to integrate with external APM

parent 98825f66
......@@ -32,6 +32,7 @@ src/main/java/org/onehippo/cms7/logging/log4j/MdcOrJndiPropertyFilter.java -text
src/main/java/org/onehippo/cms7/logging/log4j/MdcPropertyFilter.java -text
src/main/java/org/onehippo/cms7/util/WeakIdentityMap.java -text
src/main/java/org/onehippo/sso/UrlSafeBase64.java -text
src/test/java/org/hippoecm/hst/diagnosis/TestCustomHDC.java -text
src/test/java/org/hippoecm/hst/diagnosis/TestHDC.java -text svneol=unset#text/plain
src/test/java/org/onehippo/cms7/event/HippoEventTest.java -text
src/test/java/org/onehippo/sso/CredentialCipherTest.java -text
......@@ -29,10 +29,10 @@ import org.slf4j.LoggerFactory;
/**
* DefaultTaskImpl
*/
class DefaultTaskImpl implements Task {
public class DefaultTaskImpl implements Task {
private static final Logger log = LoggerFactory.getLogger(DefaultTaskImpl.class);
private final String name;
private Map<String, Object> attributes;
......@@ -43,7 +43,7 @@ class DefaultTaskImpl implements Task {
private long durationTimeMillis = -1L;
private boolean stopped;
DefaultTaskImpl(final Task parentTask, final String name) {
protected DefaultTaskImpl(final Task parentTask, final String name) {
this.parentTask = parentTask;
this.name = name;
this.startTimeMillis = System.currentTimeMillis();
......@@ -116,7 +116,7 @@ class DefaultTaskImpl implements Task {
childTasks = new LinkedList<Task>();
}
Task childTask = new DefaultTaskImpl(this, name);
Task childTask = createSubtask(this, name);
childTasks.add(childTask);
HDC.setCurrentTask(childTask);
return childTask;
......@@ -156,4 +156,13 @@ class DefaultTaskImpl implements Task {
return durationTimeMillis;
}
/**
* Creates a real <code>Task</code> instance.
* @param parentTask parent task
* @param name task name
* @return <code>Task</code> instance
*/
protected Task createSubtask(final Task parentTask, final String name) {
return new DefaultTaskImpl(parentTask, name);
}
}
......@@ -15,42 +15,194 @@
*/
package org.hippoecm.hst.diagnosis;
import org.slf4j.LoggerFactory;
/**
* Hierarchical Diagnostic Context
* Hierarchical Diagnostic Context.
* <P>This provides static methods to start, get and clean up diagnostic tasks.</P>
* <P>This also allows to customize <code>HDC</code> by setting a system property,
* <code>org.hippoecm.hst.diagnosis</code> to a specific implementation class name.
* A custom <code>HDC</code> implementation may override the instance methods (<code>#doXXX</code>)
* to extend the functionality.</code></P>
* <P>
* For example, suppose you want to extend <code>HDC</code> to report the duration of each task
* to an external performance measuring system. Then one example implementation could look like the following:
* </P>
* <PRE>
* public class CustomExternalApmIntegratedHDC extends HDC {
*
* // Suppose externalAPM is your external application performance monitoring system
* // at your hand to integrate with.
* private ExternalAPM externalAPM = ...;
*
* public CustomExternalApmIntegratedHDC() {
* super();
* }
*
* @Override
* protected Task doCreateTask(String name) {
* // Create a custom root task implementation to override Task#stop() method
* return new CustomTaskImpl(null, name) {
* @Override
* protected Task createSubtask(final Task parentTask, final String name) {
* return new CustomTaskImpl(parentTask, name);
* }
* };
* }
*
* //Custom extension task implementation which records a metric on stop.
* private class CustomTaskImpl extends DefaultTaskImpl {
*
* protected CustomTaskImpl(Task parentTask, String name) {
* super(parentTask, name);
* }
*
* @Override
* public void stop() {
* super.stop();
* // let's suppose
* externalAPM.recordMetric("Custom/site_" + getName() + "/duration", getDurationTimeMillis());
* }
*
* @Override
* protected Task createSubtask(final Task parentTask, final String name) {
* // Create a child task with this class, too.
* return new CustomTaskImpl(parentTask, name);
* }
* }
* }
* </PRE>
*/
public class HDC {
private static HDC singleton = new HDC();
static {
final String clazzName = System.getProperty(HDC.class.getName());
if (clazzName != null && !"".equals(clazzName)) {
try {
Class<?> clazz = Class.forName(clazzName);
singleton = (HDC) clazz.newInstance();
System.out.println("INFO HDC singleton: " + singleton);
LoggerFactory.getLogger(HDC.class).info("HDC singleton: {}", singleton);
} catch (Exception e) {
System.err.println("ERROR Invalid custom HDC class: " + clazzName);
e.printStackTrace();
LoggerFactory.getLogger(HDC.class).info("Invalid custom HDC class: {}", clazzName, e);
}
}
}
public static final Task NOOP_TASK = new NOOPTaskImpl();
private static ThreadLocal<Task> tlRootTask = new ThreadLocal<Task>();
private static ThreadLocal<Task> tlCurrentTask = new ThreadLocal<Task>();
private HDC() {
/**
* Start the root task with the name.
* @param name root task name
* @return root task instance
*/
public static Task start(String name) {
return singleton.doStart(name);
}
public static Task start(String name) {
/**
* Returns true if the root task was started.
* @return true if the root task was started
*/
public static boolean isStarted() {
return singleton.doIsStarted();
}
/**
* Returns the root task. Null otherwise.
* @return the root task if exists. Null, otherwise.
*/
public static Task getRootTask() {
return singleton.doGetRootTask();
}
/**
* Returns the task in the current thread context. Null if not available.
* @return the task in the current thread context. Null if not available.
*/
public static Task getCurrentTask() {
return singleton.doGetCurrentTask();
}
/**
* Sets a task in the current thread context.
* @param currentTask current task instance
*/
public static void setCurrentTask(Task currentTask) {
singleton.doSetCurrentTask(currentTask);
}
/**
* Cleans up the HDC tasks and its context.
*/
public static void cleanUp() {
singleton.doCleanUp();
}
/**
* Protected constructor which might be called by a child class.
*/
protected HDC() {
}
/**
* Internally starts the root task by the name.
* @param name root task name
* @return the root task instance
*/
protected Task doStart(String name) {
Task rootTask = tlRootTask.get();
if (rootTask != null) {
throw new IllegalStateException("The root task was already started.");
}
rootTask = new DefaultTaskImpl(null, name);
rootTask = doCreateTask(name);
tlRootTask.set(rootTask);
return rootTask;
}
public static boolean isStarted() {
/**
* Internally create a task instance by the name.
* This method is invoked by {@link #doStart(String)}, so a child class may override this method
* if it needs to override the default task implementation, {@link DefaultTaskImpl}, for instance.
* @param name task name
* @return internally created task instance
*/
protected Task doCreateTask(String name) {
return new DefaultTaskImpl(null, name);
}
/**
* Internally check whether or not the root task was started.
* @return returns if the root task was started
*/
protected boolean doIsStarted() {
return (tlRootTask.get() != null);
}
public static Task getRootTask() {
/**
* Internally returns the root task instance if available. Null otherwise.
* @return the root task instance if available. Null otherwise
*/
protected Task doGetRootTask() {
Task rootTask = tlRootTask.get();
return (rootTask != null ? rootTask : NOOP_TASK);
}
public static Task getCurrentTask() {
/**
* Internally returns the task instance in the current thread context if available. Null otherwise.
* @return the task instance in the current thread context if available. Null otherwise
*/
protected Task doGetCurrentTask() {
Task current = tlCurrentTask.get();
if (current != null) {
......@@ -60,11 +212,18 @@ public class HDC {
return getRootTask();
}
public static void setCurrentTask(Task currentTask) {
/**
* Internally sets the task instance in the current thread context.
* @param currentTask current task instance
*/
protected void doSetCurrentTask(Task currentTask) {
tlCurrentTask.set(currentTask);
}
public static void cleanUp() {
/**
* Cleans up the HDC tasks and its context.
*/
protected void doCleanUp() {
tlCurrentTask.remove();
tlRootTask.remove();
}
......
/**
* Copyright 2015-2015 Hippo B.V. (http://www.onehippo.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.hippoecm.hst.diagnosis;
import static org.junit.Assert.assertEquals;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* TestCustomHDC.
*/
public class TestCustomHDC {
private static DemoExternalAPM demoExternalAPM;
private Valve1 valve1 = new Valve1();
private Component1 comp1 = new Component1();
private Component2 comp2 = new Component2();
private Query query1 = new Query();
private Query query2 = new Query();
@BeforeClass
public static void beforeClass() throws Exception {
System.setProperty(HDC.class.getName(), CustomExternalApmIntegratedHDC.class.getName());
demoExternalAPM = new DemoExternalAPM();
}
@AfterClass
public static void afterClass() throws Exception {
System.clearProperty(HDC.class.getName());
}
@Test
public void testDefaultExample() throws Exception {
// first, the HST container will start the root task first somewhere. e.g., HstFilter or InitializationValve
Task rootTask = HDC.start("request-processing");
// when invoking each valve, HST container can start a subtask
{
Task valveTask = HDC.getCurrentTask().startSubtask("valve1");
valve1.execute();
// if the container started a subtask, then it should stop the task.
// in reality, it should use try ~ finally to guarantee this call.
valveTask.stop();
}
// also the container will stop the root task.
rootTask.stop();
// clean up all the stored thread context information..
HDC.cleanUp();
// validates the metrics accumulated in the simulated external APM
Map<String, List<Long>> metrics = demoExternalAPM.getMetrics();
assertEquals(5, metrics.size()); // root, valve1, comp1, comp2 and query
assertEquals(1, metrics.get("Custom/site_request-processing/duration").size());
assertEquals(1, metrics.get("Custom/site_valve1/duration").size());
assertEquals(1, metrics.get("Custom/site_comp1/duration").size());
assertEquals(1, metrics.get("Custom/site_comp2/duration").size());
assertEquals(2, metrics.get("Custom/site_query/duration").size());
}
private void sleepRandom(long max) {
try {
Thread.sleep(Math.abs(Math.round(Math.random() * max)));
} catch (InterruptedException e) {
}
}
/**
* Simulated external APM which takes named metric and simply stores everything in memory.
*/
public static class DemoExternalAPM {
private Map<String, List<Long>> metrics = new LinkedHashMap<>();
public void recordMetric(final String name, long value) {
List<Long> metric = metrics.get(name);
if (metric == null) {
metric = new LinkedList<>();
metrics.put(name, metric);
}
metric.add(value);
}
public Map<String, List<Long>> getMetrics() {
return metrics;
}
}
/**
* Simulated custom APM integrated HDC implementation
* which simply records a custom metric using an API.
*/
public static class CustomExternalApmIntegratedHDC extends HDC {
public CustomExternalApmIntegratedHDC() {
super();
}
@Override
protected Task doCreateTask(String name) {
// Create a custom root task implementation.
return new CustomTaskImpl(null, name) {
@Override
protected Task createSubtask(final Task parentTask, final String name) {
return new CustomTaskImpl(parentTask, name);
}
};
}
/**
* Custom extension task implementation which records a metric on stop.
*/
private class CustomTaskImpl extends DefaultTaskImpl {
protected CustomTaskImpl(Task parentTask, String name) {
super(parentTask, name);
}
@Override
public void stop() {
super.stop();
//System.out.println("$$$$$ Custom/site_" + getName() + "/duration, " + getDurationTimeMillis());
demoExternalAPM.recordMetric("Custom/site_" + getName() + "/duration", getDurationTimeMillis());
}
@Override
protected Task createSubtask(final Task parentTask, final String name) {
return new CustomTaskImpl(parentTask, name);
}
}
}
class Valve1 {
public void execute() {
sleepRandom(10);
// A valve can also start a subtask from its current context task.
Task compTask = HDC.getCurrentTask().startSubtask("comp1");
comp1.execute();
compTask.stop();
sleepRandom(10);
compTask = HDC.getCurrentTask().startSubtask("comp2");
comp2.execute();
compTask.stop();
sleepRandom(10);
}
}
// Normally component developers do not need to manage tasks by themselves
// because the task context for each component will be provided by the container.
// e.g., by HstComponentInvoker or an AOP for HstComponentInvoker, etc.
// so, the following component examples doesn't contain task management codes.
class Component1 {
public void execute() {
sleepRandom(10);
query1.execute("//element[jcr:contains(., 'hippo')]");
sleepRandom(10);
}
}
class Component2 {
public void execute() {
sleepRandom(10);
query2.execute("//element[jcr:contains(., 'cms')]");
sleepRandom(10);
}
}
// HST Content Beans may manage its task context. e.g., HstQuery, HippoBeansIterator, etc.
class Query {
public void execute(String statement) {
sleepRandom(10);
Task queryTask = HDC.getCurrentTask().startSubtask("query");
sleepRandom(10);
queryTask.setAttribute("statement", statement);
queryTask.stop();
sleepRandom(10);
}
}
}
......@@ -18,20 +18,13 @@ package org.hippoecm.hst.diagnosis;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.hippoecm.hst.diagnosis.HDC;
import org.hippoecm.hst.diagnosis.Task;
import org.hippoecm.hst.diagnosis.TaskLogFormatUtils;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TestHDC
* TestHDC.
*/
public class TestHDC {
private static Logger log = LoggerFactory.getLogger(TestHDC.class);
private Valve1 valve1 = new Valve1();
private Valve2 valve2 = new Valve2();
private Component1 comp1 = new Component1();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment