/*
* #!
* Ontopia Engine
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* 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 net.ontopia.utils;
import java.io.Writer;
import java.io.IOException;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
import java.util.Comparator;
import java.text.DecimalFormat;
/**
* INTERNAL: Statistics collector for profiling queries, whether tolog
* or SQL.
*/
public class QueryProfiler {
static DecimalFormat df = new DecimalFormat("0.#");
Map<String, Event> eStats;
/**
* Tracks whether traversal events have been seen. The tolog profiler does
* not provide these, and tracking this lets us leave out two unnecessary
* columns in the report.
*/
boolean traverse;
public QueryProfiler() {
eStats = new HashMap<String, Event>();
traverse = false;
}
public synchronized void clear() {
eStats.clear();
}
public synchronized void recordExecute(String ename, long start, long end) {
Event event = getEvent(ename);
event.addExecute(start, end);
}
public synchronized void recordExecuteUpdate(String ename,
int affectedSize,
long start, long end) {
Event event = getEvent(ename);
event.addExecuteUpdate(start, end, affectedSize);
}
public synchronized void recordTraverse(String ename, boolean hasNext,
long start, long end) {
Event event = getEvent(ename);
event.addTraverse(hasNext, start, end);
traverse = true;
}
// -- Utilities
private Event getEvent(String ename) {
Event event = eStats.get(ename);
if (event == null) {
event = new Event(ename);
eStats.put(ename, event);
}
return event;
}
// -- report generation
public synchronized void generateReport(String title, Writer out)
throws IOException {
out.write("<h1>" + title + "</h1>\n");
out.write("<table>\n");
out.write("<tr><th> <th>Query <th colspan=\"2\">Total time <th>Avg <th>Min <th>Max");
out.write("<th>Times run\n");
if (traverse)
out.write("<th>Traverse time <th>Rows\n");
Object[] events = eStats.values().toArray();
Arrays.sort(events, new EventComparator());
// calculate total time
long executeTime = 0;
long executeNum = 0;
long traverseTime = 0;
long traverseNum = 0;
for (int i=0; i < events.length; i++) {
Event event = (Event)events[i];
executeTime += event.executeTime;
executeNum += event.executeNum;
traverseTime += event.traverseTime;
traverseNum += event.traverseNum;
}
// output data
for (int i=0; i < events.length; i++) {
Event event = (Event)events[i];
out.write("<tr><td>");
out.write(Integer.toString(i+1));
out.write(". <td class=\"event\">");
out.write(event.toString());
out.write("<td>");
out.write(Long.toString(event.executeTime));
out.write("<td>");
out.write(df.format((1.0f*event.executeTime / executeTime) * 100));
out.write("% <td>");
out.write(df.format((1.0f*event.executeTime)/event.executeNum));
out.write("<td>");
out.write(df.format(event.executeTimeMin));
out.write("<td>");
out.write(df.format(event.executeTimeMax));
out.write("<td>");
out.write(Long.toString(event.executeNum));
if (traverse) {
out.write("<td>");
out.write(Long.toString(event.traverseTime));
out.write("<td>");
out.write(Long.toString(event.traverseNum));
}
out.write("</tr>\n");
}
out.write("</table>\n");
out.write("<p><b>Statements</b>: ");
out.write(Long.toString(executeNum));
out.write(", <b>Total time</b>: ");
out.write(Long.toString(executeTime));
out.write(" ms, <b>Total traverse time</b>: ");
out.write(Long.toString(traverseTime));
out.write(" ms, <b>Average</b>: ");
out.write(df.format((1.0f*executeTime)/executeNum));
out.write(" ms</p>\n");
out.write("<p>Report generated: ");
out.write(new java.util.Date().toString());
out.write(", ");
out.write(net.ontopia.Ontopia.getInfo());
out.write("</p>\n");
}
// --- Event
static class Event {
String name;
long executeNum;
long executeTime;
float executeTimeMin = -1.0f;
float executeTimeMax = -1.0f;
long traverseTime;
long traverseNum;
Event(String name) {
this.name = name;
}
void addExecuteUpdate(long startTime, long endTime, int affectedSize) {
addExecute(startTime, endTime);
traverseNum += affectedSize;
}
void addExecute(long startTime, long endTime) {
int time = (int)(endTime - startTime);
if (executeTimeMax == -1.0f || time > executeTimeMax)
executeTimeMax = (float)time;
if (executeTimeMin == -1.0f || time < executeTimeMin)
executeTimeMin = (float)time;
executeTime = executeTime + time;
executeNum++;
}
void addExecute(long startTime, long endTime, int executeCount) {
int time = (int)(endTime - startTime);
float timeAvg = (time/(executeCount*1.0f));
if (executeTimeMax == -1.0f || timeAvg > executeTimeMax)
executeTimeMax = timeAvg;
if (executeTimeMin == -1.0f || timeAvg < executeTimeMin)
executeTimeMin = timeAvg;
executeTime = executeTime + time;
executeNum += executeCount;
}
void addTraverse(boolean hasNext, long startTime, long endTime) {
int time = (int)(endTime - startTime);
executeTime = executeTime + time;
traverseTime = traverseTime + time;
if (hasNext) traverseNum++;
}
public String toString() {
if (name == null)
return null;
else
return StringUtils.escapeHTMLEntities(name);
}
}
static class EventComparator implements Comparator {
public int compare(Object o1, Object o2) {
Event e1 = (Event)o1;
Event e2 = (Event)o2;
if (e1 == null) return -1;
if (e2 == null) return 1;
long e1time = e1.executeTime;
long e2time = e2.executeTime;
return (e1time > e2time ? -1 : (e1time < e2time ? 1 : 0));
}
}
}