001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.db.timing;
029
030import java.io.ByteArrayOutputStream;
031import java.io.IOException;
032import java.util.ArrayList;
033import java.util.Arrays;
034import java.util.Comparator;
035import java.util.HashMap;
036import java.util.List;
037import java.util.Map;
038
039import org.dom4j.Document;
040import org.dom4j.DocumentHelper;
041import org.dom4j.Element;
042import org.dom4j.io.OutputFormat;
043import org.dom4j.io.XMLWriter;
044
045import com.google.common.collect.Lists;
046
047/**
048 * Builds up a tree whose nodes correspond to stack trace lines of the threads calling this
049 * profiling handler. The resulting tree can be dumped as XML.<p>
050 */
051public class CmsThreadStatsTreeProfilingHandler implements I_CmsProfilingHandler {
052
053    /**
054     * The tree node.<p>
055     */
056    public static class Node {
057
058        /** The key identifying the node among its siblings. */
059        private Object m_key;
060
061        /** The counted nanos for driver calls. */
062        private long m_nanos;
063
064        /** The driver call count. */
065        private int m_count;
066
067        /** The cumulative nanos for driver calls. */
068        private long m_cumulativeNanos;
069
070        /** The cumulative driver call count. */
071        private int m_cumulativeCount;
072
073        /** The children of this node, with their respective keys as map keys. */
074        private Map<Object, Node> m_children = new HashMap<>();
075
076        /**
077         * Creates a new node.<p>
078         *
079         * @param key the key for this node
080         */
081        public Node(Object key) {
082
083            m_key = key;
084        }
085
086        /**
087         * Computes the cumulative stats for a tree and dumps it to an XML document.<p>
088         *
089         * @param node the root node
090         * @return the resulting XML document
091         */
092        public static Document dumpTree(Node node) {
093
094            node.computeCumulativeData();
095            Document doc = DocumentHelper.createDocument();
096            Element root = doc.addElement("root");
097            node.dump(root);
098            return doc;
099        }
100
101        /**
102         * Updates the count / nanos for this node.<p>
103         *
104         * @param nanos the nanoseconds to add
105         */
106        public void addCall(long nanos) {
107
108            m_count += 1;
109            m_nanos += nanos;
110        }
111
112        /**
113         * Gets the child for a given key, or adds it if it doesn't exist yet.<p>
114         *
115         * @param key the key
116         * @return the child
117         */
118        public Node addOrGetChild(Object key) {
119
120            Node child = m_children.get(key);
121            if (child == null) {
122                child = new Node(key);
123                m_children.put(key, child);
124            }
125            return child;
126
127        }
128
129        /**
130         * Gets the descendant for a given path, or adds it if it doesn't exist yet.<p>
131         *
132         * @param path the path
133         * @return the descendant
134         */
135        public Node addOrGetDescendant(List<?> path) {
136
137            Node current = this;
138            for (Object key : path) {
139                current = current.addOrGetChild(key);
140            }
141            return current;
142        }
143
144        /**
145         * Computes the cumulative driver call count and nanos.<p>
146         */
147        public void computeCumulativeData() {
148
149            m_cumulativeCount = m_count;
150            m_cumulativeNanos = m_nanos;
151            for (Node child : m_children.values()) {
152                child.computeCumulativeData();
153                m_cumulativeNanos += child.getCumulativeNanos();
154                m_cumulativeCount += child.getCumulativeCount();
155            }
156        }
157
158        /**
159         * Computes XML structure for this node and its descendants and appends it to a given element.<p>
160         *
161         * @param parent the parent element
162         */
163        public void dump(Element parent) {
164
165            Element elem = parent.addElement("location").addAttribute("key", "" + m_key).addAttribute(
166                "count",
167                "" + m_cumulativeCount).addAttribute("millis", "" + (m_cumulativeNanos / 1000000));
168
169            // Sort children so the "most expensive" ones go first
170            List<Node> children = new ArrayList<>(m_children.values());
171            children.sort(new Comparator<Node>() {
172
173                @SuppressWarnings("synthetic-access")
174                public int compare(Node o1, Node o2) {
175
176                    return -Long.compare(o1.getCumulativeNanos(), o2.getCumulativeNanos());
177                }
178            });
179            for (Node child : children) {
180                child.dump(elem);
181            }
182        }
183
184        /**
185         * Gets the key for the node.<p>
186         *
187         * @return tbe key
188         */
189        public Object getKey() {
190
191            return m_key;
192        }
193
194        /**
195         * Gets the cumulative driver call count (does not automatically update).<p>
196         *
197         * @return the cumulative driver call count
198         */
199        private int getCumulativeCount() {
200
201            return m_cumulativeCount;
202        }
203
204        /**
205         * Gets the cumulative driver call nanos (does not automatically update).<p>
206         *
207         * @return the cumulative driver call nanos
208         */
209        private long getCumulativeNanos() {
210
211            return m_cumulativeNanos;
212        }
213
214    }
215
216    /** The root node. */
217    private Node m_root = new Node("ROOT");
218
219    /** Flag which records whether we have measured data yet. */
220    private boolean m_hasData;
221
222    /**
223     * Dumps the tree as XML.<p>
224     *
225     * @return the tree in XML format
226     */
227    public String dump() {
228
229        try {
230            Document doc = Node.dumpTree(m_root);
231            OutputFormat outformat = OutputFormat.createPrettyPrint();
232            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
233            outformat.setEncoding("UTF-8");
234            XMLWriter writer = new XMLWriter(buffer, outformat);
235            writer.write(doc);
236            writer.flush();
237            return new String(buffer.toByteArray(), "UTF-8");
238        } catch (IOException e) {
239            // CAn't happen
240            return null;
241        }
242    }
243
244    /**
245     * Returns true if we received any data yet.<p>
246     *
247     * @return true if the handler has received data
248     */
249    public boolean hasData() {
250
251        return m_hasData;
252    }
253
254    /**
255     * @see org.opencms.db.timing.I_CmsProfilingHandler#putTime(java.lang.String, long)
256     */
257    public synchronized void putTime(String key, long nanos) {
258
259        m_hasData = true;
260        StackTraceElement[] traceElems = (new Throwable()).getStackTrace();
261        List<StackTraceElement> trace = Arrays.asList(traceElems);
262
263        List<Object> keys = new ArrayList<>();
264        // Since this handler is used during startup, which mostly happens in a single thread,
265        // we want to distinguish calls from different threads, e.g. scheduled jobs.
266        keys.add("THREAD " + Thread.currentThread().getId());
267        keys.addAll(Lists.reverse(trace));
268        // Cut off proxy-specific garbage from stack trace
269        keys = keys.subList(0, keys.size() - 3);
270        Node child = m_root.addOrGetDescendant(keys);
271        child.addCall(nanos);
272    }
273
274}