From cf15dff5fb4dcd1ede573978b59e78fbce2a8864 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Thu, 2 Feb 2023 13:02:33 +0100 Subject: [PATCH 1/2] NEW: Ebean can generate HtmlMetricReports --- .../java/io/ebean/meta/MetaInfoManager.java | 4 + .../io/ebean/meta/MetricReportGenerator.java | 22 + .../java/io/ebean/meta/MetricReportValue.java | 39 + .../server/core/DefaultMetaInfoManager.java | 5 + .../server/core/DumpMetrics.java | 19 +- .../core/HtmlMetricReportGenerator.java | 679 ++++++++++++++++++ .../io/ebeaninternal/server/core/metrics.css | 112 +++ .../io/ebeaninternal/server/core/metrics.js | 205 ++++++ .../server/core/HtmlMetricTest.java | 26 + .../query/finder/TestCustomerFinder.java | 34 +- 10 files changed, 1142 insertions(+), 3 deletions(-) create mode 100644 ebean-api/src/main/java/io/ebean/meta/MetricReportGenerator.java create mode 100644 ebean-api/src/main/java/io/ebean/meta/MetricReportValue.java create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/core/HtmlMetricReportGenerator.java create mode 100644 ebean-core/src/main/resources/io/ebeaninternal/server/core/metrics.css create mode 100644 ebean-core/src/main/resources/io/ebeaninternal/server/core/metrics.js create mode 100644 ebean-core/src/test/java/io/ebeaninternal/server/core/HtmlMetricTest.java diff --git a/ebean-api/src/main/java/io/ebean/meta/MetaInfoManager.java b/ebean-api/src/main/java/io/ebean/meta/MetaInfoManager.java index 81e7944f04..7f61640bff 100644 --- a/ebean-api/src/main/java/io/ebean/meta/MetaInfoManager.java +++ b/ebean-api/src/main/java/io/ebean/meta/MetaInfoManager.java @@ -64,5 +64,9 @@ default List collectMetricsAsData() { */ List queryPlanCollectNow(QueryPlanRequest request); + /** + * Creates a new MetricReportGenerator. This can be used to embed in your web-application + */ + MetricReportGenerator createReportGenerator(); } diff --git a/ebean-api/src/main/java/io/ebean/meta/MetricReportGenerator.java b/ebean-api/src/main/java/io/ebean/meta/MetricReportGenerator.java new file mode 100644 index 0000000000..054b77a56d --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/meta/MetricReportGenerator.java @@ -0,0 +1,22 @@ +package io.ebean.meta; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +/** + * The metric report. most likely a HTML report. + */ +public interface MetricReportGenerator { + + /** + * Writes the report to the outputStream. The stream will not be closed. + */ + void writeReport(OutputStream out) throws IOException; + + /** + * Used to configure the metric. This report dependent. + * Returns a string result, that should be sent back to the web application + */ + String configure(List values); +} diff --git a/ebean-api/src/main/java/io/ebean/meta/MetricReportValue.java b/ebean-api/src/main/java/io/ebean/meta/MetricReportValue.java new file mode 100644 index 0000000000..4ab5a2c9d0 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/meta/MetricReportValue.java @@ -0,0 +1,39 @@ +package io.ebean.meta; + +/** + * A Metric report value used to configure a metric report. + * This is most likely a REST call from the provided web-application + */ +public class MetricReportValue { + private String name; + private String value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public int intValue() { + return Integer.valueOf(value); + } + + public MetricReportValue() { + + } + + public MetricReportValue(String name, Object value) { + this.name = name; + this.value = String.valueOf(value); + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java index b4a29697c3..a4b2aaaa77 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java @@ -45,6 +45,11 @@ public BasicMetricVisitor visitBasic() { return basic; } + @Override + public MetricReportGenerator createReportGenerator() { + return new HtmlMetricReportGenerator(server); + } + @Override public void resetAllMetrics() { server.visitMetrics(new ResetVisitor()); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetrics.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetrics.java index c13a7d6dbe..4df3ce4ed9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetrics.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetrics.java @@ -7,6 +7,12 @@ import io.ebean.meta.SortMetric; import io.ebeaninternal.api.SpiEbeanServer; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Comparator; import java.util.List; @@ -19,6 +25,7 @@ final class DumpMetrics { private boolean dumpHash; private boolean dumpSql; private boolean dumpLoc; + private boolean dumpHtml; private Comparator sortBy = SortMetric.NAME; @@ -32,6 +39,7 @@ final class DumpMetrics { dumpLoc = options.contains("loc"); dumpSql = options.contains("sql"); dumpHash = options.contains("hash"); + dumpHtml = options.contains("html"); for (int i = 5; i < 10; i++) { width = Math.max(width, optionWidth(i * 10)); } @@ -72,7 +80,16 @@ private Comparator setSortOption(String option) { } void dump() { - + if (dumpHtml) { + File file = new File("metric-report-" + server.name() + "-" + + DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss.SSS").format(LocalDateTime.now()) + ".html"); + try (OutputStream out = new FileOutputStream(file)) { + server.metaInfo().createReportGenerator().writeReport(out); + out("html report written to: " + file.getAbsolutePath() + "\n"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } out("-- Dumping metrics for " + server.name() + " -- "); ServerMetrics serverMetrics = server.metaInfo().collectMetrics(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/HtmlMetricReportGenerator.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/HtmlMetricReportGenerator.java new file mode 100644 index 0000000000..01d890b251 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/HtmlMetricReportGenerator.java @@ -0,0 +1,679 @@ +package io.ebeaninternal.server.core; + +import io.ebean.Database; +import io.ebean.annotation.Platform; +import io.ebean.cache.ServerCacheManager; +import io.ebean.config.dbplatform.DatabasePlatform; +import io.ebean.core.type.ScalarTypeUtils; +import io.ebean.meta.BasicMetricVisitor; +import io.ebean.meta.MetaCountMetric; +import io.ebean.meta.MetaInfoManager; +import io.ebean.meta.MetaQueryMetric; +import io.ebean.meta.MetaQueryPlan; +import io.ebean.meta.MetaTimedMetric; +import io.ebean.meta.MetricNamingMatch; +import io.ebean.meta.MetricReportGenerator; +import io.ebean.meta.MetricReportValue; +import io.ebean.meta.QueryPlanInit; +import io.ebean.meta.QueryPlanRequest; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * HtmlMetricReportGenerator provides a neat interface for ebean metrics. + * + * @author Roland Praml, FOCONIS AG + */ +public class HtmlMetricReportGenerator implements MetricReportGenerator { + + private static final Pattern SPLITPATTERN = Pattern.compile("[\\._]"); + // pattern for MariaDB binary UUIDs + private static final Pattern BINPATTERN = Pattern.compile("\\[.*\\[(-?\\d{1,3}), (-?\\d{1,3}), (-?\\d{1,3}), (-?\\d{1,3}), " + + "(-?\\d{1,3}), (-?\\d{1,3}), (-?\\d{1,3}), (-?\\d{1,3}), (-?\\d{1,3}), (-?\\d{1,3}), (-?\\d{1,3}), (-?\\d{1,3}), " + + "(-?\\d{1,3}), (-?\\d{1,3}), (-?\\d{1,3}), (-?\\d{1,3})\\].*\\]"); + + private final DatabasePlatform platform; + private final MetaInfoManager metaInfo; + private final ServerCacheManager cacheManager; + private final QueryPlanRequest queryRequest = new QueryPlanRequest(); + private final QueryPlanInit initRequest = new QueryPlanInit(); + private final String name; + private List queryPlans = Collections.emptyList(); + + /** + * Initializes the report with some common defaults. + */ + public HtmlMetricReportGenerator(Database db) { + platform = db.pluginApi().databasePlatform(); + metaInfo = db.metaInfo(); + cacheManager = db.cacheManager(); + initRequest.thresholdMicros(100_000); + initRequest.setAll(true); + queryRequest.maxCount(10); + queryRequest.maxTimeMillis(30_000); + name = db.name(); + } + + /** + * This is used to configure the current report. + * It will receive the REST calls from the web application and returns either OK or RELOAD if the page should be reloaded. + */ + @Override + public synchronized String configure(List configurations) { + String ret = "OK"; + for (MetricReportValue configuration : configurations) { + + if (configuration.getName().startsWith("hash.")) { + String hash = configuration.getName().substring(5); + if (configuration.intValue() > 0) { + initRequest.hashes().add(hash); + } else { + initRequest.hashes().remove(hash); + } + if (initRequest.isAll()) { + initRequest.setAll(false); + ret = "REFRESH"; + } + + } else { + switch (configuration.getName()) { + case "queryRequest.maxCount": + queryRequest.maxCount(configuration.intValue()); + break; + + case "queryRequest.maxTimeMillis": + queryRequest.maxTimeMillis(configuration.intValue()); + break; + + case "queryRequest.since": + queryRequest.since(configuration.intValue()); + break; + + case "queryRequest.apply": + queryPlans = metaInfo.queryPlanCollectNow(queryRequest); + ret = "REFRESH"; + break; + + case "initRequest.thresholdMicros": + initRequest.thresholdMicros(configuration.intValue()); + break; + + case "initRequest.isAll": + if (configuration.intValue() > 0) { + initRequest.hashes().clear(); + } + initRequest.setAll(configuration.intValue() > 0); + break; + + case "initRequest.apply": + queryPlans = metaInfo.queryPlanInit(initRequest); + ret = "REFRESH"; + break; + + case "clearCaches": + cacheManager.clearAll(); + ret = "REFRESH"; + break; + + case "resetMetrics": + metaInfo.resetAllMetrics(); + ret = "REFRESH"; + break; + + default: + throw new IllegalArgumentException(configuration.getName() + " is invalid"); + + } + } + } + return ret; + } + + /** + * Writes the report as UTF-8 to the outputstream. + */ + @Override + public synchronized void writeReport(OutputStream out) throws IOException { + OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); + Html html = new Html(); + createTabs(html); + StringBuilder sb = new StringBuilder(60 + ); + sb.append("Ebean metrics report for "); + addText(sb, name); // prevent HTML injection in name ;) + html.write(writer, sb.toString()); + writer.flush(); + } + + /** + * Create tabs. Can be overwritten to create additional tabs. + */ + protected void createTabs(Html html) { + // by default, we want to collect all metrics, but do not want to reset them + BasicMetricVisitor bmv = new BasicMetricVisitor(name, MetricNamingMatch.INSTANCE, false, true, true, true); + metaInfo.visitMetrics(bmv); + actionTab(html); + queryMetricTab(bmv.queryMetrics(), html); + timedMetricTab(bmv.timedMetrics(), html); + countMetricTab(bmv.countMetrics(), html); + queryPlansTab(html); + } + + /** + * 1st tab: Action tab. + */ + protected void actionTab(Html html) { + HtmlTab tab; + + tab = html.tab("Actions"); + tab.startTable("Name", "Value"); + tab.input("initRequest.thresholdMicros", initRequest.thresholdMicros()); + tab.input("initRequest.isAll", initRequest.isAll() ? 1 : 0); + tab.action("initRequest.apply", "Start capturing"); + + tab.input("queryRequest.maxCount", queryRequest.maxCount()); + tab.input("queryRequest.maxTimeMillis", queryRequest.maxTimeMillis()); + tab.input("queryRequest.since", queryRequest.since()); + tab.tableRow("current time", System.currentTimeMillis()); + tab.action("queryRequest.apply", "Collect plans"); + + + tab.action("clearCaches", "Clear caches"); + tab.action("resetMetrics", "Reset all metrics"); + + tab.endTable(); + tab.html("

Usage:

"); + tab.html("
    \n"); + tab.html("
  1. Set initRequest.thresholdMicros to the value, you want to capture
  2. \n"); + tab.html("
  3. Add hashes or set initRequest.isAll = 1 (default)
  4. \n"); + tab.html("
  5. Click 'Start capturing'
  6. \n"); + tab.html("
  7. Do the action, which executes the query/queries you want to inspect
  8. \n"); + tab.html("
  9. Click 'Collect plans' to collect the query-plans
  10. \n"); + tab.html("
\n"); + } + + /** + * 2nd tab: Query Metrics. + */ + protected void queryMetricTab(List metrics, Html html) { + + HtmlTab tab = html.tab("Query metrics"); + tab.startTable("type?", "?", "?", "?", "count", "total", "mean", "max", "sql", "hash"); + for (MetaQueryMetric metric : metrics) { + String[] names = splitPad(metric.name(), 4); + tab.tableRow(names[0], names[1], names[2], names[3], + metric.count(), + micros(metric.total()), micros(metric.mean()), micros(metric.max()), + metric.sql(), hash(metric.hash())); + } + tab.endTable(); + } + + /** + * 3rd tab: Timed metrics. + */ + protected void timedMetricTab(List metrics, Html html) { + HtmlTab tab = html.tab("Timed metrics"); + tab.startTable("type?", "?", "?", "?", "count", "total", "mean", "max"); + for (MetaTimedMetric metric : metrics) { + String[] names = splitPad(metric.name(), 4); + tab.tableRow(names[0], names[1], names[2], names[3], + metric.count(), + micros(metric.total()), micros(metric.mean()), micros(metric.max())); + } + tab.endTable(); + } + + /** + * 4th tab: Count metrics. + */ + protected void countMetricTab(List metrics, Html html) { + HtmlTab tab = html.tab("Count Metrics"); + tab.startTable("type?", "?", "?", "?", "count"); + for (MetaCountMetric metric : metrics) { + String[] names = splitPad(metric.name(), 4); + tab.tableRow(names[0], names[1], names[2], names[3], metric.count()); + } + tab.endTable(); + } + + /** + * 5th tab: Query plans. + */ + protected void queryPlansTab(Html html) { + HtmlTab tab = html.tab("Query plans"); + tab.startTable("type?", /*"tenant?",*/ "count", "micros", "sql", "hash"); + for (MetaQueryPlan queryPlan : queryPlans) { + tab.tableRow(queryPlan.beanType().getSimpleName(), + // queryPlan.tenantId(), TODO: Add tenant later. + queryPlan.captureCount(), + micros(queryPlan.queryTimeMicros()), + queryPlan(queryPlan.sql(), queryPlan.bind(), queryPlan.plan()), + hash(queryPlan.hash())); + } + tab.endTable(); + } + + /** + * Splits the string on '.' or '_' with a fixed length of entries. + */ + protected static String[] splitPad(String name, int length) { + String[] ret = SPLITPATTERN.split(name, length); + if (ret.length < length) { + return Arrays.copyOf(ret, length); + } + return ret; + } + + /** + * Adds html-safe text to the string-builder. + */ + protected static void addText(StringBuilder sb, String text) { + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + + switch (ch) { + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '"': + sb.append("""); + break; + case '&': + sb.append("&"); + break; + case '\'': + sb.append("'"); + break; // HTML entity for Apostroph ' + default: + sb.append(ch); + break; + } + } + } + + /** + * Creates a QueryPlan object. This is rendered as sql and optionally displayable bind and plan. + */ + protected QueryPlan queryPlan(String sql, String bind, String plan) { + + if (bind != null && (platform.isPlatform(Platform.MARIADB) || platform.isPlatform(Platform.MYSQL))) { + // MariaDB UUIDs beatifier. + Matcher matcher = BINPATTERN.matcher(bind); + + while (matcher.matches()) { + byte[] bytes = new byte[16]; + for (int i = 0; i < 16; i++) { + bytes[i] = Byte.parseByte(matcher.group(i + 1)); + } + UUID uuid = ScalarTypeUtils.uuidFromBytes(bytes, true); + bind = bind.substring(0, matcher.start(1) - 1) + uuid + bind.substring(matcher.end(16) + 1); + matcher = BINPATTERN.matcher(bind); + } + } + return new QueryPlan(sql, bind, plan, platform.isPlatform(Platform.SQLSERVER)); + } + + /** + * Creates a Micros object. It is rendered in human readable form. e.g. "12345678" micros is converted to "12.3 s" + */ + protected Micros micros(long value) { + return new Micros(value); + } + + /** + * Creates a Hash object. It is rendered with a checkbox. + */ + protected Hash hash(String hash) { + return new Hash(hash, initRequest.includeHash(hash)); + } + + /** + * Returns the currently collected plans. Maybe useful, if you want to fetch them via JSON. + */ + public List getCurrentPlans() { + return queryPlans; + } + + /** + * Human readable micros object. + */ + protected static class Micros { + private final long micros; + + protected Micros(long micros) { + this.micros = micros; + } + + @Override + public String toString() { + if (micros < 1_000L) { + return String.format("%d µs", micros); + } else if (micros < 10_000L) { + return String.format("%.2f ms", micros / 1000d); + } else if (micros < 100_000L) { + return String.format("%.1f ms", micros / 1000d); + } else if (micros < 1_000_000L) { + return String.format("%.0f ms", micros / 1000d); + } else if (micros < 10_000_000L) { + return String.format("%.2f s", micros / 1000_000d); + } else if (micros < 100_000_000L) { + return String.format("%.1f s", micros / 1000_000d); + } else { + return String.format("%.0f s", micros / 1000_000d); + } + } + } + + /** + * QueryPan popup object. + */ + protected static class QueryPlan { + final String sql; + final String bind; + final String plan; + final boolean sqlServer; + + QueryPlan(String sql, String bind, String plan, boolean sqlServer) { + this.sql = sql; + this.bind = bind; + this.plan = plan; + this.sqlServer = sqlServer; + } + } + + /** + * Hash object. + */ + public static class Hash { + final String hash; + final boolean checked; + + Hash(String hash, boolean checked) { + this.hash = hash; + this.checked = checked; + } + } + + /** + * This class reprensets the whole HTML plage with it's tabs. + */ + public static class Html { + + private final List tabs = new ArrayList<>(); + + public HtmlTab tab(String title) { + HtmlTab htmlTab = new HtmlTab(title); + tabs.add(htmlTab); + return htmlTab; + } + + protected void appendResource(Appendable out, String resName) throws IOException { + InputStream res = getClass().getResourceAsStream(resName); + if (res == null) { + throw new NullPointerException("Could not find " + resName); + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(res, StandardCharsets.UTF_8))) { + Iterator it = reader.lines().iterator(); + while (it.hasNext()) { + out.append(it.next()).append('\n'); + } + } + } + + public void write(Appendable out, String title) throws IOException { + out.append("\n"); + out.append("\n"); + out.append(""); + out.append(title); + out.append(""); + //sb.append(""); + out.append(""); + out.append("\n"); + out.append(""); + out.append("

"); + out.append(title); + out.append("

"); + out.append(" display raw values
"); + out.append(" display query plan bind values
"); + out.append(" display query plan details
"); + out.append("
\n"); + // add radio inputs that controls the selected tabs. + for (int i = 0; i < tabs.size(); i++) { + out.append(" \n"); + } + + // add labels (=tabs) + out.append(" \n"); + + // add tab contents + out.append("
\n"); + for (int i = 0; i < tabs.size(); i++) { + out.append("
"); + out.append(tabs.get(i).toString()); + out.append("
\n"); + } + out.append("
\n"); + out.append("
\n"); + // add javascript. + out.append(""); + } + + /** + * Adds css for each tab by replacing the '@' sign with tab numbers. + */ + protected void addTabCss(Appendable sb, String template, String css) throws IOException { + for (int i = 0; i < tabs.size(); i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(template.replace("@", String.valueOf(i))); + } + sb.append(css); + sb.append('\n'); + } + } + + /** + * HtmlTab that is mainly used to render tables. + */ + public static class HtmlTab { + + private final String title; + + private final StringBuilder sb = new StringBuilder(); + + HtmlTab(String title) { + this.title = title; + } + + /** + * Return the table's title. + */ + public String getTitle() { + return title; + } + + /** + * Adds table headers. If header ends with '?' - a filter will be added. + */ + public void startTable(String... headers) { + sb.append("\n"); + sb.append("\n"); + sb.append(""); + + boolean hasFilter = false; + for (String header : headers) { + sb.append("\n"); + } + sb.append(""); + if (hasFilter) { + // add second header row for filters + sb.append(""); + for (String header : headers) { + sb.append("\n"); + } + sb.append(""); + } + sb.append("\n"); + } + + public void tableRow(Object... items) { + sb.append(""); + + for (Object item : items) { + if (item == null) { + sb.append(""); + + } else if (item instanceof QueryPlan) { + // Render the QueryPlan sql, bind and plan output. + sb.append(""); + + } else if (item instanceof Micros) { + sb.append(""); + + } else if (item instanceof Number) { + sb.append(""); + + } else if (item instanceof Hash) { + Hash hash = (Hash) item; + sb.append(""); + + } else { + sb.append(""); + + } + } + sb.append("\n"); + } + + /** + * Adds an input field to the table. Changes are sent to the updateValue javascript function. + */ + public void input(String label, long value) { + sb.append("\n"); + } + + /** + * Adds an action button to the table. Changes are sent to the updateValue javascript function. + */ + public void action(String actionId, String caption) { + sb.append(""); + sb.append("\n"); + } + + public void endTable() { + // add a second tbody. it is used as buffer for filtered entries + sb.append("
"); + addText(sb, header); + if (header.endsWith("?")) { + sb.setLength(sb.length() - 1); + hasFilter = true; + } + sb.append("
"); + if (header.endsWith("?")) { + sb.append("
 "); + QueryPlan queryPlan = (QueryPlan) item; + addText(sb, String.valueOf(queryPlan.sql)); + if (queryPlan.bind != null) { + sb.append("
"); + addText(sb, queryPlan.bind); + sb.append(""); + } + if (queryPlan.plan != null) { + sb.append(""); + if (queryPlan.sqlServer) { + sb.append("Download"); + } else { + addText(sb, String.valueOf(queryPlan.plan)); + } + sb.append(""); + } + sb.append("
"); + addText(sb, String.valueOf(item)); + sb.append("
"); + addText(sb, String.valueOf(((Micros) item).micros)); + sb.append("
"); + addText(sb, String.valueOf(item)); + sb.append(""); + sb.append(" ").append(hash.hash).append(""); + addText(sb, String.valueOf(item)); + sb.append("
"); + addText(sb, label); + sb.append(""); + sb.append("
 
\n"); + } + + /** + * Add HTML to this tab. + */ + public void html(String html) { + sb.append(html); + } + + @Override + public String toString() { + return sb.toString(); + } + } +} diff --git a/ebean-core/src/main/resources/io/ebeaninternal/server/core/metrics.css b/ebean-core/src/main/resources/io/ebeaninternal/server/core/metrics.css new file mode 100644 index 0000000000..caf18cb866 --- /dev/null +++ b/ebean-core/src/main/resources/io/ebeaninternal/server/core/metrics.css @@ -0,0 +1,112 @@ +@charset "UTF-8"; +.sortable th { + cursor: pointer; +} + +.sortable th::after, .sortable th::before { + transition: color 0.1s ease-in-out; + font-size: 1.2em; + color: transparent; +} +.sortable tr.sortHdr th::after { + margin-left: 3px; + content: "▸"; +} +.sortable tr.sortHdr th:hover::after { + color: inherit; +} +.sortable tr.sortHdr th.dir-d::after { + color: inherit; + content: "▾"; +} +.sortable tr.sortHdr th.dir-u::after { + color: inherit; + content: "▴"; +} + +.sortable .number { + text-align: right; + white-space: nowrap; +} +.sortable .hash { + white-space: nowrap; +} +/* raw number display */ +.sortable .number div:nth-child(2) { + display: none; +} +#raw:checked ~ div .sortable .number div:nth-child(1) { + display: none; +} +#raw:checked ~ div .sortable .number div:nth-child(2) { + display: block; +} + +.sortable .filtered { + display: none; +} +.sortable { + --stripe-color: #e4e4e4; + --th-color: #fff; + --th-bg: #808080; + --td-color: #000; + --td-on-stripe-color: #000; + border-spacing: 0; + min-width: 640px; +} +.sortable tbody tr:nth-child(odd) { + background-color: var(--stripe-color); + color: var(--td-on-stripe-color); +} +.sortable th { + background: var(--th-bg); + color: var(--th-color); + font-weight: normal; + text-align: left; + text-transform: capitalize; + vertical-align: baseline; + white-space: nowrap; +} + +.sortable td { + color: var(--td-color); +} +.sortable td, +.sortable th { + padding: 5px; +} + +body { + font-size: 14px; +} + +p { + line-height: 1.7em; +} + +code { + font-family: monospace; + background: #eee; + padding: 5px; + border-radius: 2px; +} + +* { + box-sizing: border-box; + font-family: -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.plan { display : none; } + +#plans:checked ~ div table tbody tr td code { + display: block; + white-space: pre-wrap; +} + +.bind { display : none; } +#binds:checked ~ div table tbody tr td i { + display: block; + white-space: pre-wrap; +} diff --git a/ebean-core/src/main/resources/io/ebeaninternal/server/core/metrics.js b/ebean-core/src/main/resources/io/ebeaninternal/server/core/metrics.js new file mode 100644 index 0000000000..8d9f09699c --- /dev/null +++ b/ebean-core/src/main/resources/io/ebeaninternal/server/core/metrics.js @@ -0,0 +1,205 @@ +/** + * The sort code is adapted from https://github.com/tofsjonas/sortable + * Copyleft 2017 Jonas Earendel + * License: http://unlicense.org + */ + + (function() { // start closure +function findElementRecursive(element, tag) { + return !element ? null : element.nodeName === tag ? element : findElementRecursive(element.parentNode, tag) +} + +function getValue(element) { + return element.getAttribute('data-sort') || element.innerText +} + +document.addEventListener('click', function (e) { + try { + var descending_th_class = ' dir-d ' + var ascending_th_class = ' dir-u ' + var ascending_table_sort_class = 'asc' + var regex_dir = / dir-(u|d) / + var element = findElementRecursive(e.target, 'TH') + var tr = findElementRecursive(element, 'TR') + var table = findElementRecursive(tr, 'TABLE') + + function reClassify(element, dir) { + element.className = element.className.replace(regex_dir, '') + dir + } + + if (table && tr && tr.className == 'sortHdr') { + var column_index + var nodes = tr.cells + // Reset thead cells and get column index + for (var i = 0; i < nodes.length; i++) { + if (nodes[i] === element) { + column_index = i + } else { + reClassify(nodes[i], '') + } + } + var dir = descending_th_class + + // Check if we're sorting ascending or descending + if (element.className.indexOf(descending_th_class) !== -1 + || (table.className.indexOf(ascending_table_sort_class) !== -1 + && element.className.indexOf(ascending_th_class) == -1)) { + dir = ascending_th_class + } + + // Update the `th` class accordingly + reClassify(element, dir) + + // Get the array rows in an array, so we can sort them... + var rows = [].slice.call(table.tBodies[0].rows, 0) + var reverse = dir === ascending_th_class + + // Sort them using Array.prototype.sort() + rows.sort(function (a, b) { + var x = getValue((reverse ? a : b).cells[column_index]) + var y = getValue((reverse ? b : a).cells[column_index]) + return x.length && y.length && !isNaN(x - y) ? x - y : x.localeCompare(y) + }) + + // Make a clone without content + var clone_tbody = table.tBodies[0].cloneNode() + // Fill it with the sorted values + while (rows.length) { + clone_tbody.appendChild(rows.splice(0, 1)[0]) + } + // And finally replace the unsorted table with the sorted one + table.replaceChild(clone_tbody, table.tBodies[0]) + } + } catch (error) { + console.log(error) + } +}) + +function getIndex(collection, element) { + for (var i = 0; i < collection.length; i++) { + if (collection[i] === element) return i; + } + return -1; +} + +function updateFilterValues() { + for (var i = 0; i < filters.length; i++) { + var filter = filters[i]; + var table = findElementRecursive(filter, 'TABLE') + var cell = findElementRecursive(filter, 'TH') + var tr = findElementRecursive(filter, 'TR') + var idx = getIndex(tr.cells, cell) + var option = document.createElement("option"); + option.text = "-"; + [].slice.call(filter.options, 0).forEach(e => e.remove()); + filter.add(option); + var set = new Set(); + const rows = table.tBodies[0].rows; + for (var k = 0; k < rows.length; k++) { + const txt = rows[k].cells[idx].innerText; + if (txt) set.add(txt); + } + Array.from(set).sort().forEach(item => { + var option = document.createElement("option"); + option.text = item; + filter.add(option); + }); + } +} +function matchFilter(row, filters) { + for (var i = 0; i < filters.length; i++) { + var filter = filters[i]; + if (filter.value != "-") { + if (row.cells[filter.getAttribute('data-idx')].innerText != filter.value) { + return false; + } + } + } + return true; +} +function applyFilters(filter) { + var table = findElementRecursive(filter, 'TABLE') + var filters = table.getElementsByTagName("SELECT"); + var tBody = table.tBodies[0]; + var spareBody = table.tBodies[1]; + + var rows = [].slice.call(tBody.rows, 0).concat([].slice.call(spareBody.rows)); + var clone_tbody = tBody.cloneNode() + var clone_sparebody = spareBody.cloneNode() + // Fill it with the sorted values + while (rows.length) { + var row = rows.splice(0, 1)[0]; + if (matchFilter(row, filters)) { + clone_tbody.appendChild(row); + } else { + clone_sparebody.appendChild(row); + } + } + + table.replaceChild(clone_tbody, tBody) + table.replaceChild(clone_sparebody, spareBody) +} + +function initFilter(filter) { + var table = findElementRecursive(filter, 'TABLE') + var cell = findElementRecursive(filter, 'TH') + var tr = findElementRecursive(filter, 'TR') + var idx = getIndex(tr.cells, cell) + + var spare = document.createElement("table"); + spare.style="display:none"; + cell.appendChild(spare); + filter.onchange = evt => applyFilters(evt.target) + filter.setAttribute('data-idx', idx) + + var option = document.createElement("option"); + option.text = "-"; + [].slice.call(filter.options, 0).forEach(e => e.remove()); + filter.add(option); + var set = new Set(); + const rows = table.tBodies[0].rows; + for (var k = 0; k < rows.length; k++) { + const txt = rows[k].cells[idx].innerText; + if (txt) set.add(txt); + } + Array.from(set).sort().forEach(item => { + var option = document.createElement("option"); + option.text = item; + filter.add(option); + }); +} + +var filters = document.getElementsByTagName("SELECT") +for (var i = 0; i < filters.length; i++) initFilter(filters[i]); +})(); + +/** + * used to communicate with 'configure' endpoint + */ +function updateValue(element) { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + if (this.responseText == "OK") { + return; + } + if (this.responseText == "REFRESH") { + if (confirm("Action executed successfully. Reload pages?")) { + document.location.reload(); + } + return; + } + } + alert("An error occured: " + this.responseText); + } + }; + xmlhttp.open("POST", "configure", false); + xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + var value = element.value; + if (element.type == 'checkbox') { + value = element.checked ? 1 : 0; + } + xmlhttp.send(JSON.stringify([{'name' : element.name , 'value' : value}])); + +} diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/core/HtmlMetricTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/core/HtmlMetricTest.java new file mode 100644 index 0000000000..35b1ebddd3 --- /dev/null +++ b/ebean-core/src/test/java/io/ebeaninternal/server/core/HtmlMetricTest.java @@ -0,0 +1,26 @@ +package io.ebeaninternal.server.core; + +import io.ebean.DB; +import io.ebean.meta.MetricReportGenerator; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HtmlMetricTest { + + + @Test + public void testReport() throws IOException { + MetricReportGenerator generator = DB.getDefault().metaInfo().createReportGenerator(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + generator.writeReport(baos); + + assertThat(baos.toString(StandardCharsets.UTF_8)).contains(""); + } +} diff --git a/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java b/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java index 8b02734242..589740e799 100644 --- a/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java +++ b/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java @@ -1,17 +1,22 @@ package org.tests.query.finder; -import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import io.ebean.Transaction; -import io.ebean.xtest.IgnorePlatform; import io.ebean.annotation.Platform; import io.ebean.meta.*; import io.ebean.test.LoggedSql; +import io.ebean.xtest.BaseTestCase; +import io.ebean.xtest.IgnorePlatform; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.EBasic; import org.tests.model.basic.ResetBasicData; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -164,6 +169,31 @@ public void test_nativeSingleAttribute() { assertThat(names).isNotEmpty(); } + + @Test + @Disabled // run manually to generate sample report + public void test_report_queryPlans() throws IOException { + + ResetBasicData.reset(); + + // change default collect query plan threshold to 200 micros + MetricReportGenerator generator = server().metaInfo().createReportGenerator(); + generator.configure(List.of( + new MetricReportValue("initRequest.isAll", 1), + new MetricReportValue("initRequest.thresholdMicros", 2), + new MetricReportValue("initRequest.apply", 1))); + + // the server has some plans + runQueries(); + + generator.configure(List.of(new MetricReportValue("queryRequest.apply", 2))); + + File file = new File("sample-reports/report-" + server().name() + ".html"); + try (OutputStream out = new FileOutputStream(file)) { + generator.writeReport(out); + } + } + @Test @IgnorePlatform(Platform.DB2) // no query plans yet, for DB2 public void test_finders_queryPlans() { From 1d8aa7c0a994a2e05787b3a0e510e31eb40c5caa Mon Sep 17 00:00:00 2001 From: Roland Praml <roland.praml@foconis.de> Date: Thu, 2 Feb 2023 13:32:13 +0100 Subject: [PATCH 2/2] some sample reports --- ebean-test/sample-reports/report-h2.html | 501 +++++++++++++++++ .../sample-reports/report-mariadb-106.html | 465 ++++++++++++++++ .../report-metrics-on-shutdown.html | 517 +++++++++++++++++ ebean-test/sample-reports/report-oracle.html | 522 ++++++++++++++++++ .../sample-reports/report-postgres.html | 480 ++++++++++++++++ .../sample-reports/report-sqlserver2019.html | 461 ++++++++++++++++ 6 files changed, 2946 insertions(+) create mode 100644 ebean-test/sample-reports/report-h2.html create mode 100644 ebean-test/sample-reports/report-mariadb-106.html create mode 100644 ebean-test/sample-reports/report-metrics-on-shutdown.html create mode 100644 ebean-test/sample-reports/report-oracle.html create mode 100644 ebean-test/sample-reports/report-postgres.html create mode 100644 ebean-test/sample-reports/report-sqlserver2019.html diff --git a/ebean-test/sample-reports/report-h2.html b/ebean-test/sample-reports/report-h2.html new file mode 100644 index 0000000000..5db8ea2e54 --- /dev/null +++ b/ebean-test/sample-reports/report-h2.html @@ -0,0 +1,501 @@ +<!DOCTYPE html><html lang='en'> +<head> +<title>Ebean metrics report for h2 +

Ebean metrics report for h2

display raw values
display query plan bind values
display query plan details
+ + + + + + +
+
+ + + + + + + + + + + + + + +
NameValue
initRequest.thresholdMicros
initRequest.isAll
 
queryRequest.maxCount
queryRequest.maxTimeMillis
queryRequest.since
current time1675339600244
 
 
 
+

Usage:

    +
  1. Set initRequest.thresholdMicros to the value, you want to capture
  2. +
  3. Add hashes or set initRequest.isAll = 1 (default)
  4. +
  5. Click 'Start capturing'
  6. +
  7. Do the action, which executes the query/queries you want to inspect
  8. +
  9. Click 'Collect plans' to collect the query-plans
  10. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmaxsqlhash
      
ormCustomerFindernamesStartingWith 1
283 µs
283
283 µs
283
283 µs
283
select name from o_customer where name like ? order by name 25c7fe502c5a20ebe5b13db21e70e6a9
ormCustomerfindList 1
2,51 ms
2513
2,51 ms
2513
2,51 ms
2513
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 abbf1022a928ad38083aeb72da10a936
ormCustomerFinderbyNameStatus 1
1,14 ms
1144
1,14 ms
1144
1,14 ms
1144
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where (t0.status = ? and lower(t0.name) like ? escape'|' ) order by t0.name de3affa5b4bff07e19c1c012590dcde6
ormCustomerFinderupdateNames 1
418 µs
418
418 µs
418
418 µs
418
update o_customer set name=?, version = version + 1 where id > ? 5d05304e44ad6f7b2f1ac04db07407f3
ormCustomerFindertotalCount 1
206 µs
206
206 µs
206
206 µs
206
select count(*) from o_customer t0 1c7fd652b396c412977198fadd76b115
ormCustomersomeDeleteQuery 1
179 µs
179
179 µs
179
179 µs
179
select t0.id from o_customer t0 where t0.name = ? 7cc206becaf1d9a8a403d7f693bd104a
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmax
    
txnmain  2
3,07 ms
3071
1,54 ms
1535
2,45 ms
2450
txnreadonly  4
12,2 ms
12241
3,06 ms
3060
5,85 ms
5847
ebeanqueryplanbindcapture 4
976 µs
976
244 µs
244
833 µs
833
ebeanqueryplancollect 4
2,46 ms
2460
615 µs
615
1,53 ms
1531
+
+
+ + + + + + + + + + + + + + +
typecount
 
l2nCustomerBhit3
l2nCustomerBput4
+
+
+ + + + + + + + + + + + + + + + +
typecountmicrossqlhash
    
Customer1
283 µs
283
select name from o_customer where name like ? order by name
[F%]PLAN +SELECT + "NAME" +FROM "PUBLIC"."O_CUSTOMER" + /* PUBLIC.O_CUSTOMER.tableScan */ +WHERE "NAME" LIKE ?1 +ORDER BY 1
25c7fe502c5a20ebe5b13db21e70e6a9
Customer1
1,14 ms
1144
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where (t0.status = ? and lower(t0.name) like ? escape'|' ) order by t0.name
[A, rob%]PLAN +SELECT + "T0"."ID", + "T0"."STATUS", + "T0"."NAME", + "T0"."SMALLNOTE", + "T0"."ANNIVERSARY", + "T0"."CRETIME", + "T0"."UPDTIME", + "T0"."VERSION", + "T0"."BILLING_ADDRESS_ID", + "T0"."SHIPPING_ADDRESS_ID" +FROM "PUBLIC"."O_CUSTOMER" "T0" + /* PUBLIC.O_CUSTOMER.tableScan */ +WHERE ("T0"."STATUS" = ?1) + AND (LOWER("T0"."NAME") LIKE ?2 ESCAPE '|') +ORDER BY 3
de3affa5b4bff07e19c1c012590dcde6
Customer1
206 µs
206
select count(*) from o_customer t0
[]PLAN +SELECT + COUNT(*) +FROM "PUBLIC"."O_CUSTOMER" "T0" + /* PUBLIC.IX_O_CUSTOMER_BILLING_ADDRESS_ID */ +/* direct lookup */
1c7fd652b396c412977198fadd76b115
Customer1
2,51 ms
2513
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0
[]PLAN +SELECT + "T0"."ID", + "T0"."STATUS", + "T0"."NAME", + "T0"."SMALLNOTE", + "T0"."ANNIVERSARY", + "T0"."CRETIME", + "T0"."UPDTIME", + "T0"."VERSION", + "T0"."BILLING_ADDRESS_ID", + "T0"."SHIPPING_ADDRESS_ID" +FROM "PUBLIC"."O_CUSTOMER" "T0" + /* PUBLIC.O_CUSTOMER.tableScan */
abbf1022a928ad38083aeb72da10a936
+
+
+
+ \ No newline at end of file diff --git a/ebean-test/sample-reports/report-mariadb-106.html b/ebean-test/sample-reports/report-mariadb-106.html new file mode 100644 index 0000000000..a970847e86 --- /dev/null +++ b/ebean-test/sample-reports/report-mariadb-106.html @@ -0,0 +1,465 @@ + + +Ebean metrics report for mariadb-106 +

Ebean metrics report for mariadb-106

display raw values
display query plan bind values
display query plan details
+ + + + + + +
+
+ + + + + + + + + + + + + + +
NameValue
initRequest.thresholdMicros
initRequest.isAll
 
queryRequest.maxCount
queryRequest.maxTimeMillis
queryRequest.since
current time1675339786795
 
 
 
+

Usage:

    +
  1. Set initRequest.thresholdMicros to the value, you want to capture
  2. +
  3. Add hashes or set initRequest.isAll = 1 (default)
  4. +
  5. Click 'Start capturing'
  6. +
  7. Do the action, which executes the query/queries you want to inspect
  8. +
  9. Click 'Collect plans' to collect the query-plans
  10. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmaxsqlhash
      
ormCustomerFindernamesStartingWith 1
515 µs
515
515 µs
515
515 µs
515
select name from o_customer where name like ? order by name 25c7fe502c5a20ebe5b13db21e70e6a9
ormCustomerfindList 1
3,68 ms
3683
3,68 ms
3683
3,68 ms
3683
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 abbf1022a928ad38083aeb72da10a936
ormCustomerFinderbyNameStatus 1
689 µs
689
689 µs
689
689 µs
689
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where (t0.status = ? and lower(t0.name) like ? escape'|' ) order by t0.name de3affa5b4bff07e19c1c012590dcde6
ormCustomerFinderupdateNames 1
433 µs
433
433 µs
433
433 µs
433
update o_customer set name=?, version = version + 1 where id > ? 5d05304e44ad6f7b2f1ac04db07407f3
ormCustomerFindertotalCount 1
288 µs
288
288 µs
288
288 µs
288
select count(*) from o_customer t0 1c7fd652b396c412977198fadd76b115
ormCustomersomeDeleteQuery 1
312 µs
312
312 µs
312
312 µs
312
select t0.id from o_customer t0 where t0.name = ? 7cc206becaf1d9a8a403d7f693bd104a
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmax
    
txnmain  2
2,64 ms
2636
1,32 ms
1318
1,89 ms
1885
txnreadonly  4
16,4 ms
16412
4,10 ms
4103
7,09 ms
7089
ebeanqueryplanbindcapture 4
987 µs
987
246 µs
246
843 µs
843
ebeanqueryplancollect 4
1,84 ms
1841
460 µs
460
540 µs
540
+
+
+ + + + + + + + + + + + + + +
typecount
 
l2nCustomerBhit3
l2nCustomerBput4
+
+
+ + + + + + + + + + + + + + + + +
typecountmicrossqlhash
    
Customer1
3,68 ms
3683
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0
[]id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL null null null null 4
abbf1022a928ad38083aeb72da10a936
Customer1
515 µs
515
select name from o_customer where name like ? order by name
[F%]id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE o_customer ALL null null null null 4 Using where; Using filesort
25c7fe502c5a20ebe5b13db21e70e6a9
Customer1
288 µs
288
select count(*) from o_customer t0
[]id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index null ix_o_customer_billing_address_id 5 null 4 Using index
1c7fd652b396c412977198fadd76b115
Customer1
689 µs
689
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where (t0.status = ? and lower(t0.name) like ? escape'|' ) order by t0.name
[A, rob%]id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL null null null null 4 Using where; Using filesort
de3affa5b4bff07e19c1c012590dcde6
+
+
+
+ \ No newline at end of file diff --git a/ebean-test/sample-reports/report-metrics-on-shutdown.html b/ebean-test/sample-reports/report-metrics-on-shutdown.html new file mode 100644 index 0000000000..fa9441c908 --- /dev/null +++ b/ebean-test/sample-reports/report-metrics-on-shutdown.html @@ -0,0 +1,517 @@ + + +Ebean metrics report for h2 +

Ebean metrics report for h2

display raw values
display query plan bind values
display query plan details
+ + + + + + +
+
+ + + + + + + + + + + + + + +
NameValue
initRequest.thresholdMicros
initRequest.isAll
 
queryRequest.maxCount
queryRequest.maxTimeMillis
queryRequest.since
current time1675340276893
 
 
 
+

Usage:

    +
  1. Set initRequest.thresholdMicros to the value, you want to capture
  2. +
  3. Add hashes or set initRequest.isAll = 1 (default)
  4. +
  5. Click 'Start capturing'
  6. +
  7. Do the action, which executes the query/queries you want to inspect
  8. +
  9. Click 'Collect plans' to collect the query-plans
  10. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmaxsqlhash
      
ormASimpleBeanbyId 2
118 µs
118
59 µs
59
104 µs
104
select t0.id, t0.name from asimple_bean t0 where t0.id = ? c690b587185454b7956d2057c60a83cd
ormBSimpleForbyId 1
197 µs
197
197 µs
197
197 µs
197
select t0.id, t0.name, t0.other, t0.when_modified, t0.version from bsimple_for t0 where t0.id = ? 1971f3625c9ce11cc12714e1b8ddb6b1
ormBSimpleForfindList 1
86 µs
86
86 µs
86
86 µs
86
select t0.id, t0.name, t0.other, t0.when_modified, t0.version from bsimple_for t0 b45cf4ba3040de83a56d78483b589bb6
ormBSimpleFordelete 1
64 µs
64
64 µs
64
64 µs
64
delete from bsimple_for t0 244be5fd44f10e5211f3401abbab78ea
ormContactfindList 2
144 µs
144
72 µs
72
119 µs
119
select t0.customer_id, t0.id, t0.first_name, t0.last_name, t0.email from contact t0 where (t0.customer_id) in (?) 5ad2d33567dbd18dc78679d44d30481e
ormCountryfindEach 24
339 µs
339
14 µs
14
31 µs
31
select t0.code, t0.name from o_country t0 de5e0bd06033557679ea0b929797ace6
ormCountryfindCount 24
100 µs
100
4 µs
4
9 µs
9
select count(*) from o_country t0 8d71aa64f2e9892d15742b556b707dff
ormCustomerFinderbyName 1
119 µs
119
119 µs
119
119 µs
119
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where t0.name = ? 7d7092362cab9a2aa6dace4c192f3707
ormCustomerfindList 1
179 µs
179
179 µs
179
179 µs
179
select t0.id, t0.name from o_customer t0 order by t0.name cfc88c969ca5c7a2ac60d6a3a33b696f
ormCustomerfindEach 24
1,93 ms
1934
80 µs
80
167 µs
167
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 887c4320e3aca8573244142ed361d93a
ormCustomerfindList 1
264 µs
264
264 µs
264
264 µs
264
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where t0.name like ? escape'|' order by t0.name b8c460e034609a5f492caf7454107854
ormCustomerfindList 1
57 µs
57
57 µs
57
57 µs
57
select t0.id, t0.name, t0.status from o_customer t0 where lower(t0.name) like ? escape'' b4217601b1c328d3a9f94d3c9d11af29
ormCustomerfindList 1
85 µs
85
85 µs
85
85 µs
85
select t0.id, t0.name, t0.status from o_customer t0 where lower(t0.name) like ? escape'' b4217601b1c328d3a9f94d3c9d11af29
ormCustomerfindList 1
128 µs
128
128 µs
128
128 µs
128
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where t0.name like ? escape'|' 3f221e679c21a2f8d6275f17c163e6be
ormCustomerfindList 1
141 µs
141
141 µs
141
141 µs
141
select t0.id, t0.name from o_customer t0 where lower(t0.name) like ? escape'' c0535dba6bbd61980a4735ef0d8bb1e1
ormCustomerfindList 1
256 µs
256
256 µs
256
256 µs
256
select t0.id, t0.name, t0.version, t1.id, t1.line_1, t1.line_2, t1.city, t2.code, t2.name from o_customer t0 left join o_address t1 on t1.id = t0.billing_address_id left join o_country t2 on t2.code = t1.country_code where lower(t0.name) like ? escape'' c5af77d5efd1c847bc71d5d308849026
ormCustomerfindList 1
151 µs
151
151 µs
151
151 µs
151
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 order by t0.name, t0.id limit 10 offset 3 5d777527d679c875e9b94bf7ab383952
ormCustomerfindCount 1
218 µs
218
218 µs
218
218 µs
218
select count(*) from o_customer t0 where t0.cretime > ? cfa5e5c25967154e886b4e48bc4db360
ormCustomerfindCount 1
64 µs
64
64 µs
64
64 µs
64
select count(*) from o_customer t0 where t0.cretime >= ? eccc05f70ac87f85639b809d84e873ce
ormCustomerfindCount 1
61 µs
61
61 µs
61
61 µs
61
select count(*) from o_customer t0 where t0.updtime >= ? 26e31d9983652d8a69e2915feb7f3113
ormCustomerfindList 1
162 µs
162
162 µs
162
162 µs
162
select t0.id, t0.name from o_customer t0 where lower(t0.name) like ? escape'' c0535dba6bbd61980a4735ef0d8bb1e1
ormCustomerfindList 1
113 µs
113
113 µs
113
113 µs
113
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 order by t0.name 59478a7f97b27e8882dc3758846597d2
ormCustomerfindList 1
168 µs
168
168 µs
168
168 µs
168
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 order by t0.id limit 10 bc19751e75b5da9650df488d60c97039
ormCustomerfindList 1
123 µs
123
123 µs
123
123 µs
123
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 order by t0.id limit 10 offset 3 16abda3067edf0e5a66df40669f65553
ormCustomerfindList 1
104 µs
104
104 µs
104
104 µs
104
select t0.id, t0.name, t0.status from o_customer t0 order by t0.status f202d28966a51799588482ad60795026
ormCustomerfindCount 24
683 µs
683
28 µs
28
120 µs
120
select count(*) from o_customer t0 e68902886c7a8a9b17f23ce661936aae
ormCustomerfindList 1
104 µs
104
104 µs
104
104 µs
104
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 limit 10 044beafba8824f31e8140fc88056b5ac
ormCustomerfindCount 1
12 µs
12
12 µs
12
12 µs
12
select count(*) from o_customer t0 e68902886c7a8a9b17f23ce661936aae
ormCustomerfindList 1
91 µs
91
91 µs
91
91 µs
91
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 limit 10 offset 3 70b75c2841581a23e74a22114e8d4692
ormCustomerfindList 1
177 µs
177
177 µs
177
177 µs
177
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 order by t0.name limit 10 offset 3 0b142a1540a6cf0e2b838154f26b2dc2
ormCustomerbyId 1
180 µs
180
180 µs
180
180 µs
180
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id, t1.id, t1.first_name, t1.last_name from o_customer t0 left join contact t1 on t1.customer_id = t0.id where t0.id = ? order by t0.id aad41c12c27931c030f0880404e7ca47
ormCustomerfindCount 1
71 µs
71
71 µs
71
71 µs
71
select count(*) from o_customer t0 where t0.updtime > ? 5878096991ea299b751ad59e36021f1b
ormCustomerfindCount 1
10 µs
10
10 µs
10
10 µs
10
select count(*) from o_customer t0 e68902886c7a8a9b17f23ce661936aae
ormCustomerfindList 1
118 µs
118
118 µs
118
118 µs
118
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 order by t0.id limit 100 f5604894d0a2b4da2ea13c805ece6b23
ormOrderTestQueryDefaultBatchSizetest_findEach24
988 µs
988
41 µs
41
105 µs
105
select t0.id, t0.status, t0.order_date, t0.ship_date, t1.name, t0.cretime, t0.updtime, t0.kcustomer_id from o_order t0 join o_customer t1 on t1.id = t0.kcustomer_id 01a9ab78d3958e12f45940c03f35f0e8
ormOrderTestQueryDefaultBatchSizetest_findList1
51 µs
51
51 µs
51
51 µs
51
select t0.id, t0.status, t0.order_date, t0.ship_date, t1.name, t0.cretime, t0.updtime, t0.kcustomer_id from o_order t0 join o_customer t1 on t1.id = t0.kcustomer_id df00013a1718623fc989e503088d3df1
ormOrderbyId 1
135 µs
135
135 µs
135
135 µs
135
select t0.id, t0.status, t0.order_date, t0.ship_date, t1.name, t0.cretime, t0.updtime, t0.kcustomer_id from o_order t0 join o_customer t1 on t1.id = t0.kcustomer_id where t0.id = ? d551be2865d8c254c837a58768671962
ormOrderfindCount 24
277 µs
277
11 µs
11
110 µs
110
select count(*) from o_order t0 f0b8f6294152eedd23994188a45902d9
ormProductfindEach 24
658 µs
658
27 µs
27
118 µs
118
select t0.id, t0.sku, t0.name, t0.cretime, t0.updtime from o_product t0 f47777c121cf11b9c965b4cb99b51bda
ormProductfindCount 24
114 µs
114
4 µs
4
39 µs
39
select count(*) from o_product t0 accdb8054212fd7edfbb28e8719a558b
ormUUOnebyId 2
79 µs
79
39 µs
39
69 µs
69
select t0.id, t0.name, t0.description, t0.version from uuone t0 where t0.id = ? f71fe67daf63ee8edfd42b7ecfaec805
ormVehiclebyId 1
173 µs
173
173 µs
173
173 µs
173
select t0.dtype, t0.id, t0.license_number, t0.registration_date, t0.cretime, t0.updtime, t0.version, t1.dtype, t0.lease_id, t0.siz, t0.driver, t0.car_ref_id, t0.notes, t0.siz, t0.truck_ref_id, t0.capacity from vehicle t0 left join vehicle_lease t1 on t1.id = t0.lease_id where t0.id = ? bf9ebef36c7583733e7b744b75db69c5
ormVehiclebyId 1
71 µs
71
71 µs
71
71 µs
71
select t0.dtype, t0.id from vehicle t0 where t0.id = ? 1f0349be8f6ff67fefb2d13345532014
dtoDCustCamelCols0  1
264 µs
264
264 µs
264
264 µs
264
select id as num_with, name as name_for_me from o_customer f7f5aff789319a9ae20e1d190f14a639
dtoDCustCamelCols2  1
157 µs
157
157 µs
157
157 µs
157
select id as numWith, name as nameForMe from o_customer 9fec9abe1a71830124060d5bce57b89a
dtoDCustCamelCols2  1
236 µs
236
236 µs
236
236 µs
236
select id as numwith, name as nameforme from o_customer a5b28e9d5f5cacc28dc570af1ef3d7e9
dtoDCust  2
118 µs
118
59 µs
59
96 µs
96
select id, name from o_customer where name = ? 4262124e6c6757e40d51f1c122ea3629
dtoDCust  1
629 µs
629
629 µs
629
629 µs
629
select c.id, c.name, count(o.id) as totalOrders + from o_customer c + join o_order o on o.kcustomer_id = c.id + where c.name like ? + group by c.id, c.name 254baf6249614f45b03df8ae306ba0bf
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmax
    
txnmain  33
7,44 ms
7442
225 µs
225
1,29 ms
1294
txnreadonly  232
17,4 ms
17405
75 µs
75
636 µs
636
iudASimpleBeaninsert 1
106 µs
106
106 µs
106
106 µs
106
iudASimpleBeanupdate 1
37 µs
37
37 µs
37
37 µs
37
iudASimpleBeandelete 1
17 µs
17
17 µs
17
17 µs
17
iudBSimpleForinsert 4
196 µs
196
49 µs
49
115 µs
115
iudBSimpleFordelete 2
124 µs
124
62 µs
62
82 µs
82
iudCarinsert 2
82 µs
82
41 µs
41
48 µs
48
iudCardelete 2
134 µs
134
67 µs
67
91 µs
91
iudEBasicVerinsert 2
108 µs
108
54 µs
54
68 µs
68
iudEBasicVerdelete 2
196 µs
196
98 µs
98
151 µs
151
iudEBasicVerinsertBatch 15
421 µs
421
28 µs
28
57 µs
57
iudUUOneinsert 2
40 µs
40
20 µs
20
32 µs
32
sqlupdate 5
367 µs
367
73 µs
73
219 µs
219
ebeanqueryplanbindcapture 29
125 µs
125
4 µs
4
13 µs
13
+
+
+ + + + + + + + + + + + + + + + + +
typecount
 
l2nUUOneBmiss2
l2nUUOneBput2
l2nEBasicVerQclear14
l2nCustomerBput57
loadoneref  1
+
+
+ + + + + + + + + + + + +
typecountmicrossqlhash
    
+
+
+
+ \ No newline at end of file diff --git a/ebean-test/sample-reports/report-oracle.html b/ebean-test/sample-reports/report-oracle.html new file mode 100644 index 0000000000..5e9e6a1e00 --- /dev/null +++ b/ebean-test/sample-reports/report-oracle.html @@ -0,0 +1,522 @@ + + +Ebean metrics report for oracle +

Ebean metrics report for oracle

display raw values
display query plan bind values
display query plan details
+ + + + + + +
+
+ + + + + + + + + + + + + + +
NameValue
initRequest.thresholdMicros
initRequest.isAll
 
queryRequest.maxCount
queryRequest.maxTimeMillis
queryRequest.since
current time1675339870197
 
 
 
+

Usage:

    +
  1. Set initRequest.thresholdMicros to the value, you want to capture
  2. +
  3. Add hashes or set initRequest.isAll = 1 (default)
  4. +
  5. Click 'Start capturing'
  6. +
  7. Do the action, which executes the query/queries you want to inspect
  8. +
  9. Click 'Collect plans' to collect the query-plans
  10. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmaxsqlhash
      
ormCustomerFindernamesStartingWith 1
1,87 ms
1870
1,87 ms
1870
1,87 ms
1870
select name from o_customer where name like ? order by name 25c7fe502c5a20ebe5b13db21e70e6a9
ormCustomerfindList 1
9,69 ms
9690
9,69 ms
9690
9,69 ms
9690
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 abbf1022a928ad38083aeb72da10a936
ormCustomerFinderbyNameStatus 1
6,00 ms
5998
6,00 ms
5998
6,00 ms
5998
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where (t0.status = ? and lower(t0.name) like ? escape'|' ) order by t0.name de3affa5b4bff07e19c1c012590dcde6
ormCustomerFinderupdateNames 1
5,13 ms
5125
5,13 ms
5125
5,13 ms
5125
update o_customer set name=?, version = version + 1 where id > ? 5d05304e44ad6f7b2f1ac04db07407f3
ormCustomerFindertotalCount 1
1,69 ms
1692
1,69 ms
1692
1,69 ms
1692
select count(*) from o_customer t0 1c7fd652b396c412977198fadd76b115
ormCustomersomeDeleteQuery 1
2,56 ms
2563
2,56 ms
2563
2,56 ms
2563
select t0.id from o_customer t0 where t0.name = ? 7cc206becaf1d9a8a403d7f693bd104a
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmax
    
txnmain  2
10,4 ms
10419
5,21 ms
5209
7,22 ms
7215
txnreadonly  4
38,9 ms
38859
9,71 ms
9714
16,4 ms
16427
ebeanqueryplanbindcapture 4
1,16 ms
1163
290 µs
290
972 µs
972
ebeanqueryplancollect 4
187 ms
187009
46,8 ms
46752
149 ms
149015
+
+
+ + + + + + + + + + + + + + +
typecount
 
l2nCustomerBhit3
l2nCustomerBput4
+
+
+ + + + + + + + + + + + + + + + +
typecountmicrossqlhash
    
Customer1
1,87 ms
1870
select name from o_customer where name like ? order by name
[F%]PLAN_TABLE_OUTPUT +Plan hash value: 2162045307 + +--------------------------------------------------------------------------------- +| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | +--------------------------------------------------------------------------------- +| 0 | SELECT STATEMENT | | 4 | 88 | 4 (25)| 00:00:01 | +| 1 | SORT ORDER BY | | 4 | 88 | 4 (25)| 00:00:01 | +|* 2 | TABLE ACCESS FULL| O_CUSTOMER | 4 | 88 | 3 (0)| 00:00:01 | +--------------------------------------------------------------------------------- + +Predicate Information (identified by operation id): +--------------------------------------------------- + + 2 - filter("NAME" LIKE :1) + +Note +----- + - dynamic statistics used: dynamic sampling (level=2)
25c7fe502c5a20ebe5b13db21e70e6a9
Customer1
9,69 ms
9690
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0
[]PLAN_TABLE_OUTPUT +Plan hash value: 1102649756 + +-------------------------------------------------------------------------------- +| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | +-------------------------------------------------------------------------------- +| 0 | SELECT STATEMENT | | 4 | 652 | 3 (0)| 00:00:01 | +| 1 | TABLE ACCESS FULL| O_CUSTOMER | 4 | 652 | 3 (0)| 00:00:01 | +-------------------------------------------------------------------------------- + +Note +----- + - dynamic statistics used: dynamic sampling (level=2)
abbf1022a928ad38083aeb72da10a936
Customer1
1,69 ms
1692
select count(*) from o_customer t0
[]PLAN_TABLE_OUTPUT +Plan hash value: 386826248 + +------------------------------------------------------------------------------- +| Id | Operation | Name | Rows | Cost (%CPU)| Time | +------------------------------------------------------------------------------- +| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 | +| 1 | SORT AGGREGATE | | 1 | | | +| 2 | INDEX FAST FULL SCAN| PK_O_CUSTOMER | 4 | 2 (0)| 00:00:01 | +------------------------------------------------------------------------------- + +Note +----- + - dynamic statistics used: dynamic sampling (level=2)
1c7fd652b396c412977198fadd76b115
Customer1
6,00 ms
5998
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where (t0.status = ? and lower(t0.name) like ? escape'|' ) order by t0.name
[A, rob%]PLAN_TABLE_OUTPUT +Plan hash value: 2162045307 + +--------------------------------------------------------------------------------- +| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | +--------------------------------------------------------------------------------- +| 0 | SELECT STATEMENT | | 1 | 163 | 4 (25)| 00:00:01 | +| 1 | SORT ORDER BY | | 1 | 163 | 4 (25)| 00:00:01 | +|* 2 | TABLE ACCESS FULL| O_CUSTOMER | 1 | 163 | 3 (0)| 00:00:01 | +--------------------------------------------------------------------------------- + +Predicate Information (identified by operation id): +--------------------------------------------------- + + 2 - filter("T0"."STATUS"=:1 AND LOWER("T0"."NAME") LIKE :2 ESCAPE '|') + +Note +----- + - dynamic statistics used: dynamic sampling (level=2)
de3affa5b4bff07e19c1c012590dcde6
+
+
+
+ \ No newline at end of file diff --git a/ebean-test/sample-reports/report-postgres.html b/ebean-test/sample-reports/report-postgres.html new file mode 100644 index 0000000000..a061fb519f --- /dev/null +++ b/ebean-test/sample-reports/report-postgres.html @@ -0,0 +1,480 @@ + + +Ebean metrics report for postgres +

Ebean metrics report for postgres

display raw values
display query plan bind values
display query plan details
+ + + + + + +
+
+ + + + + + + + + + + + + + +
NameValue
initRequest.thresholdMicros
initRequest.isAll
 
queryRequest.maxCount
queryRequest.maxTimeMillis
queryRequest.since
current time1675339653585
 
 
 
+

Usage:

    +
  1. Set initRequest.thresholdMicros to the value, you want to capture
  2. +
  3. Add hashes or set initRequest.isAll = 1 (default)
  4. +
  5. Click 'Start capturing'
  6. +
  7. Do the action, which executes the query/queries you want to inspect
  8. +
  9. Click 'Collect plans' to collect the query-plans
  10. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmaxsqlhash
      
ormCustomerFindernamesStartingWith 1
356 µs
356
356 µs
356
356 µs
356
select name from o_customer where name like ? order by name 25c7fe502c5a20ebe5b13db21e70e6a9
ormCustomerfindList 1
4,71 ms
4712
4,71 ms
4712
4,71 ms
4712
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 abbf1022a928ad38083aeb72da10a936
ormCustomerFinderbyNameStatus 1
1,22 ms
1218
1,22 ms
1218
1,22 ms
1218
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where (t0.status = ? and lower(t0.name) like ? escape'|' ) order by t0.name de3affa5b4bff07e19c1c012590dcde6
ormCustomerFinderupdateNames 1
720 µs
720
720 µs
720
720 µs
720
update o_customer set name=?, version = version + 1 where id > ? 5d05304e44ad6f7b2f1ac04db07407f3
ormCustomerFindertotalCount 1
628 µs
628
628 µs
628
628 µs
628
select count(*) from o_customer t0 1c7fd652b396c412977198fadd76b115
ormCustomersomeDeleteQuery 1
940 µs
940
940 µs
940
940 µs
940
select t0.id from o_customer t0 where t0.name = ? 7cc206becaf1d9a8a403d7f693bd104a
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmax
    
txnmain  2
5,03 ms
5034
2,52 ms
2517
3,06 ms
3060
txnreadonly  4
17,8 ms
17817
4,45 ms
4454
8,20 ms
8198
ebeanqueryplanbindcapture 4
1,06 ms
1061
265 µs
265
891 µs
891
ebeanqueryplancollect 4
2,51 ms
2509
627 µs
627
921 µs
921
+
+
+ + + + + + + + + + + + + + +
typecount
 
l2nCustomerBhit3
l2nCustomerBput4
+
+
+ + + + + + + + + + + + + + + + +
typecountmicrossqlhash
    
Customer1
356 µs
356
select name from o_customer where name like ? order by name
[F%]Sort (cost=12.51..12.52 rows=1 width=98) (actual time=0.010..0.010 rows=1 loops=1) + Sort Key: name + Sort Method: quicksort Memory: 25kB + -> Seq Scan on o_customer (cost=0.00..12.50 rows=1 width=98) (actual time=0.006..0.006 rows=1 loops=1) + Filter: ((name)::text ~~ 'F%'::text) + Rows Removed by Filter: 3 +Planning Time: 0.073 ms +Execution Time: 0.019 ms
25c7fe502c5a20ebe5b13db21e70e6a9
Customer1
628 µs
628
select count(*) from o_customer t0
[]Aggregate (cost=12.50..12.51 rows=1 width=8) (actual time=0.005..0.005 rows=1 loops=1) + -> Seq Scan on o_customer t0 (cost=0.00..12.00 rows=200 width=0) (actual time=0.002..0.002 rows=4 loops=1) +Planning Time: 0.027 ms +Execution Time: 0.015 ms
1c7fd652b396c412977198fadd76b115
Customer1
1,22 ms
1218
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where (t0.status = ? and lower(t0.name) like ? escape'|' ) order by t0.name
[A, rob%]Sort (cost=13.51..13.52 rows=1 width=364) (actual time=0.065..0.065 rows=0 loops=1) + Sort Key: name + Sort Method: quicksort Memory: 25kB + -> Seq Scan on o_customer t0 (cost=0.00..13.50 rows=1 width=364) (actual time=0.063..0.063 rows=0 loops=1) + Filter: (((status)::text = 'A'::text) AND (lower((name)::text) ~~ 'rob%'::text)) + Rows Removed by Filter: 4 +Planning Time: 0.033 ms +Execution Time: 0.072 ms
de3affa5b4bff07e19c1c012590dcde6
Customer1
4,71 ms
4712
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0
[]Seq Scan on o_customer t0 (cost=0.00..12.00 rows=200 width=364) (actual time=0.002..0.003 rows=4 loops=1) +Planning Time: 0.021 ms +Execution Time: 0.007 ms
abbf1022a928ad38083aeb72da10a936
+
+
+
+ \ No newline at end of file diff --git a/ebean-test/sample-reports/report-sqlserver2019.html b/ebean-test/sample-reports/report-sqlserver2019.html new file mode 100644 index 0000000000..94c596a009 --- /dev/null +++ b/ebean-test/sample-reports/report-sqlserver2019.html @@ -0,0 +1,461 @@ + + +Ebean metrics report for sqlserver2019 +

Ebean metrics report for sqlserver2019

display raw values
display query plan bind values
display query plan details
+ + + + + + +
+
+ + + + + + + + + + + + + + +
NameValue
initRequest.thresholdMicros
initRequest.isAll
 
queryRequest.maxCount
queryRequest.maxTimeMillis
queryRequest.since
current time1675340101661
 
 
 
+

Usage:

    +
  1. Set initRequest.thresholdMicros to the value, you want to capture
  2. +
  3. Add hashes or set initRequest.isAll = 1 (default)
  4. +
  5. Click 'Start capturing'
  6. +
  7. Do the action, which executes the query/queries you want to inspect
  8. +
  9. Click 'Collect plans' to collect the query-plans
  10. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmaxsqlhash
      
ormCustomerFindernamesStartingWith 1
1,67 ms
1667
1,67 ms
1667
1,67 ms
1667
select name from o_customer where name like ? order by name 25c7fe502c5a20ebe5b13db21e70e6a9
ormCustomerfindList 1
4,52 ms
4517
4,52 ms
4517
4,52 ms
4517
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 abbf1022a928ad38083aeb72da10a936
ormCustomerFinderbyNameStatus 1
6,33 ms
6330
6,33 ms
6330
6,33 ms
6330
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where (t0.status = ? and lower(t0.name) like ? ) order by t0.name e1c1b9e15481cbbd830154d331d55a91
ormCustomerFinderupdateNames 1
4,17 ms
4165
4,17 ms
4165
4,17 ms
4165
update o_customer set name=?, version = version + 1 where id > ? 5d05304e44ad6f7b2f1ac04db07407f3
ormCustomerFindertotalCount 1
1,26 ms
1260
1,26 ms
1260
1,26 ms
1260
select count(*) from o_customer t0 1c7fd652b396c412977198fadd76b115
ormCustomersomeDeleteQuery 1
1,39 ms
1387
1,39 ms
1387
1,39 ms
1387
select t0.id from o_customer t0 where t0.name = ? 7cc206becaf1d9a8a403d7f693bd104a
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
typecounttotalmeanmax
    
txnmain  2
9,48 ms
9480
4,74 ms
4740
6,77 ms
6771
txnreadonly  4
30,6 ms
30612
7,65 ms
7653
13,6 ms
13586
ebeanqueryplanbindcapture 4
1,16 ms
1157
289 µs
289
951 µs
951
ebeanqueryplancollect 4
11,8 ms
11814
2,95 ms
2953
3,38 ms
3378
+
+
+ + + + + + + + + + + + + + +
typecount
 
l2nCustomerBhit3
l2nCustomerBput4
+
+
+ + + + + + + + + + + + + + + + +
typecountmicrossqlhash
    
Customer1
1,26 ms
1260
select count(*) from o_customer t0
[]Download
1c7fd652b396c412977198fadd76b115
Customer1
4,52 ms
4517
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0
[]Download
abbf1022a928ad38083aeb72da10a936
Customer1
6,33 ms
6330
select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.billing_address_id, t0.shipping_address_id from o_customer t0 where (t0.status = ? and lower(t0.name) like ? ) order by t0.name
[A, rob%]Download
e1c1b9e15481cbbd830154d331d55a91
Customer1
1,67 ms
1667
select name from o_customer where name like ? order by name
[F%]Download
25c7fe502c5a20ebe5b13db21e70e6a9
+
+
+
+ \ No newline at end of file