diff --git a/ebean-api/src/main/java/io/ebean/ProfileLocation.java b/ebean-api/src/main/java/io/ebean/ProfileLocation.java index 33584a0b3..605de580a 100644 --- a/ebean-api/src/main/java/io/ebean/ProfileLocation.java +++ b/ebean-api/src/main/java/io/ebean/ProfileLocation.java @@ -45,6 +45,14 @@ public interface ProfileLocation { */ String label(); + /** + * Return a hash of the location that intentionally excludes the line number. + *

+ * The hash is expected to be stable regardless of line number in the source file + * so that is identifies the class and method location over a long time. + */ + long hash(); + /** * Return the full location. */ diff --git a/ebean-api/src/main/java/io/ebean/meta/MetaTimedMetric.java b/ebean-api/src/main/java/io/ebean/meta/MetaTimedMetric.java index f95f3248d..dbd0f0a0e 100644 --- a/ebean-api/src/main/java/io/ebean/meta/MetaTimedMetric.java +++ b/ebean-api/src/main/java/io/ebean/meta/MetaTimedMetric.java @@ -6,6 +6,14 @@ package io.ebean.meta; */ public interface MetaTimedMetric extends MetaMetric { + /** + * Return the metric location hash if defined. + *

+ * This hash excludes line number with the intention of being stable over time + * as code changes move the source line (but the method is the same). + */ + long locationHash(); + /** * Return the metric location if defined. */ diff --git a/ebean-api/src/main/java/io/ebean/meta/MetricData.java b/ebean-api/src/main/java/io/ebean/meta/MetricData.java index d8f80a0a9..7f2eb38b6 100644 --- a/ebean-api/src/main/java/io/ebean/meta/MetricData.java +++ b/ebean-api/src/main/java/io/ebean/meta/MetricData.java @@ -14,6 +14,7 @@ public class MetricData { private Long mean; private Long max; private Long total; + private long locHash; public MetricData(String name) { this.name = name; @@ -38,6 +39,14 @@ public class MetricData { this.sqlHash = sqlHash; } + public void setLocHash(long locHash) { + this.locHash = locHash; + } + + public long getLocHash() { + return locHash; + } + public String getLoc() { return loc; } diff --git a/ebean-api/src/main/java/io/ebean/metric/TimedMetricStats.java b/ebean-api/src/main/java/io/ebean/metric/TimedMetricStats.java index 3012e9e9f..8c88d839e 100644 --- a/ebean-api/src/main/java/io/ebean/metric/TimedMetricStats.java +++ b/ebean-api/src/main/java/io/ebean/metric/TimedMetricStats.java @@ -12,6 +12,11 @@ public interface TimedMetricStats extends MetaTimedMetric { */ void setLocation(String location); + /** + * Additionally set the location hash. + */ + void setLocationHash(long locationHash); + /** * Override the name based on profile location. */ 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 f31fa849c..968e96a98 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 @@ -134,6 +134,7 @@ class DumpMetrics { String location = metric.location(); if (dumpLoc && location != null) { sb.append("\n loc:").append(location); + sb.append("\n locHash:").append(metric.locationHash()); } if (dumpSql) { sb.append(" \n\n sql:").append(metric.sql()).append("\n\n"); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsData.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsData.java index 4ac8859b6..07980228f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsData.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsData.java @@ -56,6 +56,7 @@ class DumpMetricsData { final MetricData data = create(metric); appendCounters(data, metric); data.setLoc(metric.location()); + data.setLocHash(metric.locationHash()); } private void addCount(MetaCountMetric metric) { @@ -71,6 +72,7 @@ class DumpMetricsData { } private void appendLocationAndSql(MetricData data, MetaQueryMetric metric) { + data.setLocHash(metric.locationHash()); data.setLoc(metric.location()); data.setSql(metric.sql()); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsJson.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsJson.java index b0644ec9f..c116ebd26 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsJson.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsJson.java @@ -188,6 +188,7 @@ class DumpMetricsJson implements ServerMetricsAsJson { metricStart(metric); appendTiming(metric); if (isIncludeDetail(metric)) { + keyVal("locHash", metric.locationHash()); appendExtra("loc", metric.location()); } metricEnd(); @@ -198,6 +199,7 @@ class DumpMetricsJson implements ServerMetricsAsJson { appendTiming(metric); if (withHash) { keyVal("sqlHash", metric.sqlHash()); + keyVal("locHash", metric.locationHash()); } if (isIncludeDetail(metric)) { appendExtra("loc", metric.location()); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java index 507938a96..8e8a5ffc0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java @@ -10,9 +10,11 @@ final class BasicProfileLocation implements ProfileLocation { private final String fullLocation; private final String location; private final String label; + private final long hash; BasicProfileLocation(String fullLocation) { this.fullLocation = fullLocation; + this.hash = UtilLocation.hash(fullLocation); this.location = shortDesc(fullLocation); this.label = UtilLocation.label(location); } @@ -42,6 +44,11 @@ final class BasicProfileLocation implements ProfileLocation { return location; } + @Override + public long hash() { + return hash; + } + @Override public String fullLocation() { return fullLocation; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java index c5ff7d3ac..9c6340ad4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java @@ -8,17 +8,14 @@ import io.ebean.ProfileLocation; class DProfileLocation implements ProfileLocation { private static final String IO_EBEAN = "io.ebean"; - private static final String UNKNOWN = "unknown"; private String fullLocation; - private String location; - private String label; + private long hash; private final int lineNumber; - private int traceCount; DProfileLocation() { @@ -53,6 +50,7 @@ class DProfileLocation implements ProfileLocation { label = UtilLocation.label(shortDesc); location = shortDesc; fullLocation = loc; + hash = UtilLocation.hash(loc); initWith(label); return true; } @@ -71,6 +69,11 @@ class DProfileLocation implements ProfileLocation { return location; } + @Override + public long hash() { + return hash; + } + @Override public String fullLocation() { return fullLocation; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMeta.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMeta.java index 5359d96ed..0354bbd0a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMeta.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMeta.java @@ -49,6 +49,10 @@ class DQueryPlanMeta { return (profileLocation == null) ? null : profileLocation.location(); } + public long getLocationHash() { + return (profileLocation == null) ? 0 : profileLocation.hash(); + } + public String getSql() { return sql; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMetric.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMetric.java index fda1d4d9b..41a0658f4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMetric.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMetric.java @@ -83,6 +83,11 @@ class DQueryPlanMetric implements QueryPlanMetric { return meta.getLocation(); } + @Override + public long locationHash() { + return meta.getLocationHash(); + } + @Override public long count() { return stats.count(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimeMetricStats.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimeMetricStats.java index 2207fef3d..82731dfd3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimeMetricStats.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimeMetricStats.java @@ -7,18 +7,15 @@ import io.ebean.metric.TimedMetricStats; */ class DTimeMetricStats implements TimedMetricStats { - private String name; - private final boolean collected; - - private String location; - private final long count; - private final long total; - private final long max; + private String name; + private String location; + private long locationHash; + DTimeMetricStats(String name, boolean collected, long count, long total, long max) { this.name = name; this.collected = collected; @@ -40,6 +37,7 @@ class DTimeMetricStats implements TimedMetricStats { .append(" max:").append(max); if (location != null) { sb.append(" loc:").append(location); + sb.append(" locHash:").append(locationHash); } return sb.toString(); } @@ -49,6 +47,11 @@ class DTimeMetricStats implements TimedMetricStats { this.location = location; } + @Override + public void setLocationHash(long locationHash) { + this.locationHash = locationHash; + } + @Override public boolean initialCollection() { return !collected; @@ -69,6 +72,11 @@ class DTimeMetricStats implements TimedMetricStats { return location; } + @Override + public long locationHash() { + return locationHash; + } + /** * Return the count of values collected. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/UtilLocation.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/UtilLocation.java index 7b70dfcb7..00b388bbd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/UtilLocation.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/UtilLocation.java @@ -1,7 +1,21 @@ package io.ebeaninternal.server.profile; +import io.ebeaninternal.server.util.Checksum; + final class UtilLocation { + /** + * Return a hash of the full description excluding the source line number. + */ + static long hash(String full) { + final int pos = full.lastIndexOf('('); + if (pos > -1) { + return Checksum.checksum(full.substring(0, pos)); + } else { + return Checksum.checksum(full); + } + } + static String label(String shortDescription) { int pos = shortDescription.indexOf("("); if (pos == -1) { @@ -21,5 +35,4 @@ final class UtilLocation { } return desc; } - } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java index 796290808..770ac522d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java @@ -54,46 +54,32 @@ public class CQueryPlan implements SpiQueryPlan { static final String RESULT_SET_BASED_RAW_SQL = "--ResultSetBasedRawSql"; private final SpiEbeanServer server; - private final ProfileLocation profileLocation; - private final String location; - + private final long locationHash; private final String label; - private final String name; - private final CQueryPlanKey planKey; - private final boolean rawSql; - private final String sql; private final long sqlHash; - private final String logWhereSql; - private final SqlTree sqlTree; /** * Encrypted properties required additional binding. */ private final STreeProperty[] encryptedProps; - private final CQueryPlanStats stats; - private final Class beanType; - final DataTimeZone dataTimeZone; - private final int asOfTableCount; /** * Key used to identify the query plan in audit logging. */ private volatile String auditQueryHash; - private final Set dependentTables; - private final SpiQueryBindCapture bindCapture; /** @@ -106,9 +92,10 @@ public class CQueryPlan implements SpiQueryPlan { this.planKey = request.getQueryPlanKey(); SpiQuery query = request.getQuery(); this.profileLocation = query.getProfileLocation(); + this.location = (profileLocation == null) ? null : profileLocation.location(); + this.locationHash = (profileLocation == null) ? 0 : profileLocation.hash(); this.label = query.getPlanLabel(); this.name = deriveName(label, query.getType(), request.getBeanDescriptor().getSimpleName()); - this.location = location(); this.asOfTableCount = query.getAsOfTableCount(); this.sql = sqlRes.getSql(); this.sqlTree = sqlTree; @@ -130,9 +117,10 @@ public class CQueryPlan implements SpiQueryPlan { this.beanType = request.getBeanDescriptor().getBeanType(); SpiQuery query = request.getQuery(); this.profileLocation = query.getProfileLocation(); + this.location = (profileLocation == null) ? null : profileLocation.location(); + this.locationHash = (profileLocation == null) ? 0 : profileLocation.hash(); this.label = query.getPlanLabel(); this.name = deriveName(label, query.getType(), request.getBeanDescriptor().getSimpleName()); - this.location = location(); this.planKey = buildPlanKey(sql, logWhereSql); this.asOfTableCount = 0; this.sql = sql; @@ -169,10 +157,6 @@ public class CQueryPlan implements SpiQueryPlan { return sql.equals(RESULT_SET_BASED_RAW_SQL) || query.getType().isUpdate() ? SpiQueryBindCapture.NOOP : server.createQueryBindCapture(this); } - private String location() { - return (profileLocation == null) ? null : profileLocation.location(); - } - private CQueryPlanKey buildPlanKey(String sql, String logWhereSql) { return new RawSqlQueryPlanKey(sql, false, logWhereSql); } @@ -219,6 +203,10 @@ public class CQueryPlan implements SpiQueryPlan { return location; } + public long getLocationHash() { + return locationHash; + } + @Override public void queryPlanInit(long thresholdMicros) { bindCapture.queryPlanInit(thresholdMicros); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java index 36e070653..ab7dc0b63 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java @@ -106,6 +106,11 @@ public final class CQueryPlanStats { return queryPlan.getLocation(); } + @Override + public long locationHash() { + return queryPlan.getLocationHash(); + } + @Override public long count() { return metrics.count(); diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/profile/UtilLocationTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/profile/UtilLocationTest.java index 1bdbcd2f7..5fcbe90a4 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/profile/UtilLocationTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/profile/UtilLocationTest.java @@ -8,8 +8,14 @@ public class UtilLocationTest { @Test public void label() { - assertThat(UtilLocation.label("foo")).isEqualTo("foo"); assertThat(UtilLocation.label("ProfileLocationTest$Other.(ProfileLocationTest.java:47)")).isEqualTo("ProfileLocationTest$Other.init"); } + + @Test + public void hash() { + assertThat(UtilLocation.hash("org.foo.MyFoo.doIt(MyFoo.java:12)")).isEqualTo(396279222L); + assertThat(UtilLocation.hash("org.foo.MyFoo.doIt(MyFoo.java:13)")).isEqualTo(396279222L); + assertThat(UtilLocation.hash("org.foo.MyFoo.doIt(MyFoo.java:945)")).isEqualTo(396279222L); + } } diff --git a/ebean-core/src/test/java/org/tests/model/basic/finder/CustomerFinder.java b/ebean-core/src/test/java/org/tests/model/basic/finder/CustomerFinder.java index c76728fca..79db60023 100644 --- a/ebean-core/src/test/java/org/tests/model/basic/finder/CustomerFinder.java +++ b/ebean-core/src/test/java/org/tests/model/basic/finder/CustomerFinder.java @@ -37,7 +37,6 @@ public class CustomerFinder extends Finder { } public List byNameStatus(String nameStartsWith, Customer.Status status) { - return query("where status = :status and name istartsWith :name order by name") .setParameter("status", status) .setParameter("name", nameStartsWith) @@ -45,7 +44,6 @@ public class CustomerFinder extends Finder { } public List namesStartingWith(String name) { - return nativeSql("select name from o_customer where name like ? order by name") .setParameter(name + "%") .findSingleAttributeList(); diff --git a/ebean-core/src/test/java/org/tests/profile/ProfileLocationTest.java b/ebean-core/src/test/java/org/tests/profile/ProfileLocationTest.java index d93b5c75f..d452b7463 100644 --- a/ebean-core/src/test/java/org/tests/profile/ProfileLocationTest.java +++ b/ebean-core/src/test/java/org/tests/profile/ProfileLocationTest.java @@ -7,20 +7,28 @@ import static org.assertj.core.api.Assertions.assertThat; public class ProfileLocationTest { - private static ProfileLocation loc = ProfileLocation.create(12, "foo"); - - private static ProfileLocation loc2 = ProfileLocation.create(); + private static final ProfileLocation loc = ProfileLocation.create(12, "foo"); + private static final ProfileLocation locB = ProfileLocation.create(); + private static final ProfileLocation loc2 = ProfileLocation.create(); private boolean doIt() { + locB.obtain(); // simulate a location moving by line number only return loc.obtain(); } @Test public void test_obtain() { assertThat(doIt()).isTrue(); - assertThat(loc.fullLocation()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt(ProfileLocationTest.java:15)"); - assertThat(loc.location()).isEqualTo("ProfileLocationTest.doIt(ProfileLocationTest.java:15)"); + assertThat(loc.fullLocation()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt(ProfileLocationTest.java:16)"); + assertThat(loc.location()).isEqualTo("ProfileLocationTest.doIt(ProfileLocationTest.java:16)"); assertThat(loc.label()).isEqualTo("ProfileLocationTest.doIt"); + assertThat(loc.hash()).isEqualTo(1867926812L); + + // same hash even when the line number has changed + assertThat(locB.fullLocation()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt(ProfileLocationTest.java:15)"); + assertThat(locB.location()).isEqualTo("ProfileLocationTest.doIt(ProfileLocationTest.java:15)"); + assertThat(locB.label()).isEqualTo("ProfileLocationTest.doIt"); + assertThat(locB.hash()).isEqualTo(1867926812L); } @Test @@ -35,7 +43,7 @@ public class ProfileLocationTest { other.hashCode(); assertThat(loc2.label()).isEqualTo("ProfileLocationTest$Other.init"); - assertThat(loc2.location()).isEqualTo("ProfileLocationTest$Other.(ProfileLocationTest.java:44)"); + assertThat(loc2.location()).isEqualTo("ProfileLocationTest$Other.(ProfileLocationTest.java:52)"); } static class Other { diff --git a/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java b/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java index 108b4a398..6dfd99422 100644 --- a/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java +++ b/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java @@ -242,7 +242,8 @@ public class TestCustomerFinder extends BaseTestCase { assertThat(metricsJson).contains("\"name\":\"txn.main\""); assertThat(metricsJson).contains("\"name\":\"orm.Customer.findList\""); - assertThat(metricsJson).contains("\"loc\":\"CustomerFinder.byNameStatus(CustomerFinder.java:44)\""); + assertThat(metricsJson).contains("\"locHash\":3254522637"); + assertThat(metricsJson).contains("\"loc\":\"CustomerFinder.byNameStatus(CustomerFinder.java:43)\""); if (isH2() || isPostgres()) { assertThat(metricsJson).contains("\"sqlHash\":3634991469"); assertThat(metricsJson).contains("\"sql\":\"select t0.id, t0.status,");