Skip to content

Commit 09cb6ab

Browse files
thombergsbrian-brazil
authored andcommitted
Added per-query metrics in HibernateStatisticsCollector (#388)
exposing accumulated total execution time per query (update to Hibernate 5 was necessary) moved hibernate dependency to 5.2.0 Signed-off-by: Tom Hombergs <tom.hombergs@gmail.com>
1 parent 4a52c13 commit 09cb6ab

File tree

3 files changed

+244
-3
lines changed

3 files changed

+244
-3
lines changed

simpleclient_hibernate/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
<dependency>
4646
<groupId>org.hibernate</groupId>
4747
<artifactId>hibernate-core</artifactId>
48-
<version>4.2.0.Final</version>
48+
<version>5.2.0.Final</version>
4949
<scope>provided</scope>
5050
</dependency>
5151

simpleclient_hibernate/src/main/java/io/prometheus/client/hibernate/HibernateStatisticsCollector.java

Lines changed: 178 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.prometheus.client.Collector;
44
import io.prometheus.client.CollectorRegistry;
55
import io.prometheus.client.CounterMetricFamily;
6+
import io.prometheus.client.GaugeMetricFamily;
67
import java.util.ArrayList;
78
import java.util.Arrays;
89
import java.util.Collections;
@@ -32,15 +33,23 @@
3233
* SessionFactory sessionFactory =
3334
* entityManagerFactory.unwrap(SessionFactory.class);
3435
* </pre>
36+
* <p>
37+
* When {@code enablePerQueryMetrics()} has been called, certain metrics like execution
38+
* time are collected per query. This may create a lot of monitoring data, so it should
39+
* be used with caution.
3540
*
3641
* @author Christian Kaltepoth
3742
*/
3843
public class HibernateStatisticsCollector extends Collector {
3944

4045
private static final List<String> LABEL_NAMES = Collections.singletonList("unit");
4146

47+
private static final List<String> LABEL_NAMES_PER_QUERY = Arrays.asList("unit", "query");
48+
4249
private final Map<String, SessionFactory> sessionFactories = new ConcurrentHashMap<String, SessionFactory>();
4350

51+
private boolean perQueryMetricsEnabled;
52+
4453
/**
4554
* Creates an empty collector. If you use this constructor, you have to add one or more
4655
* session factories to the collector by calling the {@link #add(SessionFactory, String)}
@@ -74,6 +83,19 @@ public HibernateStatisticsCollector add(SessionFactory sessionFactory, String na
7483
return this;
7584
}
7685

86+
/**
87+
* Enables collection of per-query metrics. Produces a lot of monitoring data, so use with caution.
88+
* <p>
89+
* Per-query metrics have a label "query" with the actual HQL query as value. The query will contain
90+
* placeholders ("?") instead of the real parameter values (example: {@code select u from User u where id=?}).
91+
*
92+
* @return Returns the collector
93+
*/
94+
public HibernateStatisticsCollector enablePerQueryMetrics() {
95+
this.perQueryMetricsEnabled = true;
96+
return this;
97+
}
98+
7799
@Override
78100
public List<MetricFamilySamples> collect() {
79101
List<MetricFamilySamples> metrics = new ArrayList<MetricFamilySamples>();
@@ -82,6 +104,9 @@ public List<MetricFamilySamples> collect() {
82104
metrics.addAll(getCacheMetrics());
83105
metrics.addAll(getEntityMetrics());
84106
metrics.addAll(getQueryExecutionMetrics());
107+
if (perQueryMetricsEnabled) {
108+
metrics.addAll(getPerQueryMetrics());
109+
}
85110
return metrics;
86111
}
87112

@@ -448,25 +473,176 @@ public double getValue(Statistics statistics) {
448473
);
449474
}
450475

476+
private List<MetricFamilySamples> getPerQueryMetrics() {
477+
List<MetricFamilySamples> metrics = new ArrayList<MetricFamilySamples>();
478+
479+
metrics.addAll(Arrays.asList(
480+
481+
createCounterForQuery("hibernate_per_query_cache_hit_total",
482+
"Global number of cache hits for query (getCacheHitCount)",
483+
new ValueProviderPerQuery() {
484+
@Override
485+
public double getValue(Statistics statistics, String query) {
486+
return statistics.getQueryStatistics(query)
487+
.getCacheHitCount();
488+
}
489+
}
490+
),
491+
createCounterForQuery("hibernate_per_query_cache_miss_total",
492+
"Global number of cache misses for query (getCacheMissCount)",
493+
new ValueProviderPerQuery() {
494+
@Override
495+
public double getValue(Statistics statistics, String query) {
496+
return statistics.getQueryStatistics(query)
497+
.getCacheMissCount();
498+
}
499+
}
500+
),
501+
createCounterForQuery("hibernate_per_query_cache_put_total",
502+
"Global number of cache puts for query (getCachePutCount)",
503+
new ValueProviderPerQuery() {
504+
@Override
505+
public double getValue(Statistics statistics, String query) {
506+
return statistics.getQueryStatistics(query)
507+
.getCachePutCount();
508+
}
509+
}
510+
),
511+
createCounterForQuery("hibernate_per_query_execution_total",
512+
"Global number of executions for query (getExecutionCount)",
513+
new ValueProviderPerQuery() {
514+
@Override
515+
public double getValue(Statistics statistics, String query) {
516+
return statistics.getQueryStatistics(query)
517+
.getExecutionCount();
518+
}
519+
}
520+
),
521+
createCounterForQuery("hibernate_per_query_execution_rows_total",
522+
"Global number of rows for all executions of query (getExecutionRowCount)",
523+
new ValueProviderPerQuery() {
524+
@Override
525+
public double getValue(Statistics statistics, String query) {
526+
return statistics.getQueryStatistics(query)
527+
.getExecutionRowCount();
528+
}
529+
}
530+
),
531+
createGaugeForQuery("hibernate_per_query_execution_min_seconds",
532+
"Minimum execution time of query in seconds (based on getExecutionMinTime)",
533+
new ValueProviderPerQuery() {
534+
@Override
535+
public double getValue(Statistics statistics, String query) {
536+
return toSeconds(statistics.getQueryStatistics(query)
537+
.getExecutionMinTime());
538+
}
539+
}
540+
),
541+
createGaugeForQuery("hibernate_per_query_execution_max_seconds",
542+
"Maximum execution time of query in seconds (based on getExecutionMaxTime)",
543+
new ValueProviderPerQuery() {
544+
@Override
545+
public double getValue(Statistics statistics, String query) {
546+
return toSeconds(statistics.getQueryStatistics(query)
547+
.getExecutionMaxTime());
548+
}
549+
}
550+
),
551+
createCounterForQuery("hibernate_per_query_execution_seconds_total",
552+
"Accumulated execution time of query in seconds (based on getExecutionTotalTime)",
553+
new ValueProviderPerQuery() {
554+
@Override
555+
public double getValue(Statistics statistics, String query) {
556+
return toSeconds(statistics.getQueryStatistics(query)
557+
.getExecutionTotalTime());
558+
}
559+
}
560+
)
561+
));
562+
563+
return metrics;
564+
}
565+
451566
private CounterMetricFamily createCounter(String metric, String help, ValueProvider provider) {
452567

453568
CounterMetricFamily metricFamily = new CounterMetricFamily(metric, help, LABEL_NAMES);
454569

455570
for (Entry<String, SessionFactory> entry : sessionFactories.entrySet()) {
456571
metricFamily.addMetric(
457-
Collections.singletonList(entry.getKey()),
458-
provider.getValue(entry.getValue().getStatistics())
572+
Collections.singletonList(entry.getKey()),
573+
provider.getValue(entry.getValue().getStatistics())
459574
);
460575
}
461576

462577
return metricFamily;
463578

464579
}
465580

581+
private CounterMetricFamily createCounterForQuery(String metric, String help, ValueProviderPerQuery provider) {
582+
583+
final CounterMetricFamily counters = new CounterMetricFamily(metric, help, LABEL_NAMES_PER_QUERY);
584+
585+
addMetricsForQuery(new PerQuerySamples() {
586+
@Override
587+
public void addMetric(List<String> labelValues, double value) {
588+
counters.addMetric(labelValues, value);
589+
}
590+
}, provider);
591+
592+
return counters;
593+
594+
}
595+
596+
private GaugeMetricFamily createGaugeForQuery(String metric, String help, ValueProviderPerQuery provider) {
597+
598+
final GaugeMetricFamily gauges = new GaugeMetricFamily(metric, help, LABEL_NAMES_PER_QUERY);
599+
600+
addMetricsForQuery(new PerQuerySamples() {
601+
@Override
602+
public void addMetric(List<String> labelValues, double value) {
603+
gauges.addMetric(labelValues, value);
604+
}
605+
}, provider);
606+
607+
return gauges;
608+
609+
}
610+
611+
private void addMetricsForQuery(PerQuerySamples samples, ValueProviderPerQuery provider) {
612+
613+
for (Entry<String, SessionFactory> entry : sessionFactories.entrySet()) {
614+
SessionFactory sessionFactory = entry.getValue();
615+
Statistics stats = sessionFactory.getStatistics();
616+
String unitName = entry.getKey();
617+
618+
for (String query : stats.getQueries()) {
619+
samples.addMetric(Arrays.asList(unitName, query), provider.getValue(stats, query));
620+
}
621+
}
622+
}
623+
624+
private double toSeconds(long milliseconds){
625+
return milliseconds / 1000d;
626+
}
627+
628+
private interface PerQuerySamples {
629+
630+
void addMetric(List<String> labelValues, double value);
631+
632+
}
633+
634+
466635
private interface ValueProvider {
467636

468637
double getValue(Statistics statistics);
469638

470639
}
471640

641+
private interface ValueProviderPerQuery {
642+
643+
double getValue(Statistics statistics, String query);
644+
645+
}
646+
647+
472648
}

simpleclient_hibernate/src/test/java/io/prometheus/client/hibernate/HibernateStatisticsCollectorTest.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package io.prometheus.client.hibernate;
22

33
import static org.hamcrest.CoreMatchers.is;
4+
import static org.hamcrest.CoreMatchers.nullValue;
45
import static org.junit.Assert.assertThat;
6+
import static org.mockito.ArgumentMatchers.eq;
57
import static org.mockito.Mockito.mock;
68
import static org.mockito.Mockito.when;
79

810
import io.prometheus.client.CollectorRegistry;
911
import org.hibernate.SessionFactory;
12+
import org.hibernate.stat.QueryStatistics;
1013
import org.hibernate.stat.Statistics;
1114
import org.junit.Before;
1215
import org.junit.Rule;
@@ -20,13 +23,15 @@ public class HibernateStatisticsCollectorTest {
2023

2124
private SessionFactory sessionFactory;
2225
private Statistics statistics;
26+
private QueryStatistics queryStatistics;
2327
private CollectorRegistry registry;
2428

2529
@Before
2630
public void before() {
2731
registry = new CollectorRegistry();
2832
sessionFactory = mock(SessionFactory.class);
2933
statistics = mock(Statistics.class);
34+
queryStatistics = mock(QueryStatistics.class);
3035
when(sessionFactory.getStatistics()).thenReturn(statistics);
3136
}
3237

@@ -159,6 +164,60 @@ public void shouldPublishQueryExecutionMetrics() {
159164

160165
}
161166

167+
@Test
168+
public void shouldPublishPerQueryMetricsWhenEnabled() {
169+
String query = "query";
170+
mockQueryStatistics(query);
171+
172+
new HibernateStatisticsCollector()
173+
.add(sessionFactory, "factory6")
174+
.enablePerQueryMetrics()
175+
.register(registry);
176+
177+
assertThat(getSampleForQuery("hibernate_per_query_cache_hit_total", "factory6", query), is(1.0));
178+
assertThat(getSampleForQuery("hibernate_per_query_cache_miss_total", "factory6", query), is(2.0));
179+
assertThat(getSampleForQuery("hibernate_per_query_cache_put_total", "factory6", query), is(3.0));
180+
assertThat(getSampleForQuery("hibernate_per_query_execution_max_seconds", "factory6", query), is(0.555d));
181+
assertThat(getSampleForQuery("hibernate_per_query_execution_min_seconds", "factory6", query), is(0.123d));
182+
assertThat(getSampleForQuery("hibernate_per_query_execution_rows_total", "factory6", query), is(7.0));
183+
assertThat(getSampleForQuery("hibernate_per_query_execution_total", "factory6", query), is(8.0));
184+
assertThat(getSampleForQuery("hibernate_per_query_execution_seconds_total", "factory6", query), is(102.540d));
185+
186+
}
187+
188+
@Test
189+
public void shouldNotPublishPerQueryMetricsByDefault() {
190+
String query = "query";
191+
mockQueryStatistics(query);
192+
193+
new HibernateStatisticsCollector()
194+
.add(sessionFactory, "factory7")
195+
.register(registry);
196+
197+
assertThat(getSampleForQuery("hibernate_per_query_cache_hit_total", "factory7", query), nullValue());
198+
assertThat(getSampleForQuery("hibernate_per_query_cache_miss_total", "factory7", query), nullValue());
199+
assertThat(getSampleForQuery("hibernate_per_query_cache_put_total", "factory7", query), nullValue());
200+
assertThat(getSampleForQuery("hibernate_per_query_execution_max_seconds", "factory7", query), nullValue());
201+
assertThat(getSampleForQuery("hibernate_per_query_execution_min_seconds", "factory7", query), nullValue());
202+
assertThat(getSampleForQuery("hibernate_per_query_execution_rows_total", "factory7", query), nullValue());
203+
assertThat(getSampleForQuery("hibernate_per_query_execution_total", "factory7", query), nullValue());
204+
assertThat(getSampleForQuery("hibernate_per_query_execution_seconds", "factory7", query), nullValue());
205+
206+
}
207+
208+
private void mockQueryStatistics(String query) {
209+
when(statistics.getQueries()).thenReturn(new String[]{query});
210+
when(statistics.getQueryStatistics(eq(query))).thenReturn(queryStatistics);
211+
when(queryStatistics.getCacheHitCount()).thenReturn(1L);
212+
when(queryStatistics.getCacheMissCount()).thenReturn(2L);
213+
when(queryStatistics.getCachePutCount()).thenReturn(3L);
214+
when(queryStatistics.getExecutionMaxTime()).thenReturn(555L);
215+
when(queryStatistics.getExecutionMinTime()).thenReturn(123L);
216+
when(queryStatistics.getExecutionRowCount()).thenReturn(7L);
217+
when(queryStatistics.getExecutionCount()).thenReturn(8L);
218+
when(queryStatistics.getExecutionTotalTime()).thenReturn(102540L);
219+
}
220+
162221
@Test
163222
public void shouldFailIfNoSessionFactoriesAreRegistered() {
164223

@@ -175,4 +234,10 @@ private Double getSample(String metric, String factory) {
175234
);
176235
}
177236

237+
private Double getSampleForQuery(String metric, String factory, String query) {
238+
return registry.getSampleValue(
239+
metric, new String[]{"unit", "query"}, new String[]{factory, query}
240+
);
241+
}
242+
178243
}

0 commit comments

Comments
 (0)