You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/10/18 23:24:54 UTC

[sling-org-apache-sling-reqanalyzer] 01/18: SLING-2516 Request Performance Analysis helper along with Java application to display the collected data

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-reqanalyzer.git

commit 51b2b9be95e71c226871c4047c78c78c97523c6e
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Fri Jun 22 06:29:14 2012 +0000

    SLING-2516 Request Performance Analysis helper along with Java application to display the collected data
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1352777 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            | 141 ++++++++++++++++
 .../reqanalyzer/impl/RequestAnalysisLogger.java    | 161 +++++++++++++++++++
 .../apache/sling/reqanalyzer/impl/gui/Main.java    |  59 +++++++
 .../sling/reqanalyzer/impl/gui/MainFrame.java      |  82 ++++++++++
 .../impl/gui/RequestListSelectionListener.java     | 107 +++++++++++++
 .../reqanalyzer/impl/gui/RequestTableModel.java    |  98 ++++++++++++
 .../reqanalyzer/impl/gui/RequestTrackerFile.java   | 105 ++++++++++++
 .../impl/gui/RequestTrackerFileEntry.java          | 118 ++++++++++++++
 .../apache/sling/reqanalyzer/impl/gui/Util.java    | 177 +++++++++++++++++++++
 9 files changed, 1048 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..0e99218
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>12</version>
+    </parent>
+
+    <artifactId>org.apache.sling.reqanalyzer</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling Request Processing Analyzer</name>
+    <description>
+        Helps analyzing the processing times of Sling
+        requests. Writes the following information into
+        a log file:
+           - request start timestamp
+           - request end timestamp
+           - request URL, user, response status
+           - request progresstracker          
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/reqanalyzer</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/reqanalyzer</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/reqanalyzer</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>maven-sling-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Category>sling</Bundle-Category>
+                        <Main-Class>
+                        	org.apache.sling.reqanalyzer.impl.gui.Main
+                        </Main-Class>
+                    </instructions>
+                </configuration>
+            </plugin>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>animal-sniffer-maven-plugin</artifactId>
+				<configuration>
+				  <signature>
+				    <groupId>org.codehaus.mojo.signature</groupId>
+				    <artifactId>java16</artifactId>
+				    <version>1.0</version>
+				  </signature>
+				</configuration>
+			</plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.engine</artifactId>
+            <version>2.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.settings</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.8.2</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/reqanalyzer/impl/RequestAnalysisLogger.java b/src/main/java/org/apache/sling/reqanalyzer/impl/RequestAnalysisLogger.java
new file mode 100644
index 0000000..bcf98c4
--- /dev/null
+++ b/src/main/java/org/apache/sling/reqanalyzer/impl/RequestAnalysisLogger.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.reqanalyzer.impl;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.Iterator;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper;
+import org.apache.sling.engine.EngineConstants;
+import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.framework.Constants;
+
+@Component(metatype = false)
+@Service
+@Properties({ @Property(name = EngineConstants.FILTER_NAME, value = "RequestAnalysisLogger"),
+        @Property(name = EngineConstants.SLING_FILTER_SCOPE, value = EngineConstants.FILTER_SCOPE_REQUEST),
+        @Property(name = Constants.SERVICE_RANKING, intValue = Integer.MAX_VALUE) })
+public class RequestAnalysisLogger implements Filter {
+
+    @Reference
+    private SlingSettingsService settings;
+
+    private BufferedWriter logFile;
+
+    @SuppressWarnings("unused")
+    @Activate
+    private void activate() throws IOException {
+        final File logFile = new File(settings.getSlingHomePath(), "logs/requesttracker.txt");
+        logFile.getParentFile().mkdirs();
+        final FileOutputStream out = new FileOutputStream(logFile, true);
+        this.logFile = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
+    }
+
+    @SuppressWarnings("unused")
+    @Deactivate
+    private void deactivate() throws IOException {
+        if (this.logFile != null) {
+            this.logFile.close();
+            this.logFile = null;
+        }
+    }
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
+            ServletException {
+
+        if (request instanceof SlingHttpServletRequest) {
+            final long start = System.currentTimeMillis();
+            final AnylserSlingHttpServletResponse slingRes = new AnylserSlingHttpServletResponse(
+                    (SlingHttpServletResponse) response);
+            try {
+                chain.doFilter(request, response);
+            } finally {
+                final long end = System.currentTimeMillis();
+                final SlingHttpServletRequest slingReq = (SlingHttpServletRequest) request;
+
+                StringBuilder pw = new StringBuilder(1024);
+                pw.append(String.format(":%d:%d:%s:%s:%s:%d%n", start, (end - start), slingReq.getMethod(),
+                        slingReq.getRequestURI(), slingRes.getContentType(), slingRes.getStatus()));
+
+                final Iterator<String> entries = slingReq.getRequestProgressTracker().getMessages();
+                while (entries.hasNext()) {
+                    pw.append('!').append(entries.next());
+                }
+
+                BufferedWriter out = this.logFile;
+                if (out != null) {
+                    out.write(pw.toString());
+                    out.flush();
+                }
+            }
+        } else {
+            chain.doFilter(request, response);
+        }
+    }
+
+    public void destroy() {
+    }
+
+    private static class AnylserSlingHttpServletResponse extends SlingHttpServletResponseWrapper {
+
+        private int status = 200;
+
+        public AnylserSlingHttpServletResponse(SlingHttpServletResponse wrappedResponse) {
+            super(wrappedResponse);
+        }
+
+        public int getStatus() {
+            return status;
+        }
+
+        @Override
+        public void setStatus(int sc) {
+            this.status = sc;
+            super.setStatus(sc);
+        }
+
+        @Override
+        public void setStatus(int sc, String sm) {
+            this.status = sc;
+            super.setStatus(sc, sm);
+        }
+
+        @Override
+        public void sendError(int sc) throws IOException {
+            this.status = sc;
+            super.sendError(sc);
+        }
+
+        @Override
+        public void sendError(int sc, String msg) throws IOException {
+            this.status = sc;
+            super.sendError(sc, msg);
+        }
+
+        @Override
+        public void sendRedirect(String location) throws IOException {
+            this.status = HttpServletResponse.SC_FOUND;
+            super.sendRedirect(location);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/reqanalyzer/impl/gui/Main.java b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/Main.java
new file mode 100644
index 0000000..c0efcca
--- /dev/null
+++ b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/Main.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.reqanalyzer.impl.gui;
+
+import java.awt.Dimension;
+import java.awt.GraphicsEnvironment;
+import java.awt.Toolkit;
+import java.io.File;
+import java.io.IOException;
+
+public class Main {
+
+    public static void main(String[] args) throws IOException {
+        if (GraphicsEnvironment.isHeadless()) {
+            System.err.println("Cannot run in headless mode");
+            System.exit(1);
+        }
+
+        if (args.length == 0) {
+            System.err.println("Missing argument <file>");
+            System.exit(1);
+        }
+
+        File file = new File(args[0]);
+        if (!file.canRead()) {
+            System.err.println("Cannot read from file " + file);
+            System.exit(1);
+        }
+
+        final int limit;
+        if (args.length >= 2) {
+            limit = Integer.parseInt(args[1]);
+        } else {
+            limit = Integer.MAX_VALUE;
+        }
+
+        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+
+        MainFrame frame = new MainFrame(file, limit, screenSize);
+        frame.setVisible(true);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/reqanalyzer/impl/gui/MainFrame.java b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/MainFrame.java
new file mode 100644
index 0000000..7feb4f1
--- /dev/null
+++ b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/MainFrame.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.reqanalyzer.impl.gui;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.table.JTableHeader;
+
+public class MainFrame extends JFrame {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String MAIN_Y = "main.y";
+    private static final String MAIN_X = "main.x";
+    private static final String MAIN_HEIGHT = "main.height";
+    private static final String MAIN_WIDTH = "main.width";
+    private static final String MAIN_COLS = "main.cols";
+
+    public MainFrame(final File file, final int limit, final Dimension screenSize) throws FileNotFoundException,
+            IOException {
+
+        JTextPane text = Util.showStartupDialog("Reading from " + file, screenSize);
+
+        final RequestTrackerFile dm = new RequestTrackerFile(file, limit, text);
+        
+        final JTable table = new JTable(dm);
+        table.setAutoCreateRowSorter(true);
+        table.setGridColor(Color.GRAY);
+        table.setShowGrid(true);
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        table.setRowSelectionAllowed(true);
+        table.setTableHeader(new JTableHeader(table.getColumnModel()));
+        Util.setupColumnWidths(table.getColumnModel(), MAIN_COLS);
+//        table.setFont(new Font("Monospaced", table.getFont().getStyle(), table.getFont().getSize()));
+        table.getSelectionModel().addListSelectionListener(new RequestListSelectionListener(this, table, screenSize));
+
+        add(new JScrollPane(table));
+
+        setTitle(file.getPath());
+
+        // exit the application if the main frame is closed
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(WindowEvent e) {
+                System.exit(0);
+            }
+        });
+
+        // setup location and size and ensure updating preferences
+        Util.setupComponentLocationSize(this, MAIN_X, MAIN_Y, MAIN_WIDTH, MAIN_HEIGHT, (int) screenSize.getWidth() / 4,
+                0, (int) screenSize.getWidth() / 2, (int) screenSize.getHeight() / 4);
+
+        Util.disposeStartupDialog(text);
+    }
+}
diff --git a/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestListSelectionListener.java b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestListSelectionListener.java
new file mode 100644
index 0000000..1c12ed2
--- /dev/null
+++ b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestListSelectionListener.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.reqanalyzer.impl.gui;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Window;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.IOException;
+
+import javax.swing.JDialog;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableModel;
+
+public class RequestListSelectionListener implements ListSelectionListener {
+
+    private static final String REQUEST_Y = "request.y";
+    private static final String REQUEST_X = "request.x";
+    private static final String REQUEST_HEIGHT = "request.height";
+    private static final String REQUEST_WIDTH = "request.width";
+    private static final String REQUEST_COLS = "request.cols";
+
+    private final Window parent;
+    private final JTable table;
+    private final Dimension screenSize;
+
+    private JTable dataField = null;
+
+    public RequestListSelectionListener(final Window parent, final JTable table, final Dimension screenSize) {
+        this.parent = parent;
+        this.table = table;
+        this.screenSize = screenSize;
+    }
+
+    public void valueChanged(ListSelectionEvent e) {
+        if (!e.getValueIsAdjusting()) {
+            ListSelectionModel lsm = (ListSelectionModel) e.getSource();
+            int idx = lsm.getMinSelectionIndex();
+            if (idx >= 0) {
+                try {
+                    idx = table.getRowSorter().convertRowIndexToModel(idx);
+                    TableModel tm = ((RequestTrackerFile) table.getModel()).getData(idx);
+                    if (dataField == null) {
+                        dataField = new JTable();
+                        dataField.setAutoCreateRowSorter(true);
+                        dataField.setGridColor(Color.GRAY);
+                        dataField.setShowGrid(true);
+                        dataField.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+                        dataField.setRowSelectionAllowed(true);
+                        dataField.setTableHeader(new JTableHeader(dataField.getColumnModel()));
+                        dataField.setFont(new Font("Monospaced", dataField.getFont().getStyle(), dataField.getFont().getSize()));
+                        dataField.setShowHorizontalLines(false);
+//                        dataField.setIntercellSpacing(new Dimension(3, 5));
+
+                        JDialog d = new JDialog(this.parent);
+                        d.add(new JScrollPane(dataField));
+
+                        d.addWindowListener(new WindowAdapter() {
+                            @Override
+                            public void windowClosing(WindowEvent e) {
+                                dataField = null;
+                            }
+                        });
+
+                        // setup location and size and ensure updating preferences
+                        Util.setupComponentLocationSize(d, REQUEST_X, REQUEST_Y, REQUEST_WIDTH, REQUEST_HEIGHT,
+                                (int) screenSize.getWidth() / 4, (int) screenSize.getHeight() / 4,
+                                (int) screenSize.getWidth() / 2, (int) screenSize.getHeight() / 2);
+
+                        d.setVisible(true);
+                    }
+
+                    dataField.setModel(tm);
+
+                    Util.setupColumnWidths(dataField.getColumnModel(), REQUEST_COLS);
+
+                } catch (IOException e1) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestTableModel.java b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestTableModel.java
new file mode 100644
index 0000000..078bdbe
--- /dev/null
+++ b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestTableModel.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.reqanalyzer.impl.gui;
+
+import java.util.ArrayList;
+
+import javax.swing.event.TableModelListener;
+import javax.swing.table.TableModel;
+
+public class RequestTableModel implements TableModel {
+
+    private final ArrayList<Object[]> rows = new ArrayList<Object[]>();
+
+    private long previousStamp;
+    
+    void addRow(String row) {
+        // split row: "%1$7d (%2$tF %2$tT) %3$s%n
+        
+        final String stampS = row.substring(0, 7);
+        final int endTimeStamp = row.indexOf(')');
+        final String message = row.substring(endTimeStamp+2); 
+        
+        long stamp = Long.parseLong(stampS.trim());
+        long delta = stamp - this.previousStamp;
+        this.previousStamp = stamp;
+        
+        final Object[] rowValue = new Object[]{ stamp, delta, message };
+        this.rows.add(rowValue);
+    }
+    
+    public int getRowCount() {
+        return rows.size();
+    }
+
+    public int getColumnCount() {
+        return 3;
+    }
+
+    public String getColumnName(int columnIndex) {
+        switch (columnIndex) {
+            case 0:
+                return "Timestamp";
+            case 1:
+                return "Delta";
+            case 2:
+                return "Message";
+            default:
+                throw new IllegalArgumentException("columnIndex=" + columnIndex);
+        }
+    }
+
+    public Class<?> getColumnClass(int columnIndex) {
+        switch (columnIndex) {
+            case 0:
+                return Long.class;
+            case 1:
+                return Long.class;
+            case 2:
+                return String.class;
+            default:
+                throw new IllegalArgumentException("columnIndex=" + columnIndex);
+        }
+    }
+
+    public boolean isCellEditable(int rowIndex, int columnIndex) {
+        return false;
+    }
+
+    public Object getValueAt(int rowIndex, int columnIndex) {
+        Object[] row = rows.get(rowIndex);
+        return row[columnIndex];
+    }
+
+    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+    }
+
+    public void addTableModelListener(TableModelListener l) {
+    }
+
+    public void removeTableModelListener(TableModelListener l) {
+    }
+}
diff --git a/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestTrackerFile.java b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestTrackerFile.java
new file mode 100644
index 0000000..7d20bec
--- /dev/null
+++ b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestTrackerFile.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.reqanalyzer.impl.gui;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JTextPane;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.TableModel;
+
+public class RequestTrackerFile implements TableModel {
+
+    private final RandomAccessFile raFile;
+    private final List<RequestTrackerFileEntry> entries;
+
+    RequestTrackerFile(File file, int limit, JTextPane text) throws FileNotFoundException, IOException {
+        this.raFile = new RandomAccessFile(file, "r");
+        this.entries = new ArrayList<RequestTrackerFileEntry>();
+        String line;
+        for (int i = 0; i < limit && (line = this.raFile.readLine()) != null; ) {
+            if (line.charAt(0) == ':') {
+                try {
+                    text.setText(line);
+                    RequestTrackerFileEntry entry = new RequestTrackerFileEntry(line, this.raFile.getFilePointer());
+                    this.entries.add(entry);
+                    i++;
+                } catch (Exception e) {
+                    System.err.println(e);
+                    e.printStackTrace(System.err);
+                    break;
+                }
+            }
+        }
+    }
+
+    TableModel getData(final int rowIndex) throws IOException {
+        final RequestTrackerFileEntry entry = this.entries.get(rowIndex);
+        final long offset = entry.getOffset();
+        RequestTableModel rtm = new RequestTableModel();
+        this.raFile.seek(offset);
+        String line;
+        while ((line = raFile.readLine()) != null && line.charAt(0) == '!') {
+            rtm.addRow(line.substring(1));
+        }
+        return rtm;
+    }
+    
+    public int getRowCount() {
+        return this.entries.size();
+    }
+
+    public int getColumnCount() {
+        return RequestTrackerFileEntry.getColumnCount();
+    }
+
+    public String getColumnName(int columnIndex) {
+        return RequestTrackerFileEntry.getColumnName(columnIndex);
+    }
+
+    public Class<?> getColumnClass(int columnIndex) {
+        return RequestTrackerFileEntry.getColumnClass(columnIndex);
+    }
+
+    public boolean isCellEditable(int rowIndex, int columnIndex) {
+        return false;
+    }
+
+    public Object getValueAt(int rowIndex, int columnIndex) {
+        RequestTrackerFileEntry entry = this.entries.get(rowIndex);
+        return entry.getField(columnIndex);
+    }
+
+    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+        throw new UnsupportedOperationException("setValue");
+    }
+
+    public void addTableModelListener(TableModelListener l) {
+        // not really ...
+    }
+
+    public void removeTableModelListener(TableModelListener l) {
+        // not really ...
+    }
+}
diff --git a/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestTrackerFileEntry.java b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestTrackerFileEntry.java
new file mode 100644
index 0000000..744e3d5
--- /dev/null
+++ b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/RequestTrackerFileEntry.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.reqanalyzer.impl.gui;
+
+public class RequestTrackerFileEntry {
+
+    /*
+     * Request Tracker Info Line:
+     * 
+     * :1340236961239:235:GET:/healthck/healthck.html:text/html; charset=UTF-8:200
+     */
+
+    private final long start;
+    private final long duration;
+    private final String method;
+    private final String url;
+    private final String contentType;
+    private final int status;
+    private final long offset;
+
+    static int getColumnCount() {
+        return 6;
+    }
+
+    static String getColumnName(final int idx) {
+        switch (idx) {
+            case 0:
+                return "Start";
+            case 1:
+                return "Duration";
+            case 2:
+                return "Method";
+            case 3:
+                return "URL";
+            case 4:
+                return "Content Type";
+            case 5:
+                return "Status";
+            default:
+                throw new IllegalArgumentException("Unknown field index " + idx);
+        }
+    }
+
+    static Class<?> getColumnClass(final int idx) {
+        switch (idx) {
+            case 0:
+                return Long.class;
+            case 1:
+                return Long.class;
+            case 2:
+                return String.class;
+            case 3:
+                return String.class;
+            case 4:
+                return String.class;
+            case 5:
+                return Integer.class;
+            default:
+                throw new IllegalArgumentException("Unknown field index " + idx);
+        }
+    }
+
+    RequestTrackerFileEntry(final String statusLine, final long offset) {
+        String[] parts = statusLine.split(":");
+        this.start = Long.parseLong(parts[1]);
+        this.duration = Long.parseLong(parts[2]);
+        this.method = parts[3];
+        
+        String url = parts[4];
+        for (int i = 5; i < parts.length - 2; i++) {
+            url += ":" + parts[i];
+        }
+        this.url = url;
+        
+        this.contentType = parts[parts.length-2];
+        this.status = Integer.parseInt(parts[parts.length-1]);
+        this.offset = offset;
+    }
+
+    Object getField(final int idx) {
+        switch (idx) {
+            case 0:
+                return start;
+            case 1:
+                return duration;
+            case 2:
+                return method;
+            case 3:
+                return url;
+            case 4:
+                return contentType;
+            case 5:
+                return status;
+            default:
+                throw new IllegalArgumentException("Unknown field index " + idx);
+        }
+    }
+
+    long getOffset() {
+        return offset;
+    }
+}
diff --git a/src/main/java/org/apache/sling/reqanalyzer/impl/gui/Util.java b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/Util.java
new file mode 100644
index 0000000..4f10539
--- /dev/null
+++ b/src/main/java/org/apache/sling/reqanalyzer/impl/gui/Util.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.reqanalyzer.impl.gui;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Window;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+
+import javax.swing.JDialog;
+import javax.swing.JTextPane;
+import javax.swing.table.TableColumnModel;
+
+public class Util {
+
+    private Util() {
+    }
+
+    static JTextPane showStartupDialog(final String title, final Dimension screenSize) {
+        JTextPane text = new JTextPane();
+        text.setText("...");
+
+        JDialog d = new JDialog((Window) null);
+        d.setTitle(title);
+        d.add(text);
+        d.setSize((int) screenSize.getWidth() / 2, 30);
+        d.setLocation((int) screenSize.getWidth() / 4, (int) screenSize.getHeight() / 2 - 15);
+        d.setVisible(true);
+
+        return text;
+    }
+
+    static void disposeStartupDialog(final Component comp) {
+        Container parent = comp.getParent();
+        while (parent != null && !(parent instanceof Window)) {
+            parent = parent.getParent();
+        }
+        if (parent instanceof Window) {
+            ((Window) parent).dispose();
+        }
+    }
+
+    static void setupComponentLocationSize(final Component comp, final String propX, final String propY,
+            final String propWidth, final String propHeight, final int defaultX, final int defaultY,
+            final int defaultWidth, final int defaultHeight) {
+
+        comp.setLocation(getPreference(propX, defaultY), getPreference(propY, defaultX));
+        comp.setSize(getPreference(propWidth, defaultWidth), getPreference(propHeight, defaultHeight));
+
+        comp.addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentMoved(ComponentEvent e) {
+                setPreference(propX, e.getComponent().getX(), false);
+                setPreference(propY, e.getComponent().getY(), true);
+            }
+
+            @Override
+            public void componentResized(ComponentEvent e) {
+                setPreference(propWidth, e.getComponent().getWidth(), false);
+                setPreference(propHeight, e.getComponent().getHeight(), true);
+            }
+        });
+    }
+
+    static void setupColumnWidths(final TableColumnModel tcm, final String propertyName) {
+        PropertyChangeListener pcl = new PropertyChangeListener() {
+            private final String pclPropName = propertyName;
+            private final TableColumnModel pclTcm = tcm;
+
+            public void propertyChange(PropertyChangeEvent evt) {
+                if ("width".equals(evt.getPropertyName())) {
+                    int[] colWidths = new int[pclTcm.getColumnCount()];
+                    for (int i = 0; i < colWidths.length; i++) {
+                        colWidths[i] = pclTcm.getColumn(i).getWidth();
+                    }
+                    setPreference(pclPropName, colWidths, true);
+                }
+            }
+        };
+
+        int[] colWidths = getPreference(propertyName, new int[0]);
+        for (int i = 0; i < colWidths.length && i < tcm.getColumnCount(); i++) {
+            tcm.getColumn(i).setPreferredWidth(colWidths[i]);
+        }
+        for (int i = 0; i < tcm.getColumnCount(); i++) {
+            tcm.getColumn(i).addPropertyChangeListener(pcl);
+        }
+    }
+
+    static void setPreference(final String name, final Object value, final boolean flush) {
+        Preferences prefs = getPreferences();
+        try {
+            prefs.sync();
+            if (value instanceof Long) {
+                prefs.putLong(name, (Long) value);
+            } else if (value instanceof Integer) {
+                prefs.putInt(name, (Integer) value);
+            } else if (value instanceof int[]) {
+                String string = null;
+                for (int val : (int[]) value) {
+                    if (string == null) {
+                        string = String.valueOf(val);
+                    } else {
+                        string += "," + val;
+                    }
+                }
+                prefs.put(name, string);
+            } else if (value != null) {
+                prefs.put(name, value.toString());
+            }
+
+            if (flush) {
+                prefs.flush();
+            }
+        } catch (BackingStoreException ioe) {
+            // ignore
+        }
+    }
+
+    static int getPreference(final String name, final int defaultValue) {
+        Preferences prefs = getPreferences();
+        try {
+            prefs.sync();
+            return prefs.getInt(name, defaultValue);
+        } catch (BackingStoreException ioe) {
+            // ignore
+        }
+        return defaultValue;
+    }
+
+    static int[] getPreference(final String name, final int[] defaultValues) {
+        Preferences prefs = getPreferences();
+        try {
+            prefs.sync();
+            String value = prefs.get(name, null);
+            if (value != null) {
+                String[] values = value.split(",");
+                int[] result = new int[values.length];
+                for (int i = 0; i < values.length; i++) {
+                    result[i] = Integer.parseInt(values[i]);
+                }
+                return result;
+            }
+        } catch (BackingStoreException ioe) {
+            // ignore
+        } catch (NumberFormatException nfe) {
+            // ignore
+        }
+        return defaultValues;
+    }
+
+    static Preferences getPreferences() {
+        return Preferences.userNodeForPackage(Util.class);
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.