blob: 874d4039ba634a44c6fd2f240c3ef9c740e9171c [file] [log] [blame]
/*
* 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.jackrabbit.oak.query;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Locale;
import java.util.TreeMap;
/**
* A simple standalone tool to analyze a log file and list slow queries.
*/
public class QueryLogAnalyzer {
public static void main(String... args) throws IOException {
if (args.length == 0) {
System.out.println("Usage: java ... <logFile> [<logFile2> ...]");
return;
}
for (String arg : args) {
analyze(arg);
}
}
private static void analyze(String fileName) throws IOException {
LineNumberReader r = new LineNumberReader(new FileReader(fileName));
int totalLineCount = 0, traversedLineCount = 0;
TreeMap<String, QueryStats> queries = new TreeMap<String, QueryStats>();
while (true) {
String line = r.readLine();
if (line == null) {
break;
}
totalLineCount++;
String number = retrieve(line, "Traversed ", " nodes ");
if (number == null) {
continue;
}
traversedLineCount++;
line = line.substring(line.indexOf("Traversed " + number + " nodes "));
String index = retrieve(line, "nodes using index ", " with filter");
String rawQuery = retrieve(line, "Filter(query=", null);
if (rawQuery == null) {
System.out.println("Unknown line: " + line);
}
String rawXpath = retrieve(rawQuery, "/* xpath: ", "*/, ");
String query = filterParams(rawQuery);
if (rawXpath != null) {
rawQuery = rawXpath;
query = filterParams(rawXpath);
}
String key = query;
QueryStats q = queries.get(key);
if (q == null) {
q = new QueryStats();
queries.put(key, q);
q.query = query;
}
q.index = index;
q.lineCount++;
int nodes = Integer.parseInt(number);
if (nodes > q.maxNodeCount) {
q.longestQuery = rawQuery;
q.maxNodeCount = nodes;
}
if (q.lastNodeCount == 0 || nodes < q.lastNodeCount) {
// start
q.runCount++;
q.lastNodeCount = 0;
} else {
// continuation
}
q.nodeCount += nodes - q.lastNodeCount;
q.lastNodeCount = nodes;
}
System.out.println("File: " + fileName);
System.out.println("Lines: " + totalLineCount);
System.out.println("Lines with 'Traversed': " + traversedLineCount);
ArrayList<QueryStats> list = new ArrayList<QueryStats>();
list.addAll(queries.values());
Collections.sort(list);
for (QueryStats q : list) {
System.out.println();
System.out.println(" Query: " + formatQuery(q.query));
if (!q.query.equals(q.longestQuery)) {
System.out.println(" Longest: " + formatQuery(q.longestQuery));
}
if (q.index != null) {
System.out.println(" Index: " + q.index);
}
System.out.printf(" %,d nodes traversed; " +
"ran %,d times, max %,d nodes, %,d lines\n",
q.nodeCount,
q.runCount, q.maxNodeCount, q.lineCount
);
}
System.out.println();
r.close();
}
private static String formatQuery(String q) {
if (q.toLowerCase(Locale.ENGLISH).indexOf("select ") >= 0) {
// (?i) means case insensitive
q = q.replaceAll("(?i)from ", "\n from ");
q = q.replaceAll("(?i)where ", "\n where ");
q = q.replaceAll("(?i)and ", "\n and ");
q = q.replaceAll("(?i)or ", "\n or ");
q = q.replaceAll("(?i)order by ", "\n order by ");
} else {
q = q.replaceAll("\\[", "\n [");
q = q.replaceAll("and ", "\n and ");
q = q.replaceAll("or ", "\n or ");
q = q.replaceAll("order by ", "\n order by ");
}
return q;
}
private static String filterParams(String x) {
// uuid
x = x.replaceAll("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
"<uuid>");
// timestamp
x = x.replaceAll("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]*[Z]",
"<timestamp>");
// number
x = x.replaceAll("[0-9]+",
"0");
// timestamp
x = x.replaceAll("<timestamp>",
"2000-01-01T00:00:00.000Z");
// uuid
x = x.replaceAll("<uuid>",
"12345678-1234-1234-123456789012");
return x;
}
private static String retrieve(String s, String prefix, String suffix) {
int start = s.indexOf(prefix);
if (start < 0) {
return null;
}
start += prefix.length();
int end;
if (suffix == null) {
end = s.length();
} else {
end = s.indexOf(suffix, start);
if (end < 0) {
return null;
}
}
return s.substring(start, end);
}
static class QueryStats implements Comparable<QueryStats> {
String query;
String longestQuery;
String index;
int lastNodeCount;
int lineCount;
int nodeCount;
int runCount;
int maxNodeCount;
@Override
public int compareTo(QueryStats o) {
int comp = Long.signum(o.nodeCount - nodeCount);
if (comp == 0) {
comp = Long.signum(o.runCount - runCount);
}
if (comp == 0) {
comp = o.query.compareTo(query);
}
return comp;
}
}
}