Index: src/main/java/org/hippoecm/hst/diagnosis/DefaultTaskImpl.java =================================================================== --- src/main/java/org/hippoecm/hst/diagnosis/DefaultTaskImpl.java (revision 0) +++ src/main/java/org/hippoecm/hst/diagnosis/DefaultTaskImpl.java (working copy) @@ -0,0 +1,159 @@ +/** + * Copyright 2012-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 java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * DefaultTaskImpl + */ +class DefaultTaskImpl implements Task { + + private static final Logger log = LoggerFactory.getLogger(DefaultTaskImpl.class); + + private final String name; + private Map attributes; + + private final Task parentTask; + private List childTasks; + + private long startTimeMillis; + private long durationTimeMillis = -1L; + private boolean stopped; + + DefaultTaskImpl(final Task parentTask, final String name) { + this.parentTask = parentTask; + this.name = name; + this.startTimeMillis = System.currentTimeMillis(); + } + + @Override + public String getName() { + return name; + } + + @Override + public Map getAttributeMap() { + if (attributes == null) { + return Collections.emptyMap(); + } + + return Collections.unmodifiableMap(attributes); + } + + @Override + public Enumeration getAttributeNames() { + if (attributes != null) { + return Collections.enumeration(attributes.keySet()); + } else { + List emptyAttrNames = Collections.emptyList(); + return Collections.enumeration(emptyAttrNames); + } + } + + @Override + public void setAttribute(String key, Object value) { + if (attributes == null) { + // keep order of insertion thus Linked + attributes = new LinkedHashMap (); + } + + attributes.put(key, value); + } + + @Override + public Object getAttribute(String key) { + if (attributes == null) { + return null; + } + + return attributes.get(key); + } + + @Override + public Object removeAttribute(String key) { + if (attributes != null) { + return attributes.remove(key); + } + + return null; + } + + @Override + public Task getParentTask() { + return parentTask; + } + + @Override + public Task startSubtask(String name) { + if (stopped) { + throw new IllegalStateException("The task was already stopped."); + } + + if (childTasks == null) { + childTasks = new LinkedList(); + } + + Task childTask = new DefaultTaskImpl(this, name); + childTasks.add(childTask); + HDC.setCurrentTask(childTask); + return childTask; + } + + @Override + public void stop() { + if (stopped) { + log.warn("Task '{}' was already stopped.", name); + return; + } + + stopped = true; + durationTimeMillis = System.currentTimeMillis() - startTimeMillis; + HDC.setCurrentTask(parentTask); + } + + @Override + public Collection getChildTasks() { + if (childTasks == null) { + return Collections.emptyList(); + } + + return Collections.unmodifiableCollection(childTasks); + } + + @Override + public boolean isRunning() { + return !stopped; + } + + @Override + public long getDurationTimeMillis() { + if (!stopped) { + log.warn("Task '{}' was not stopped hence duration time unknown.", name); + } + return durationTimeMillis; + } + +} Property changes on: src/main/java/org/hippoecm/hst/diagnosis/DefaultTaskImpl.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: src/main/java/org/hippoecm/hst/diagnosis/HDC.java =================================================================== --- src/main/java/org/hippoecm/hst/diagnosis/HDC.java (revision 0) +++ src/main/java/org/hippoecm/hst/diagnosis/HDC.java (working copy) @@ -0,0 +1,71 @@ +/** + * Copyright 2012-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; + + +/** + * Hierarchical Diagnostic Context + */ +public class HDC { + + public static final Task NOOP_TASK = new NOOPTaskImpl(); + + private static ThreadLocal tlRootTask = new ThreadLocal(); + private static ThreadLocal tlCurrentTask = new ThreadLocal(); + + private HDC() { + } + + public static Task start(String name) { + Task rootTask = tlRootTask.get(); + + if (rootTask != null) { + throw new IllegalStateException("The root task was already started."); + } + + rootTask = new DefaultTaskImpl(null, name); + tlRootTask.set(rootTask); + return rootTask; + } + + public static boolean isStarted() { + return (tlRootTask.get() != null); + } + + public static Task getRootTask() { + Task rootTask = tlRootTask.get(); + return (rootTask != null ? rootTask : NOOP_TASK); + } + + public static Task getCurrentTask() { + Task current = tlCurrentTask.get(); + + if (current != null) { + return current; + } + + return getRootTask(); + } + + public static void setCurrentTask(Task currentTask) { + tlCurrentTask.set(currentTask); + } + + public static void cleanUp() { + tlCurrentTask.remove(); + tlRootTask.remove(); + } +} Property changes on: src/main/java/org/hippoecm/hst/diagnosis/HDC.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: src/main/java/org/hippoecm/hst/diagnosis/NOOPTaskImpl.java =================================================================== --- src/main/java/org/hippoecm/hst/diagnosis/NOOPTaskImpl.java (revision 0) +++ src/main/java/org/hippoecm/hst/diagnosis/NOOPTaskImpl.java (working copy) @@ -0,0 +1,95 @@ +/** + * Copyright 2012-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 java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +/** + * NOOPTaskImpl + */ +class NOOPTaskImpl implements Task { + + private boolean stopped; + + NOOPTaskImpl() { + } + + @Override + public String getName() { + return ""; + } + + @Override + public Map getAttributeMap() { + return Collections.emptyMap(); + } + + @Override + public Enumeration getAttributeNames() { + List emptyAttrNames = Collections.emptyList(); + return Collections.enumeration(emptyAttrNames); + } + + @Override + public void setAttribute(String key, Object value) { + } + + @Override + public Object getAttribute(String key) { + return null; + } + + @Override + public Object removeAttribute(String key) { + return null; + } + + @Override + public Task getParentTask() { + return null; + } + + @Override + public Task startSubtask(String name) { + HDC.setCurrentTask(this); + return this; + } + + @Override + public void stop() { + stopped = true; + } + + @Override + public Collection getChildTasks() { + return Collections.emptyList(); + } + + @Override + public boolean isRunning() { + return !stopped; + } + + @Override + public long getDurationTimeMillis() { + return 0L; + } + +} Property changes on: src/main/java/org/hippoecm/hst/diagnosis/NOOPTaskImpl.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: src/main/java/org/hippoecm/hst/diagnosis/Task.java =================================================================== --- src/main/java/org/hippoecm/hst/diagnosis/Task.java (revision 0) +++ src/main/java/org/hippoecm/hst/diagnosis/Task.java (working copy) @@ -0,0 +1,101 @@ +/** + * Copyright 2012-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 java.util.Collection; +import java.util.Enumeration; +import java.util.Map; + +/** + * A unit of execution. + * A task may start a subtask, and may contain multiple child subtasks. + * Each task may have attributes map of necessary data needed for diagnostics. + * By the way, the root task should be given by the container. + */ +public interface Task { + + /** + * returns the task name + * @return + */ + String getName(); + + /** + * Returns attribute map which is unmodifiable. So, do not try to put or remove items directly from the returned map. + * @return + */ + Map getAttributeMap(); + + /** + * Enumerates the attribute names + */ + public Enumeration getAttributeNames(); + + /** + * Set an attribute for the task. The object value should have a proper #toString method + * as by default, the #toString method is used for displaying the object in the diagnostics. + * @param key attribute name + * @param value attribute value + */ + void setAttribute(String key, Object value); + + /** + * Retrieve the attribute value by the attribute name. When not found, null + * is returned + */ + Object getAttribute(String key); + + /** + * Removes the attribute by the attribute name. When an Object was + * removed for key, this object is returned. Otherwise null is returned. + */ + Object removeAttribute(String key); + + /** + * @return Returns the parent task and null if this is the root task + */ + Task getParentTask(); + + /** + * Starts and returns a child subtask with the name. + * @param name + * @return + */ + Task startSubtask(String name); + + /** + * Stops the task + */ + void stop(); + + /** + * Returns the child tasks collection + * @return + */ + Collection getChildTasks(); + + /** + * Returns true if the task was started but not stopped. + * @return + */ + boolean isRunning(); + + /** + * Returns the task execution duration time in milliseconds + * @return + */ + long getDurationTimeMillis(); +} Property changes on: src/main/java/org/hippoecm/hst/diagnosis/Task.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: src/main/java/org/hippoecm/hst/diagnosis/TaskLogFormatUtils.java =================================================================== --- src/main/java/org/hippoecm/hst/diagnosis/TaskLogFormatUtils.java (revision 0) +++ src/main/java/org/hippoecm/hst/diagnosis/TaskLogFormatUtils.java (working copy) @@ -0,0 +1,103 @@ +/* + * Copyright 2012-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 java.util.BitSet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility class to get a pretty printed hierarchical task log + */ +public class TaskLogFormatUtils { + + private static final Logger log = LoggerFactory.getLogger(TaskLogFormatUtils.class); + + /** + * @return returns the task nicely hierarchical formatted + */ + public static String getTaskLog(Task task) { + return getTaskLog(task, -1); + } + + /** + * + * @param task the task to log + * @param maxDepth the maximum depth until how deep child tasks should be logged. maxDepth of -1 + * will log all descendant tasks, maxDepth of 0 only the rootTask, maxDepth + * of 1 the rootTask plus its direct children, etc. + * @return + */ + public static String getTaskLog(Task task, final int maxDepth) { + StringBuilder sb = new StringBuilder(256); + appendTaskLog(sb, task, 0, new BitSet(0), false, maxDepth); + return sb.toString(); + } + + private static void appendTaskLog(final StringBuilder sb, + final Task task, + final int depth, + final BitSet bitset, + final boolean lastChild, + final int maxDepth) { + if (maxDepth > -1 && depth > maxDepth) { + return; + } + + BitSet hidePipeAt = new BitSet(depth); + hidePipeAt.or(bitset); + for (int i = 0; i < depth; i++) { + if (i > 0) { + if (hidePipeAt.get(i)) { + sb.append(" "); + } else { + sb.append("|"); + } + } + + sb.append(" "); + } + if (depth > 0) { + if (lastChild) { + sb.append("`"); + hidePipeAt.set(depth); + } else { + sb.append("|"); + } + } + try { + String msg = "- " + task.getName() + " (" + task.getDurationTimeMillis() + "ms): " + task.getAttributeMap(); + sb.append(msg).append('\n'); + } catch (Throwable e) { + if (log.isDebugEnabled()) { + log.warn("Exception during writing task", e); + } else { + log.warn("Exception during writing task : {}", e.toString()); + } + } + + int count = 0; + for (Task childTask : task.getChildTasks()) { + count++; + if (count == task.getChildTasks().size()) { + appendTaskLog(sb, childTask, depth + 1,hidePipeAt, true, maxDepth); + } else { + appendTaskLog(sb, childTask, depth + 1,hidePipeAt, false, maxDepth); + } + } + } +} Property changes on: src/main/java/org/hippoecm/hst/diagnosis/TaskLogFormatUtils.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: src/test/java/org/hippoecm/hst/diagnosis/TestHDC.java =================================================================== --- src/test/java/org/hippoecm/hst/diagnosis/TestHDC.java (revision 0) +++ src/test/java/org/hippoecm/hst/diagnosis/TestHDC.java (working copy) @@ -0,0 +1,235 @@ +/** + * Copyright 2012-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.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 + */ +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(); + private Component2 comp2 = new Component2(); + private Query query1 = new Query(); + private Query query2 = new Query(); + + @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(); + } + + { + Task valveTask = HDC.getCurrentTask().startSubtask("valve2"); + valve2.execute(); + valveTask.stop(); + } + + // also the container will stop the root task. + rootTask.stop(); + + // all the task execution information can be collected and reported later (maybe in another valve before cleanupValve) + final String logSummary = logSummary(); + assertTrue(logSummary.contains("valve2")); + + final String logSummaryWithDepth0 = logSummary(0); + assertFalse(logSummaryWithDepth0.contains("valve2")); + + final String logSummaryWithDepthMinus1 = logSummary(-1); + assertTrue(logSummaryWithDepthMinus1.contains("valve2")); + + final String logSummaryWithDepth1 = logSummary(1); + assertTrue(logSummaryWithDepth1.contains("valve2")); + + // clean up all the stored thread context information.. + HDC.cleanUp(); + } + + @Test + public void testNOOPExample() throws Exception { + + // 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(); + } + + { + Task valveTask = HDC.getCurrentTask().startSubtask("valve2"); + valve2.execute(); + valveTask.stop(); + } + + // all the task execution information can be collected and reported later (maybe in another valve before cleanupValve) + final String logSummary = logSummary(); + + assertFalse(logSummary.contains("valve2")); + + // clean up all the stored thread context information.. + HDC.cleanUp(); + } + + @Test + public void testRootTaskOnlyExample() throws Exception { + // first, the HST container will start the root task first somewhere. e.g., HstFilter or InitializationValve + Task rootTask = HDC.start("request-processing"); + HDC.setCurrentTask(HDC.NOOP_TASK); + + // 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(); + } + + { + Task valveTask = HDC.getCurrentTask().startSubtask("valve2"); + valve2.execute(); + valveTask.stop(); + } + + // also the container will stop the root task. + rootTask.stop(); + + // all the task execution information can be collected and reported later (maybe in another valve before cleanupValve) + final String logSummary = logSummary(); + + assertFalse(logSummary.contains("valve2")); + + // clean up all the stored thread context information.. + HDC.cleanUp(); + } + + + private String logSummary() { + Task rootTask = HDC.getRootTask(); + return TaskLogFormatUtils.getTaskLog(rootTask); + } + + private String logSummary(final int depth) { + Task rootTask = HDC.getRootTask(); + return TaskLogFormatUtils.getTaskLog(rootTask, depth); + } + + + private void sleepRandom(long max) { + try { + Thread.sleep(Math.abs(Math.round(Math.random() * max))); + } catch (InterruptedException e) { + } + } + + 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"); + Task compTaskA = HDC.getCurrentTask().startSubtask("comp1A"); + comp1.execute(); + compTaskA.stop(); + comp1.execute(); + compTask.stop(); + + + + sleepRandom(10); + + compTask = HDC.getCurrentTask().startSubtask("comp2"); + comp2.execute(); + compTask.stop(); + + sleepRandom(10); + } + } + + class Valve2 { + public void execute() { + sleepRandom(10); + + 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); + } + } + +} Property changes on: src/test/java/org/hippoecm/hst/diagnosis/TestHDC.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property