1
18 package net.bull.javamelody.internal.web;
19
20 import java.io.IOException;
21 import java.io.PrintWriter;
22 import java.text.DecimalFormat;
23 import java.text.DecimalFormatSymbols;
24 import java.util.Collection;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.regex.Pattern;
30
31 import net.bull.javamelody.internal.common.Parameters;
32 import net.bull.javamelody.internal.model.CacheInformations;
33 import net.bull.javamelody.internal.model.Collector;
34 import net.bull.javamelody.internal.model.Counter;
35 import net.bull.javamelody.internal.model.CounterRequest;
36 import net.bull.javamelody.internal.model.JCacheInformations;
37 import net.bull.javamelody.internal.model.JRobin;
38 import net.bull.javamelody.internal.model.JavaInformations;
39 import net.bull.javamelody.internal.model.MemoryInformations;
40 import net.bull.javamelody.internal.model.TomcatInformations;
41
42
150 class PrometheusController {
151
152 private static final String METRIC_PREFIX = "javamelody_";
153
154 private static final Pattern CAMEL_TO_SNAKE_PATTERN = Pattern.compile("([a-z])([A-Z]+)");
155 private static final Pattern SANITIZE_TO_UNDERSCORE_PATTERN = Pattern.compile("[- :]");
156 private static final Pattern SANITIZE_REMOVE_PATTERN = Pattern.compile("[^a-z0-9_]");
157
158 private static final String EMPTY_STRING = "";
159 private static final String UNDERSCORE = "_";
160
161 private enum MetricType {
162 GAUGE("gauge"), COUNTER("counter");
163
164 private final String code;
165
166 MetricType(String code) {
167 this.code = code;
168 }
169
170 public String getCode() {
171 return code;
172 }
173 }
174
175 private final JavaInformations javaInformations;
176 private final Collector collector;
177 private final PrintWriter out;
178 private final DecimalFormat decimalFormat;
179
180 PrometheusController(List<JavaInformations> javaInformations, Collector collector,
181 PrintWriter out) throws IOException {
182 super();
183 assert javaInformations != null && !javaInformations.isEmpty();
184 assert collector != null;
185 assert out != null;
186
187
188
189 if (javaInformations.size() > 1) {
190 throw new IOException(
191 "Prometheus from collector server is not supported for several nodes in one application"
192 + " - configure Prometheus to scrape nodes directly or declare several applications in the collector server.");
193 }
194 this.javaInformations = javaInformations.get(0);
195 this.collector = collector;
196 this.out = out;
197
198 decimalFormat = new DecimalFormat();
199 final DecimalFormatSymbols decimalFormatSymbols = DecimalFormatSymbols
200 .getInstance(Locale.US);
201
202 decimalFormatSymbols.setNaN("NaN");
203 decimalFormat.setDecimalFormatSymbols(decimalFormatSymbols);
204 decimalFormat.setGroupingUsed(false);
205 decimalFormat.setMinimumIntegerDigits(1);
206 decimalFormat.setMaximumFractionDigits(15);
207 }
208
209
214 void report(boolean includeLastValue) throws IOException {
215
216
217 reportOnMemoryInformations(javaInformations.getMemoryInformations());
218
219
220 reportOnJavaInformations();
221
222
223 if (javaInformations.getTomcatInformationsList() != null) {
224 reportOnTomcatInformations();
225 }
226
227
228 if (javaInformations.isCacheEnabled()) {
229 reportOnCacheInformations();
230 }
231 if (javaInformations.isJCacheEnabled()) {
232 reportOnJCacheInformations();
233 }
234
235 reportOnCollector();
236
237 if (includeLastValue) {
238 reportOnLastValues();
239 }
240 }
241
242
243 private void reportOnCacheInformations() {
244
245 final List<CacheInformations> cacheInformationsList = javaInformations
246 .getCacheInformationsList();
247 final Map<String, CacheInformations> cacheInfos = new LinkedHashMap<>(
248 cacheInformationsList.size());
249 for (final CacheInformations cacheInfo : cacheInformationsList) {
250 final String fields = "{cache_name=\"" + sanitizeName(cacheInfo.getName()) + "\"}";
251 cacheInfos.put(fields, cacheInfo);
252 }
253 printHeader(MetricType.GAUGE, "cache_in_memory_count", "cache in memory count");
254 for (final Map.Entry<String, CacheInformations> entry : cacheInfos.entrySet()) {
255 printLongWithFields("cache_in_memory_count", entry.getKey(),
256 entry.getValue().getInMemoryObjectCount());
257 }
258 printHeader(MetricType.GAUGE, "cache_in_memory_used_pct", "in memory used percent");
259 for (final Map.Entry<String, CacheInformations> entry : cacheInfos.entrySet()) {
260 printDoubleWithFields("cache_in_memory_used_pct", entry.getKey(),
261 (double) entry.getValue().getInMemoryPercentUsed() / 100);
262 }
263 printHeader(MetricType.GAUGE, "cache_in_memory_hits_pct", "cache in memory hit percent");
264 for (final Map.Entry<String, CacheInformations> entry : cacheInfos.entrySet()) {
265 printDoubleWithFields("cache_in_memory_hits_pct", entry.getKey(),
266 (double) entry.getValue().getInMemoryHitsRatio() / 100);
267 }
268 printHeader(MetricType.GAUGE, "cache_on_disk_count", "cache on disk count");
269 for (final Map.Entry<String, CacheInformations> entry : cacheInfos.entrySet()) {
270 printLongWithFields("cache_on_disk_count", entry.getKey(),
271 entry.getValue().getOnDiskObjectCount());
272 }
273 printHeader(MetricType.GAUGE, "cache_hits_pct", "cache hits percent");
274 for (final Map.Entry<String, CacheInformations> entry : cacheInfos.entrySet()) {
275 printDoubleWithFields("cache_hits_pct", entry.getKey(),
276 (double) entry.getValue().getHitsRatio() / 100);
277 }
278 printHeader(MetricType.COUNTER, "cache_in_memory_hits_count",
279 "total cache in memory hit count");
280 for (final Map.Entry<String, CacheInformations> entry : cacheInfos.entrySet()) {
281 printLongWithFields("cache_in_memory_hits_count", entry.getKey(),
282 entry.getValue().getInMemoryHits());
283 }
284 printHeader(MetricType.COUNTER, "cache_hits_count", "total cache hit count");
285 for (final Map.Entry<String, CacheInformations> entry : cacheInfos.entrySet()) {
286 printLongWithFields("cache_hits_count", entry.getKey(),
287 entry.getValue().getCacheHits());
288 }
289 printHeader(MetricType.COUNTER, "cache_misses_count", "total cache misses count");
290 for (final Map.Entry<String, CacheInformations> entry : cacheInfos.entrySet()) {
291 printLongWithFields("cache_misses_count", entry.getKey(),
292 entry.getValue().getCacheMisses());
293 }
294 }
295
296 private void reportOnJCacheInformations() {
297 final List<JCacheInformations> jcacheInformationsList = javaInformations
298 .getJCacheInformationsList();
299 final Map<String, JCacheInformations> cacheInfos = new LinkedHashMap<>(
300 jcacheInformationsList.size());
301 for (final JCacheInformations cacheInfo : jcacheInformationsList) {
302 final String fields = "{cache_name=\"" + sanitizeName(cacheInfo.getName()) + "\"}";
303 cacheInfos.put(fields, cacheInfo);
304 }
305 printHeader(MetricType.GAUGE, "jcache_hits_pct", "cache hits percent");
306 for (final Map.Entry<String, JCacheInformations> entry : cacheInfos.entrySet()) {
307 printDoubleWithFields("jcache_hits_pct", entry.getKey(),
308 (double) entry.getValue().getHitsRatio() / 100);
309 }
310 printHeader(MetricType.COUNTER, "jcache_hits_count", "total cache hit count");
311 for (final Map.Entry<String, JCacheInformations> entry : cacheInfos.entrySet()) {
312 printLongWithFields("jcache_hits_count", entry.getKey(),
313 entry.getValue().getCacheHits());
314 }
315 printHeader(MetricType.COUNTER, "jcache_misses_count", "total cache misses count");
316 for (final Map.Entry<String, JCacheInformations> entry : cacheInfos.entrySet()) {
317 printLongWithFields("jcache_misses_count", entry.getKey(),
318 entry.getValue().getCacheMisses());
319 }
320 }
321
322
323 private void reportOnTomcatInformations() {
324
325 final Map<String, TomcatInformations> tcInfos = new LinkedHashMap<>();
326 for (final TomcatInformations tcInfo : javaInformations.getTomcatInformationsList()) {
327 if (tcInfo.getRequestCount() > 0) {
328 final String fields = "{tomcat_name=\"" + sanitizeName(tcInfo.getName()) + "\"}";
329 tcInfos.put(fields, tcInfo);
330 }
331 }
332 if (tcInfos.isEmpty()) {
333 return;
334 }
335 printHeader(MetricType.GAUGE, "tomcat_threads_max", "tomcat max threads");
336 for (final Map.Entry<String, TomcatInformations> entry : tcInfos.entrySet()) {
337 printLongWithFields("tomcat_threads_max", entry.getKey(),
338 entry.getValue().getMaxThreads());
339 }
340 printHeader(MetricType.GAUGE, "tomcat_thread_busy_count", "tomcat currently busy threads");
341 for (final Map.Entry<String, TomcatInformations> entry : tcInfos.entrySet()) {
342 printLongWithFields("tomcat_thread_busy_count", entry.getKey(),
343 entry.getValue().getCurrentThreadsBusy());
344 }
345 printHeader(MetricType.COUNTER, "tomcat_received_bytes", "tomcat total received bytes");
346 for (final Map.Entry<String, TomcatInformations> entry : tcInfos.entrySet()) {
347 printLongWithFields("tomcat_received_bytes", entry.getKey(),
348 entry.getValue().getBytesReceived());
349 }
350 printHeader(MetricType.COUNTER, "tomcat_sent_bytes", "tomcat total sent bytes");
351 for (final Map.Entry<String, TomcatInformations> entry : tcInfos.entrySet()) {
352 printLongWithFields("tomcat_sent_bytes", entry.getKey(),
353 entry.getValue().getBytesSent());
354 }
355 printHeader(MetricType.COUNTER, "tomcat_request_count", "tomcat total request count");
356 for (final Map.Entry<String, TomcatInformations> entry : tcInfos.entrySet()) {
357 printLongWithFields("tomcat_request_count", entry.getKey(),
358 entry.getValue().getRequestCount());
359 }
360 printHeader(MetricType.COUNTER, "tomcat_error_count", "tomcat total error count");
361 for (final Map.Entry<String, TomcatInformations> entry : tcInfos.entrySet()) {
362 printLongWithFields("tomcat_error_count", entry.getKey(),
363 entry.getValue().getErrorCount());
364 }
365 printHeader(MetricType.COUNTER, "tomcat_processing_time_millis",
366 "tomcat total processing time");
367 for (final Map.Entry<String, TomcatInformations> entry : tcInfos.entrySet()) {
368 printLongWithFields("tomcat_processing_time_millis", entry.getKey(),
369 entry.getValue().getProcessingTime());
370 }
371 printHeader(MetricType.GAUGE, "tomcat_max_time_millis",
372 "tomcat max time for single request");
373 for (final Map.Entry<String, TomcatInformations> entry : tcInfos.entrySet()) {
374 printLongWithFields("tomcat_max_time_millis", entry.getKey(),
375 entry.getValue().getMaxTime());
376 }
377 }
378
379
385 private void reportOnCollector() {
386 for (final Counter counter : collector.getCounters()) {
387 if (!counter.isDisplayed()) {
388 continue;
389 }
390 final List<CounterRequest> requests = counter.getRequests();
391 long hits = 0;
392 long duration = 0;
393 long errors = 0;
394 for (final CounterRequest cr : requests) {
395 hits += cr.getHits();
396 duration += cr.getDurationsSum();
397 errors += cr.getSystemErrors();
398 }
399
400 final String sanitizedName = sanitizeName(counter.getName());
401 printLong(MetricType.COUNTER, sanitizedName + "_hits_count", "javamelody counter",
402 hits);
403 if (!counter.isErrorCounter() || counter.isJobCounter()) {
404
405 printLong(MetricType.COUNTER, sanitizedName + "_errors_count", "javamelody counter",
406 errors);
407 }
408 if (duration >= 0) {
409
410 printLong(MetricType.COUNTER, sanitizedName + "_duration_millis",
411 "javamelody counter", duration);
412 }
413 }
414 }
415
416
429 private void reportOnLastValues() throws IOException {
430 Collection<JRobin> jrobins = collector.getDisplayedCounterJRobins();
431 for (final JRobin jrobin : jrobins) {
432 printDouble(MetricType.GAUGE, "last_value_" + camelToSnake(jrobin.getName()),
433 "javamelody value per minute", jrobin.getLastValue());
434 }
435
436 jrobins = collector.getDisplayedOtherJRobins();
437 for (final JRobin jrobin : jrobins) {
438 printDouble(MetricType.GAUGE, "last_value_" + camelToSnake(jrobin.getName()),
439 "javamelody value per minute", jrobin.getLastValue());
440 }
441 }
442
443
446 private void reportOnJavaInformations() {
447
448 if (javaInformations.getSessionCount() >= 0) {
449 printLong(MetricType.GAUGE, "sessions_active_count", "active session count",
450 javaInformations.getSessionCount());
451 printLong(MetricType.GAUGE, "sessions_age_avg_minutes", "session avg age in minutes",
452 javaInformations.getSessionMeanAgeInMinutes());
453 }
454
455
456 if (!Parameters.isNoDatabase()) {
457 printLong(MetricType.COUNTER, "transactions_count", "transactions count",
458 javaInformations.getTransactionCount());
459 printLong(MetricType.GAUGE, "connections_used_count", "used connections count",
460 javaInformations.getUsedConnectionCount());
461 printLong(MetricType.GAUGE, "connections_active_count", "active connections",
462 javaInformations.getActiveConnectionCount());
463 if (javaInformations.getMaxConnectionCount() > 0) {
464 printLong(MetricType.GAUGE, "connections_max_count", "max connections",
465 javaInformations.getMaxConnectionCount());
466 printDouble(MetricType.GAUGE, "connections_used_pct", "used connections percentage",
467 javaInformations.getUsedConnectionPercentage());
468 }
469 }
470
471
472 if (javaInformations.getSystemLoadAverage() >= 0) {
473 printDouble(MetricType.GAUGE, "system_load_avg", "system load average",
474 javaInformations.getSystemLoadAverage());
475 }
476 if (javaInformations.getSystemCpuLoad() >= 0) {
477 printDouble(MetricType.GAUGE, "system_cpu_load_pct", "system cpu load",
478 javaInformations.getSystemCpuLoad());
479 }
480 if (javaInformations.getUnixOpenFileDescriptorCount() >= 0) {
481 printDouble(MetricType.GAUGE, "system_unix_file_descriptors_open_count",
482 "unix open file descriptors count",
483 javaInformations.getUnixOpenFileDescriptorCount());
484 printDouble(MetricType.GAUGE, "system_unix_file_descriptors_max",
485 "unix file descriptors max", javaInformations.getUnixMaxFileDescriptorCount());
486 printDouble(MetricType.GAUGE, "system_unix_file_descriptors_open_pct",
487 "unix open file descriptors percentage",
488 javaInformations.getUnixOpenFileDescriptorPercentage());
489 }
490 printLong(MetricType.GAUGE, "system_tmp_space_free_bytes", "tmp space available",
491 javaInformations.getFreeDiskSpaceInTemp());
492 printLong(MetricType.GAUGE, "system_tmp_space_usable_bytes", "tmp space usable",
493 javaInformations.getUsableDiskSpaceInTemp());
494
495
496 printLong(MetricType.GAUGE, "jvm_start_time", "jvm start time",
497 javaInformations.getStartDate().getTime());
498 printLong(MetricType.COUNTER, "jvm_cpu_millis", "jvm cpu millis",
499 javaInformations.getProcessCpuTimeMillis());
500 printLong(MetricType.GAUGE, "system_processors_count", "processors available",
501 javaInformations.getAvailableProcessors());
502
503
504 printLong(MetricType.GAUGE, "threads_count", "threads count",
505 javaInformations.getThreadCount());
506 printLong(MetricType.GAUGE, "threads_max_count", "threads peak count",
507 javaInformations.getPeakThreadCount());
508 printLong(MetricType.COUNTER, "threads_started_count", "total threads started",
509 javaInformations.getTotalStartedThreadCount());
510 printLong(MetricType.GAUGE, "threads_active_count", "active thread count",
511 javaInformations.getActiveThreadCount());
512
513
514 if (javaInformations.isJobEnabled()) {
515 printLong(MetricType.GAUGE, "job_executing_count", "executing job count",
516 javaInformations.getCurrentlyExecutingJobCount());
517 }
518 }
519
520 private void reportOnMemoryInformations(MemoryInformations memoryInformations) {
521 printLong(MetricType.GAUGE, "memory_used_bytes", "used memory in bytes",
522 memoryInformations.getUsedMemory());
523 printLong(MetricType.GAUGE, "memory_max_bytes", "max memory in bytes",
524 memoryInformations.getMaxMemory());
525 printDouble(MetricType.GAUGE, "memory_used_pct", "memory used percentage",
526 memoryInformations.getUsedMemoryPercentage());
527 if (memoryInformations.getUsedPermGen() > 0) {
528 printLong(MetricType.GAUGE, "memory_perm_gen_used_bytes",
529 "used perm gen memory in bytes", memoryInformations.getUsedPermGen());
530 if (memoryInformations.getMaxPermGen() > 0) {
531 printLong(MetricType.GAUGE, "memory_perm_gen_max_bytes",
532 "max perm gen memory in bytes", memoryInformations.getMaxPermGen());
533 printDouble(MetricType.GAUGE, "memory_perm_gen_used_pct",
534 "used perm gen memory percentage",
535 memoryInformations.getUsedPermGenPercentage());
536 }
537 }
538
539 printDouble(MetricType.COUNTER, "memory_gc_millis", "gc time millis",
540 memoryInformations.getGarbageCollectionTimeMillis());
541
542 if (memoryInformations.getUsedBufferedMemory() >= 0) {
543 printLong(MetricType.GAUGE, "memory_used_buffered_bytes",
544 "used buffered memory in bytes", memoryInformations.getUsedBufferedMemory());
545 }
546 printLong(MetricType.GAUGE, "memory_used_non_heap_bytes", "used non-heap memory in bytes",
547 memoryInformations.getUsedNonHeapMemory());
548 if (memoryInformations.getUsedSwapSpaceSize() >= 0) {
549 printLong(MetricType.GAUGE, "memory_used_swap_space_bytes",
550 "used memory in the OS swap space in bytes",
551 memoryInformations.getUsedSwapSpaceSize());
552 }
553 if (memoryInformations.getUsedPhysicalMemorySize() > 0) {
554 printLong(MetricType.GAUGE, "memory_used_physical_bytes",
555 "used memory in the OS in bytes",
556 memoryInformations.getUsedPhysicalMemorySize());
557 }
558 printLong(MetricType.GAUGE, "loaded_classes_count", "loaded classes count",
559 memoryInformations.getLoadedClassesCount());
560 }
561
562
567 private static String camelToSnake(String camel) {
568 return CAMEL_TO_SNAKE_PATTERN.matcher(camel).replaceAll("$1_$2").toLowerCase(Locale.US);
569 }
570
571
576 private static String sanitizeName(String name) {
577 final String lowerCaseName = name.toLowerCase(Locale.US);
578 final String separatorReplacedName = SANITIZE_TO_UNDERSCORE_PATTERN.matcher(lowerCaseName)
579 .replaceAll(UNDERSCORE);
580 return SANITIZE_REMOVE_PATTERN.matcher(separatorReplacedName).replaceAll(EMPTY_STRING);
581 }
582
583
584 private void printLong(MetricType metricType, String name, String description, long value) {
585 printHeader(metricType, name, description);
586 printLongWithFields(name, null, value);
587 }
588
589
590 private void printDouble(MetricType metricType, String name, String description, double value) {
591 printHeader(metricType, name, description);
592 printDoubleWithFields(name, null, value);
593 }
594
595
596 private void printLongWithFields(String name, String fields, long value) {
597 print(METRIC_PREFIX);
598 print(name);
599 if (fields != null) {
600 print(fields);
601 }
602 print(' ');
603 println(String.valueOf(value));
604 }
605
606
607 private void printDoubleWithFields(String name, String fields, double value) {
608 print(METRIC_PREFIX);
609 print(name);
610 if (fields != null) {
611 print(fields);
612 }
613 print(' ');
614 println(decimalFormat.format(value));
615 }
616
617
618 private void printHeader(MetricType metricType, String name, String description) {
619 print("# HELP ");
620 print(METRIC_PREFIX);
621 print(name);
622 print(' ');
623 println(description);
624
625 print("# TYPE ");
626 print(METRIC_PREFIX);
627 print(name);
628 print(' ');
629 println(metricType.getCode());
630 }
631
632 private void print(String s) {
633 out.print(s);
634 }
635
636 private void print(char c) {
637 out.print(c);
638 }
639
640 private void println(String s) {
641 out.print(s);
642
643
644
645 out.print('\n');
646 }
647 }
648