package com.insightfullogic.honest_profiler.ports.javafx.util.report;
import static com.insightfullogic.honest_profiler.ports.javafx.util.report.Table.Alignment.LEFT;
import static com.insightfullogic.honest_profiler.ports.javafx.util.report.Table.Alignment.RIGHT;
import static java.text.NumberFormat.getPercentInstance;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.insightfullogic.honest_profiler.core.aggregation.result.diff.DiffEntry;
import com.insightfullogic.honest_profiler.core.aggregation.result.diff.FlatDiff;
import com.insightfullogic.honest_profiler.core.aggregation.result.straight.Entry;
import com.insightfullogic.honest_profiler.core.aggregation.result.straight.Flat;
import com.insightfullogic.honest_profiler.core.aggregation.result.straight.Node;
import com.insightfullogic.honest_profiler.core.profiles.ProfileNode;
/**
* Utility class for creating various text reports (either ASCII-art based or CSV) based on profile data.
*/
public final class ReportUtil
{
// Class Properties
public static enum Mode
{
TEXT("", " ", ""), CSV("\"", "\",\"", "\"");
private String start;
private String middle;
private String end;
private Mode(String start, String middle, String end)
{
this.start = start;
this.middle = middle;
this.end = end;
}
private PrintWriter start(PrintWriter out)
{
out.print(start);
return out;
}
private PrintWriter middle(PrintWriter out)
{
out.print(middle);
return out;
}
private PrintWriter end(PrintWriter out)
{
out.println(end);
return out;
}
}
// - Special characters
private static char BOX_HORIZONTAL = 0x2500;
private static char BOX_VERTICAL = 0x2502;
private static char BOX_UPANDRIGHT = 0x2514;
private static char BOX_VERTICALANDRIGHT = 0x251C;
// - Predefined character sequences
private static String DROP_STRAIGHT = new String(new char[]
{ BOX_VERTICAL, ' ' });
private static String DROP_NONLAST = new String(new char[]
{ BOX_VERTICALANDRIGHT, BOX_HORIZONTAL });
private static String DROP_LAST = new String(new char[]
{ BOX_UPANDRIGHT, BOX_HORIZONTAL });
// - Formatter which renders percentages with 2 digits after the dot.
private static NumberFormat FMT_PERCENT;
// Class Constructors
// - Initialize the FMT_PERCENT NumberFormat
static
{
FMT_PERCENT = getPercentInstance();
FMT_PERCENT.setMinimumFractionDigits(2);
FMT_PERCENT.setMaximumFractionDigits(2);
}
/**
* Writes a stack (fragment) with the specified {@link Node} as root to the specified {@link PrintWriter}. Nicely
* formatted at that, using droplines.
* <p>
* TODO FIX - {@link ProfileNode}s are no longer used.
* <p>
*
* @param out the {@link PrintWriter} to wite the stack to
* @param node the root {@link Node} of the stack (fragment)
*/
public static void writeStack(PrintWriter out, Node node)
{
Table table = new Table();
table.addColumn("Method", obj -> obj.toString(), LEFT);
table.addColumn("Self Time %", object -> FMT_PERCENT.format(object), RIGHT);
table.addColumn("Total Time %", object -> FMT_PERCENT.format(object), RIGHT);
table.addColumn("Self Sample #", object -> Integer.toString((Integer)object), RIGHT);
table.addColumn("Total Sample #", object -> Integer.toString((Integer)object), RIGHT);
buildStackTable(node, 0, table, new ArrayList<>());
table.print(out);
}
/**
* Helper method which recursively adds {@link Node}s into a {@link Table}
* <p>
* TODO FIX - {@link ProfileNode}s are no longer used.
* <p>
*
* @param node the root {@link Node} being added to the {@link Table}
* @param level the depth the root {@link Node} is at
* @param table the {@link Table} the {@link Node} will be added to
* @param dropLines a {@link List} containing the {@link DropLine}s to be added left of the {@link Node}
* information.
*/
private static void buildStackTable(Node node, int level, Table table, List<DropLine> dropLines)
{
table.addRow(
indent(dropLines, level) + node.getKey(),
node.getSelfTimePct(),
node.getTotalTimePct(),
node.getSelfCnt(),
node.getTotalCnt());
if (level > 0)
{
dropLines.get(level - 1).decrement();
}
List<Node> children = node.getChildren();
if (children.size() > 0)
{
dropLines.add(new DropLine(children.size()));
children.forEach(child -> buildStackTable(child, level + 1, table, dropLines));
dropLines.remove(level);
}
}
// public static void writeThread(PrintWriter out, ProfileTree thread, Mode mode)
// {
// // TODO Implement
// }
/**
* Write the contents of a {@link Flat} aggregation to a CSV or text file, depending on the specified {@link Mode}.
* <p>
*
* @param out the {@link PrintWriter} to wite the data to
* @param entries the data to be written
* @param mode the {@link Mode} for formatting the output
*/
public static void writeFlatProfileCsv(PrintWriter out, List<Entry> entries, Mode mode)
{
mode.start(out);
out.print("Key");
mode.middle(out);
out.print("Self %");
mode.middle(out);
out.print("Total %");
mode.middle(out);
out.print("Self #");
mode.middle(out);
out.print("Total #");
mode.end(out);
entries.forEach(entry ->
{
mode.start(out);
out.print(entry.getKey());
mode.middle(out);
out.printf("%.4f", entry.getSelfCntPct());
mode.middle(out);
out.printf("%.4f", entry.getTotalCntPct());
mode.middle(out);
out.printf("%d", entry.getSelfCnt());
mode.middle(out);
out.printf("%d", entry.getTotalCnt());
mode.end(out);
});
out.flush();
}
/**
* Write the contents of a {@link FlatDiff} aggregation to a CSV or text file, depending on the specified
* {@link Mode}.
* <p>
*
* @param out the {@link PrintWriter} to wite the data to
* @param entries the data to be written
* @param mode the {@link Mode} for formatting the output
*/
public static void writeFlatProfileDiffCsv(PrintWriter out, Collection<DiffEntry> entries,
Mode mode)
{
mode.start(out);
out.print("Key");
mode.middle(out);
out.print("Base Self %");
mode.middle(out);
out.print("New Self %");
mode.middle(out);
out.print("Self % Diff");
mode.middle(out);
out.print("Base Total %");
mode.middle(out);
out.print("New Total %");
mode.middle(out);
out.print("Total % Diff");
mode.middle(out);
out.print("Base Self #");
mode.middle(out);
out.print("New Self #");
mode.middle(out);
out.print("Self # Diff");
mode.middle(out);
out.print("Base Total #");
mode.middle(out);
out.print("New Total #");
mode.middle(out);
out.print("Total # Diff");
mode.end(out);
entries.forEach(entry ->
{
mode.start(out);
out.write(entry.getKey());
mode.middle(out);
out.printf("%.4f", entry.getBaseSelfCntPct());
mode.middle(out);
out.printf("%.4f", entry.getNewSelfCntPct());
mode.middle(out);
out.printf("%.4f", entry.getSelfCntPctDiff());
mode.middle(out);
out.printf("%.4f", entry.getBaseTotalCntPct());
mode.middle(out);
out.printf("%.4f", entry.getNewTotalCntPct());
mode.middle(out);
out.printf("%.4f", entry.getTotalCntPctDiff());
mode.middle(out);
out.printf("%d", entry.getBaseSelfCnt());
mode.middle(out);
out.printf("%d", entry.getNewSelfCnt());
mode.middle(out);
out.printf("%d", entry.getSelfCntDiff());
mode.middle(out);
out.printf("%d", entry.getBaseTotalCnt());
mode.middle(out);
out.printf("%d", entry.getNewTotalCnt());
mode.middle(out);
out.printf("%d", entry.getTotalCntDiff());
mode.end(out);
});
out.flush();
}
/**
* Internal helper method for indenting stack frames, preceded by the specified {@link DropLine}s.
* <p>
*
* @param dropLines the {@link DropLine}s to be rendered
* @param level the indentation level
* @return a String containing the rendered {@link DropLine}s and whitespace to be used as prefix for indenting the
* stack frame information.
*/
private static String indent(List<DropLine> dropLines, int level)
{
StringBuilder result = new StringBuilder();
for (int i = 0; i < level - 1; i++)
{
result.append(dropLines.get(i).childrenLeft > 0 ? DROP_STRAIGHT : " ");
}
if (level > 0)
{
result.append(dropLines.get(level - 1).childrenLeft > 1 ? DROP_NONLAST : DROP_LAST);
}
return result.toString();
}
/**
* Helper class which manages the offset of a dropline to be rendered.
* <p>
* TODO Unfortunately I've completely forgotten how the algorithm I came up with works. Figure it out sometime and
* update the documentation accordingly.
*/
private static class DropLine
{
// Instance Properties
private int childrenLeft;
/**
* Constructor.
* <p>
*
* @param childrenLeft some parameter
*/
private DropLine(int childrenLeft)
{
this.childrenLeft = childrenLeft;
}
/**
* Decrement method.
*/
private void decrement()
{
childrenLeft--;
}
}
/**
* Empty Constructor for utility class.
*/
private ReportUtil()
{
// Empty Constructor for utility class
}
}