001: /*
002: * Copyright (C) The Apache Software Foundation. All rights reserved.
003: *
004: * This software is published under the terms of the Apache Software
005: * License version 1.1, a copy of which has been included with this
006: * distribution in the APACHE.txt file. */
007: package org.jahia.sqlprofiler.gui;
008:
009: import java.text.DateFormat;
010: import java.util.ArrayList;
011: import java.util.Comparator;
012: import java.util.Date;
013: import java.util.Iterator;
014: import java.util.List;
015: import java.util.SortedSet;
016: import java.util.TreeSet;
017: import javax.swing.table.AbstractTableModel;
018: import org.apache.log4j.Priority;
019: import org.apache.log4j.Category;
020: import org.jahia.sqlprofiler.QueryStatistics;
021: import java.util.StringTokenizer;
022: import java.util.NoSuchElementException;
023: import org.jahia.sqlprofiler.QueryStatEntry;
024: import java.text.NumberFormat;
025: import java.io.StringWriter;
026: import java.io.PrintWriter;
027: import org.jahia.sqlprofiler.QueryEntry;
028: import java.util.Set;
029: import java.io.FileWriter;
030: import java.util.*;
031: import java.io.IOException;
032: import java.text.SimpleDateFormat;
033:
034: /**
035: * Represents a list of <code>EventDetails</code> objects that are sorted on
036: * logging time. Methods are provided to filter the events that are visible.
037: *
038: * @author <a href="mailto:oliver@puppycrawl.com">Oliver Burn</a>
039: */
040: class LoggerTableModel extends AbstractTableModel {
041:
042: /** used to log messages **/
043: private static final Category LOG = Category
044: .getInstance(LoggerTableModel.class);
045:
046: /** names of the columns in the table **/
047: private static final String[] COL_NAMES = { "Time", "Priority",
048: "Trace", "Category", "NDC", "Message" };
049:
050: /** definition of an empty list **/
051: private static final EventDetails[] EMPTY_LIST = new EventDetails[] {};
052:
053: /** used to format dates **/
054: private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat(
055: "yyyy.MM.dd hh:mm:ss.SSS");
056:
057: /** the lock to control access **/
058: private final Object mLock = new Object();
059: /** set of all logged events - not filtered **/
060: private final SortedSet mAllEvents = new TreeSet(MY_COMP);
061: /** events that are visible after filtering **/
062: private EventDetails[] mFilteredEvents = EMPTY_LIST;
063: /** list of events that are buffered for processing **/
064: private final List mPendingEvents = new ArrayList();
065: /** indicates whether event collection is paused to the UI **/
066: private boolean mPaused = false;
067:
068: /** filter for the thread **/
069: private String mThreadFilter = "";
070: /** filter for the message **/
071: private String mMessageFilter = "";
072: /** filter for the NDC **/
073: private String mNDCFilter = "";
074: /** filter for the category **/
075: private String mCategoryFilter = "";
076: /** filter for the priority **/
077: private Priority mPriorityFilter = Priority.DEBUG;
078:
079: private ProfileStatementTableModel profileStatementModel = null;
080:
081: private QueryCountChartModel queryCountChartModel = null;
082: private QueryTrafficChartModel queryTrafficChartModel = null;
083:
084: /** use the compare logging events **/
085: private static final Comparator MY_COMP = new Comparator() {
086: /** @see Comparator **/
087: public int compare(Object aObj1, Object aObj2) {
088: if ((aObj1 == null) && (aObj2 == null)) {
089: return 0; // treat as equal
090: } else if (aObj1 == null) {
091: return -1; // null less than everything
092: } else if (aObj2 == null) {
093: return 1; // think about it. :->
094: }
095:
096: // will assume only have LoggingEvent
097: final EventDetails le1 = (EventDetails) aObj1;
098: final EventDetails le2 = (EventDetails) aObj2;
099:
100: if (le1.getTimeStamp() < le2.getTimeStamp()) {
101: return 1;
102: }
103: // assume not two events are logged at exactly the same time
104: return -1;
105: }
106: };
107:
108: /**
109: * Helper that actually processes incoming events.
110: * @author <a href="mailto:oliver@puppycrawl.com">Oliver Burn</a>
111: */
112: private class Processor implements Runnable {
113: /** loops getting the events **/
114: public void run() {
115: while (true) {
116: try {
117: Thread.sleep(1000);
118: } catch (InterruptedException e) {
119: // ignore
120: }
121:
122: synchronized (mLock) {
123: if (mPaused) {
124: continue;
125: }
126:
127: boolean toHead = true; // were events added to head
128: boolean needUpdate = false;
129: boolean needUpdateProfiler = false;
130: final Iterator it = mPendingEvents.iterator();
131: while (it.hasNext()) {
132: final EventDetails event = (EventDetails) it
133: .next();
134: mAllEvents.add(event);
135: if (event.getCategoryName().equals("p6spy")) {
136: needUpdateProfiler = true;
137: if (profileStatementModel != null) {
138: profileStatementModel
139: .processP6Event(event
140: .getMessage());
141: }
142: }
143: toHead = toHead
144: && (event == mAllEvents.first());
145: needUpdate = needUpdate || matchFilter(event);
146: }
147: if (queryCountChartModel != null) {
148: queryCountChartModel.update();
149: }
150: if (queryTrafficChartModel != null) {
151: queryTrafficChartModel.update();
152: }
153: if ((profileStatementModel != null)
154: && (needUpdateProfiler)) {
155: profileStatementModel.sortAndUpdateTable();
156: profileStatementModel.updateQueryStatsDisplay();
157: }
158: mPendingEvents.clear();
159:
160: if (needUpdate) {
161: updateFilteredEvents(toHead);
162: }
163: }
164: }
165:
166: }
167: }
168:
169: /**
170: * Creates a new <code>MyTableModel</code> instance.
171: *
172: */
173: LoggerTableModel() {
174: final Thread t = new Thread(new Processor());
175: t.setDaemon(true);
176: t.start();
177: }
178:
179: ////////////////////////////////////////////////////////////////////////////
180: // Table Methods
181: ////////////////////////////////////////////////////////////////////////////
182:
183: /** @see javax.swing.table.TableModel **/
184: public int getRowCount() {
185: synchronized (mLock) {
186: return mFilteredEvents.length;
187: }
188: }
189:
190: /** @see javax.swing.table.TableModel **/
191: public int getColumnCount() {
192: // does not need to be synchronized
193: return COL_NAMES.length;
194: }
195:
196: /** @see javax.swing.table.TableModel **/
197: public String getColumnName(int aCol) {
198: // does not need to be synchronized
199: return COL_NAMES[aCol];
200: }
201:
202: /** @see javax.swing.table.TableModel **/
203: public Class getColumnClass(int aCol) {
204: // does not need to be synchronized
205: return (aCol == 2) ? Boolean.class : Object.class;
206: }
207:
208: /** @see javax.swing.table.TableModel **/
209: public Object getValueAt(int aRow, int aCol) {
210: synchronized (mLock) {
211: final EventDetails event = mFilteredEvents[aRow];
212:
213: if (aCol == 0) {
214: return DATE_FORMATTER.format(new Date(event
215: .getTimeStamp()));
216: } else if (aCol == 1) {
217: return event.getPriority();
218: } else if (aCol == 2) {
219: return (event.getThrowableStrRep() == null) ? Boolean.FALSE
220: : Boolean.TRUE;
221: } else if (aCol == 3) {
222: return event.getCategoryName();
223: } else if (aCol == 4) {
224: return event.getNDC();
225: }
226: return event.getMessage();
227: }
228: }
229:
230: ////////////////////////////////////////////////////////////////////////////
231: // Public Methods
232: ////////////////////////////////////////////////////////////////////////////
233:
234: /**
235: * Sets the priority to filter events on. Only events of equal or higher
236: * property are now displayed.
237: *
238: * @param aPriority the priority to filter on
239: */
240: public void setPriorityFilter(Priority aPriority) {
241: synchronized (mLock) {
242: mPriorityFilter = aPriority;
243: updateFilteredEvents(false);
244: }
245: }
246:
247: /**
248: * Set the filter for the thread field.
249: *
250: * @param aStr the string to match
251: */
252: public void setThreadFilter(String aStr) {
253: synchronized (mLock) {
254: mThreadFilter = aStr.trim();
255: updateFilteredEvents(false);
256: }
257: }
258:
259: /**
260: * Set the filter for the message field.
261: *
262: * @param aStr the string to match
263: */
264: public void setMessageFilter(String aStr) {
265: synchronized (mLock) {
266: mMessageFilter = aStr.trim();
267: updateFilteredEvents(false);
268: }
269: }
270:
271: /**
272: * Set the filter for the NDC field.
273: *
274: * @param aStr the string to match
275: */
276: public void setNDCFilter(String aStr) {
277: synchronized (mLock) {
278: mNDCFilter = aStr.trim();
279: updateFilteredEvents(false);
280: }
281: }
282:
283: /**
284: * Set the filter for the category field.
285: *
286: * @param aStr the string to match
287: */
288: public void setCategoryFilter(String aStr) {
289: synchronized (mLock) {
290: mCategoryFilter = aStr.trim();
291: updateFilteredEvents(false);
292: }
293: }
294:
295: /**
296: * Add an event to the list.
297: *
298: * @param aEvent a <code>EventDetails</code> value
299: */
300: public void addEvent(EventDetails aEvent) {
301: synchronized (mLock) {
302: mPendingEvents.add(aEvent);
303: }
304: }
305:
306: /**
307: * Clear the list of all events.
308: */
309: public void clear() {
310: synchronized (mLock) {
311: mAllEvents.clear();
312: mFilteredEvents = new EventDetails[0];
313: mPendingEvents.clear();
314: if (profileStatementModel != null) {
315: profileStatementModel.clear();
316: profileStatementModel.fireTableDataChanged();
317: }
318: fireTableDataChanged();
319: }
320: }
321:
322: /** Toggle whether collecting events **/
323: public void toggle() {
324: synchronized (mLock) {
325: mPaused = !mPaused;
326: if (mPaused) {
327: if (profileStatementModel != null) {
328: profileStatementModel.displayOccurenceStats(false);
329: }
330: }
331: }
332: }
333:
334: /** @return whether currently paused collecting events **/
335: public boolean isPaused() {
336: synchronized (mLock) {
337: return mPaused;
338: }
339: }
340:
341: /**
342: * Get the throwable information at a specified row in the filtered events.
343: *
344: * @param aRow the row index of the event
345: * @return the throwable information
346: */
347: public EventDetails getEventDetails(int aRow) {
348: synchronized (mLock) {
349: return mFilteredEvents[aRow];
350: }
351: }
352:
353: public void setProfileStatementModel(
354: ProfileStatementTableModel profileStatementModel) {
355: this .profileStatementModel = profileStatementModel;
356: }
357:
358: public void setQueryCountChartModel(
359: QueryCountChartModel queryCountChartModel) {
360: this .queryCountChartModel = queryCountChartModel;
361: }
362:
363: public void setQueryTrafficChartModel(
364: QueryTrafficChartModel queryTrafficChartModel) {
365: this .queryTrafficChartModel = queryTrafficChartModel;
366: }
367:
368: ////////////////////////////////////////////////////////////////////////////
369: // Private methods
370: ////////////////////////////////////////////////////////////////////////////
371:
372: /**
373: * Update the filtered events data structure.
374: * @param aInsertedToFront indicates whether events were added to front of
375: * the events. If true, then the current first event must still exist
376: * in the list after the filter is applied.
377: */
378: private void updateFilteredEvents(boolean aInsertedToFront) {
379: final long start = System.currentTimeMillis();
380: final List filtered = new ArrayList();
381: final int size = mAllEvents.size();
382: final Iterator allEventIter = mAllEvents.iterator();
383:
384: while (allEventIter.hasNext()) {
385: final EventDetails event = (EventDetails) allEventIter
386: .next();
387: if (matchFilter(event)) {
388: filtered.add(event);
389: }
390: }
391:
392: final EventDetails lastFirst = (mFilteredEvents.length == 0) ? null
393: : mFilteredEvents[0];
394: mFilteredEvents = (EventDetails[]) filtered.toArray(EMPTY_LIST);
395:
396: if (aInsertedToFront && (lastFirst != null)) {
397: final int index = filtered.indexOf(lastFirst);
398: if (index < 1) {
399: LOG.warn("In strange state");
400: fireTableDataChanged();
401: } else {
402: fireTableRowsInserted(0, index - 1);
403: }
404: } else {
405: fireTableDataChanged();
406: }
407:
408: final long end = System.currentTimeMillis();
409: LOG.debug("Total time [ms]: " + (end - start)
410: + " in update, size: " + size);
411: }
412:
413: /**
414: * Returns whether an event matches the filters.
415: *
416: * @param aEvent the event to check for a match
417: * @return whether the event matches
418: */
419: private boolean matchFilter(EventDetails aEvent) {
420: if (aEvent.getPriority().isGreaterOrEqual(mPriorityFilter)
421: && (aEvent.getThreadName().indexOf(mThreadFilter) >= 0)
422: && (aEvent.getCategoryName().indexOf(mCategoryFilter) >= 0)
423: && ((mNDCFilter.length() == 0) || ((aEvent.getNDC() != null) && (aEvent
424: .getNDC().indexOf(mNDCFilter) >= 0)))) {
425: final String rm = aEvent.getMessage();
426: if (rm == null) {
427: // only match if we have not filtering in place
428: return (mMessageFilter.length() == 0);
429: } else {
430: return (rm.indexOf(mMessageFilter) >= 0);
431: }
432: }
433:
434: return false; // by default not match
435: }
436:
437: }
|