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