001package io.prometheus.client.hibernate; 002 003import io.prometheus.client.Collector; 004import io.prometheus.client.CollectorRegistry; 005import io.prometheus.client.CounterMetricFamily; 006import io.prometheus.client.GaugeMetricFamily; 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.Collections; 010import java.util.List; 011import java.util.Map; 012import java.util.Map.Entry; 013import java.util.concurrent.ConcurrentHashMap; 014import org.hibernate.SessionFactory; 015import org.hibernate.stat.Statistics; 016 017/** 018 * Collect metrics from one or more Hibernate SessionFactory instances. 019 * <p> 020 * Usage example for a single session factory: 021 * <pre> 022 * new HibernateStatisticsCollector(sessionFactory, "myapp").register(); 023 * </pre> 024 * Usage example for multiple session factories: 025 * <pre> 026 * new HibernateStatisticsCollector() 027 * .add(sessionFactory1, "myapp1") 028 * .add(sessionFactory2, "myapp2") 029 * .register(); 030 * </pre> 031 * If you are in a JPA environment, you can obtain the SessionFactory like this: 032 * <pre> 033 * SessionFactory sessionFactory = 034 * entityManagerFactory.unwrap(SessionFactory.class); 035 * </pre> 036 * <p> 037 * When {@code enablePerQueryMetrics()} has been called, certain metrics like execution 038 * time are collected per query. This may create a lot of monitoring data, so it should 039 * be used with caution. 040 * 041 * @author Christian Kaltepoth 042 */ 043public class HibernateStatisticsCollector extends Collector { 044 045 private static final List<String> LABEL_NAMES = Collections.singletonList("unit"); 046 047 private static final List<String> LABEL_NAMES_PER_QUERY = Arrays.asList("unit", "query"); 048 049 private final Map<String, SessionFactory> sessionFactories = new ConcurrentHashMap<String, SessionFactory>(); 050 051 private boolean perQueryMetricsEnabled; 052 053 /** 054 * Creates an empty collector. If you use this constructor, you have to add one or more 055 * session factories to the collector by calling the {@link #add(SessionFactory, String)} 056 * method. 057 */ 058 public HibernateStatisticsCollector() { 059 // nothing 060 } 061 062 /** 063 * Creates a new collector for the given session factory. Calling this constructor 064 * has the same effect as creating an empty collector and adding the session factory 065 * using {@link #add(SessionFactory, String)}. 066 * 067 * @param sessionFactory The Hibernate SessionFactory to collect metrics for 068 * @param name A unique name for this SessionFactory 069 */ 070 public HibernateStatisticsCollector(SessionFactory sessionFactory, String name) { 071 add(sessionFactory, name); 072 } 073 074 /** 075 * Registers a Hibernate SessionFactory with this collector. 076 * 077 * @param sessionFactory The Hibernate SessionFactory to collect metrics for 078 * @param name A unique name for this SessionFactory 079 * @return Returns the collector 080 */ 081 public HibernateStatisticsCollector add(SessionFactory sessionFactory, String name) { 082 sessionFactories.put(name, sessionFactory); 083 return this; 084 } 085 086 /** 087 * Enables collection of per-query metrics. Produces a lot of monitoring data, so use with caution. 088 * <p> 089 * Per-query metrics have a label "query" with the actual HQL query as value. The query will contain 090 * placeholders ("?") instead of the real parameter values (example: {@code select u from User u where id=?}). 091 * 092 * @return Returns the collector 093 */ 094 public HibernateStatisticsCollector enablePerQueryMetrics() { 095 this.perQueryMetricsEnabled = true; 096 return this; 097 } 098 099 @Override 100 public List<MetricFamilySamples> collect() { 101 List<MetricFamilySamples> metrics = new ArrayList<MetricFamilySamples>(); 102 metrics.addAll(getSessionMetrics()); 103 metrics.addAll(getConnectionMetrics()); 104 metrics.addAll(getCacheMetrics()); 105 metrics.addAll(getEntityMetrics()); 106 metrics.addAll(getQueryExecutionMetrics()); 107 if (perQueryMetricsEnabled) { 108 metrics.addAll(getPerQueryMetrics()); 109 } 110 return metrics; 111 } 112 113 @Override 114 public <T extends Collector> T register(CollectorRegistry registry) { 115 if (sessionFactories.isEmpty()) { 116 throw new IllegalStateException("You must register at least one SessionFactory."); 117 } 118 return super.register(registry); 119 } 120 121 private List<MetricFamilySamples> getSessionMetrics() { 122 return Arrays.<MetricFamilySamples>asList( 123 createCounter( 124 "hibernate_session_opened_total", 125 "Global number of sessions opened (getSessionOpenCount)", 126 new ValueProvider() { 127 @Override 128 public double getValue(Statistics statistics) { 129 return statistics.getSessionOpenCount(); 130 } 131 } 132 ), 133 createCounter( 134 "hibernate_session_closed_total", 135 "Global number of sessions closed (getSessionCloseCount)", 136 new ValueProvider() { 137 @Override 138 public double getValue(Statistics statistics) { 139 return statistics.getSessionCloseCount(); 140 } 141 } 142 ), 143 createCounter( 144 "hibernate_flushed_total", 145 "The global number of flushes executed by sessions (getFlushCount)", 146 new ValueProvider() { 147 @Override 148 public double getValue(Statistics statistics) { 149 return statistics.getFlushCount(); 150 } 151 } 152 ), 153 createCounter( 154 "hibernate_connect_total", 155 "The global number of connections requested by the sessions (getConnectCount)", 156 new ValueProvider() { 157 @Override 158 public double getValue(Statistics statistics) { 159 return statistics.getConnectCount(); 160 } 161 } 162 ), 163 createCounter( 164 "hibernate_optimistic_failure_total", 165 "The number of StaleObjectStateExceptions that occurred (getOptimisticFailureCount)", 166 new ValueProvider() { 167 @Override 168 public double getValue(Statistics statistics) { 169 return statistics.getOptimisticFailureCount(); 170 } 171 } 172 ) 173 ); 174 } 175 176 private List<MetricFamilySamples> getConnectionMetrics() { 177 return Arrays.<MetricFamilySamples>asList( 178 createCounter( 179 "hibernate_statement_prepared_total", 180 "The number of prepared statements that were acquired (getPrepareStatementCount)", 181 new ValueProvider() { 182 @Override 183 public double getValue(Statistics statistics) { 184 return statistics.getPrepareStatementCount(); 185 } 186 } 187 ), 188 createCounter( 189 "hibernate_statement_closed_total", 190 "The number of prepared statements that were released (getCloseStatementCount)", 191 new ValueProvider() { 192 @Override 193 public double getValue(Statistics statistics) { 194 return statistics.getCloseStatementCount(); 195 } 196 } 197 ), 198 createCounter( 199 "hibernate_transaction_total", 200 "The number of transactions we know to have completed (getTransactionCount)", 201 new ValueProvider() { 202 @Override 203 public double getValue(Statistics statistics) { 204 return statistics.getTransactionCount(); 205 } 206 } 207 ), 208 createCounter( 209 "hibernate_transaction_success_total", 210 "The number of transactions we know to have been successful (getSuccessfulTransactionCount)", 211 new ValueProvider() { 212 @Override 213 public double getValue(Statistics statistics) { 214 return statistics.getSuccessfulTransactionCount(); 215 } 216 } 217 ) 218 ); 219 } 220 221 private List<MetricFamilySamples> getCacheMetrics() { 222 return Arrays.<MetricFamilySamples>asList( 223 createCounter( 224 "hibernate_second_level_cache_hit_total", 225 "Global number of cacheable entities/collections successfully retrieved from the cache (getSecondLevelCacheHitCount)", 226 new ValueProvider() { 227 @Override 228 public double getValue(Statistics statistics) { 229 return statistics.getSecondLevelCacheHitCount(); 230 } 231 } 232 ), 233 createCounter( 234 "hibernate_second_level_cache_miss_total", 235 "Global number of cacheable entities/collections not found in the cache and loaded from the database (getSecondLevelCacheMissCount)", 236 new ValueProvider() { 237 @Override 238 public double getValue(Statistics statistics) { 239 return statistics.getSecondLevelCacheMissCount(); 240 } 241 } 242 ), 243 createCounter( 244 "hibernate_second_level_cache_put_total", 245 "Global number of cacheable entities/collections put in the cache (getSecondLevelCachePutCount)", 246 new ValueProvider() { 247 @Override 248 public double getValue(Statistics statistics) { 249 return statistics.getSecondLevelCachePutCount(); 250 } 251 } 252 ), 253 createCounter( 254 "hibernate_query_cache_hit_total", 255 "The global number of cached queries successfully retrieved from cache (getQueryCacheHitCount)", 256 new ValueProvider() { 257 @Override 258 public double getValue(Statistics statistics) { 259 return statistics.getQueryCacheHitCount(); 260 } 261 } 262 ), 263 createCounter( 264 "hibernate_query_cache_miss_total", 265 "The global number of cached queries not found in cache (getQueryCacheMissCount)", 266 new ValueProvider() { 267 @Override 268 public double getValue(Statistics statistics) { 269 return statistics.getQueryCacheMissCount(); 270 } 271 } 272 ), 273 createCounter( 274 "hibernate_query_cache_put_total", 275 "The global number of cacheable queries put in cache (getQueryCachePutCount)", 276 new ValueProvider() { 277 @Override 278 public double getValue(Statistics statistics) { 279 return statistics.getQueryCachePutCount(); 280 } 281 } 282 ), 283 createCounter( 284 "hibernate_natural_id_cache_hit_total", 285 "The global number of cached naturalId lookups successfully retrieved from cache (getNaturalIdCacheHitCount)", 286 new ValueProvider() { 287 @Override 288 public double getValue(Statistics statistics) { 289 return statistics.getNaturalIdCacheHitCount(); 290 } 291 } 292 ), 293 createCounter( 294 "hibernate_natural_id_cache_miss_total", 295 "The global number of cached naturalId lookups not found in cache (getNaturalIdCacheMissCount)", 296 new ValueProvider() { 297 @Override 298 public double getValue(Statistics statistics) { 299 return statistics.getNaturalIdCacheMissCount(); 300 } 301 } 302 ), 303 createCounter( 304 "hibernate_natural_id_cache_put_total", 305 "The global number of cacheable naturalId lookups put in cache (getNaturalIdCachePutCount)", 306 new ValueProvider() { 307 @Override 308 public double getValue(Statistics statistics) { 309 return statistics.getNaturalIdCachePutCount(); 310 } 311 } 312 ), 313 createCounter( 314 "hibernate_update_timestamps_cache_hit_total", 315 "The global number of timestamps successfully retrieved from cache (getUpdateTimestampsCacheHitCount)", 316 new ValueProvider() { 317 @Override 318 public double getValue(Statistics statistics) { 319 return statistics.getUpdateTimestampsCacheHitCount(); 320 } 321 } 322 ), 323 createCounter( 324 "hibernate_update_timestamps_cache_miss_total", 325 "The global number of tables for which no update timestamps was not found in cache (getUpdateTimestampsCacheMissCount)", 326 new ValueProvider() { 327 @Override 328 public double getValue(Statistics statistics) { 329 return statistics.getUpdateTimestampsCacheMissCount(); 330 } 331 } 332 ), 333 createCounter( 334 "hibernate_update_timestamps_cache_put_total", 335 "The global number of timestamps put in cache (getUpdateTimestampsCachePutCount)", 336 new ValueProvider() { 337 @Override 338 public double getValue(Statistics statistics) { 339 return statistics.getUpdateTimestampsCachePutCount(); 340 } 341 } 342 ) 343 ); 344 } 345 346 private List<MetricFamilySamples> getEntityMetrics() { 347 return Arrays.<MetricFamilySamples>asList( 348 createCounter( 349 "hibernate_entity_delete_total", 350 "Global number of entity deletes (getEntityDeleteCount)", 351 new ValueProvider() { 352 @Override 353 public double getValue(Statistics statistics) { 354 return statistics.getEntityDeleteCount(); 355 } 356 } 357 ), 358 createCounter( 359 "hibernate_entity_insert_total", 360 "Global number of entity inserts (getEntityInsertCount)", 361 new ValueProvider() { 362 @Override 363 public double getValue(Statistics statistics) { 364 return statistics.getEntityInsertCount(); 365 } 366 } 367 ), 368 createCounter( 369 "hibernate_entity_load_total", 370 "Global number of entity loads (getEntityLoadCount)", 371 new ValueProvider() { 372 @Override 373 public double getValue(Statistics statistics) { 374 return statistics.getEntityLoadCount(); 375 } 376 } 377 ), 378 createCounter( 379 "hibernate_entity_fetch_total", 380 "Global number of entity fetches (getEntityFetchCount)", 381 new ValueProvider() { 382 @Override 383 public double getValue(Statistics statistics) { 384 return statistics.getEntityFetchCount(); 385 } 386 } 387 ), 388 createCounter( 389 "hibernate_entity_update_total", 390 "Global number of entity updates (getEntityUpdateCount)", 391 new ValueProvider() { 392 @Override 393 public double getValue(Statistics statistics) { 394 return statistics.getEntityUpdateCount(); 395 } 396 } 397 ), 398 createCounter( 399 "hibernate_collection_load_total", 400 "Global number of collections loaded (getCollectionLoadCount)", 401 new ValueProvider() { 402 @Override 403 public double getValue(Statistics statistics) { 404 return statistics.getCollectionLoadCount(); 405 } 406 } 407 ), 408 createCounter( 409 "hibernate_collection_fetch_total", 410 "Global number of collections fetched (getCollectionFetchCount)", 411 new ValueProvider() { 412 @Override 413 public double getValue(Statistics statistics) { 414 return statistics.getCollectionFetchCount(); 415 } 416 } 417 ), 418 createCounter( 419 "hibernate_collection_update_total", 420 "Global number of collections updated (getCollectionUpdateCount)", 421 new ValueProvider() { 422 @Override 423 public double getValue(Statistics statistics) { 424 return statistics.getCollectionUpdateCount(); 425 } 426 } 427 ), 428 createCounter( 429 "hibernate_collection_remove_total", 430 "Global number of collections removed (getCollectionRemoveCount)", 431 new ValueProvider() { 432 @Override 433 public double getValue(Statistics statistics) { 434 return statistics.getCollectionRemoveCount(); 435 } 436 } 437 ), 438 createCounter( 439 "hibernate_collection_recreate_total", 440 "Global number of collections recreated (getCollectionRecreateCount)", 441 new ValueProvider() { 442 @Override 443 public double getValue(Statistics statistics) { 444 return statistics.getCollectionRecreateCount(); 445 } 446 } 447 ) 448 ); 449 } 450 451 private List<MetricFamilySamples> getQueryExecutionMetrics() { 452 return Arrays.<MetricFamilySamples>asList( 453 createCounter( 454 "hibernate_query_execution_total", 455 "Global number of executed queries (getQueryExecutionCount)", 456 new ValueProvider() { 457 @Override 458 public double getValue(Statistics statistics) { 459 return statistics.getQueryExecutionCount(); 460 } 461 } 462 ), 463 createCounter( 464 "hibernate_natural_id_query_execution_total", 465 "The global number of naturalId queries executed against the database (getNaturalIdQueryExecutionCount)", 466 new ValueProvider() { 467 @Override 468 public double getValue(Statistics statistics) { 469 return statistics.getNaturalIdQueryExecutionCount(); 470 } 471 } 472 ) 473 ); 474 } 475 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 566 private CounterMetricFamily createCounter(String metric, String help, ValueProvider provider) { 567 568 CounterMetricFamily metricFamily = new CounterMetricFamily(metric, help, LABEL_NAMES); 569 570 for (Entry<String, SessionFactory> entry : sessionFactories.entrySet()) { 571 metricFamily.addMetric( 572 Collections.singletonList(entry.getKey()), 573 provider.getValue(entry.getValue().getStatistics()) 574 ); 575 } 576 577 return metricFamily; 578 579 } 580 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 635 private interface ValueProvider { 636 637 double getValue(Statistics statistics); 638 639 } 640 641 private interface ValueProviderPerQuery { 642 643 double getValue(Statistics statistics, String query); 644 645 } 646 647 648}