dd_jvm_stats.sh 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. #!/usr/bin/env bash
  2. set -e
  3. OUTPUT_ROOT=$(pwd)
  4. # Thread dump interval defaults to 10 mins
  5. THREAD_DUMP_INTERVAL_SECS=600
  6. # Heap dump interval defaults to 30 mins
  7. HEAP_DUMP_INTERVAL_SECS=1800
  8. # GC usage interval defaults to 30 secs
  9. GC_INTERVAL_SECS=30
  10. # Runtime stats interval defaults to 10 secs
  11. RUNTIME_INTERVAL_SECS=10
  12. # Default runs continuously
  13. STOP_AFTER_SECS=0
  14. function usage {
  15. echo "Usage: ./dd_jvm_stats --pid <PID> [--STOP_AFTER_SECS <SECS:0>] [--runtime-interval <SECS:10>] [--thread-interval <SECS:600>] [--heap-interval <SECS:1800>] [--gc-interval <SECS:30>] [--runtime-interval <SECS:10>] [--output <PATH:./>]"
  16. }
  17. while [[ "$1" != "" ]]; do
  18. case $1 in
  19. --pid ) shift
  20. PROCESS_IDENTIFIER=$1
  21. ;;
  22. --output ) shift
  23. OUTPUT_ROOT=$1
  24. ;;
  25. --thread-interval ) shift
  26. THREAD_DUMP_INTERVAL_SECS=$1
  27. ;;
  28. --heap-interval ) shift
  29. HEAP_DUMP_INTERVAL_SECS=$1
  30. ;;
  31. --gc-interval ) shift
  32. GC_INTERVAL_SECS=$1
  33. ;;
  34. --runtime-interval ) shift
  35. RUNTIME_INTERVAL_SECS=$1
  36. ;;
  37. --stop-after ) shift
  38. STOP_AFTER_SECS=$1
  39. ;;
  40. --help ) usage
  41. exit
  42. ;;
  43. * ) usage
  44. exit 1
  45. esac
  46. shift
  47. done
  48. # At least PID has to be provided
  49. if [[ -z "$PROCESS_IDENTIFIER" ]]
  50. then
  51. usage
  52. echo "Provide the correct process id:"
  53. jcmd
  54. exit 1
  55. fi
  56. HUMAN_READABLE_FORMAT='+%Y-%m-%d %H:%M:%S'
  57. FILESYSTEM_READABLE_FORMAT='+%Y%m%d_%H%M%S'
  58. OUTPUT_FOLDER="${OUTPUT_ROOT}/monitoring_session_started_$(date "$FILESYSTEM_READABLE_FORMAT")"
  59. THREAD_DUMP_FOLDER="$OUTPUT_FOLDER/thread_dumps"
  60. HEAP_DUMP_FOLDER="$OUTPUT_FOLDER/heap_dumps"
  61. GC_STATS_FILE="$OUTPUT_FOLDER/gc_stats.csv"
  62. RUNTIME_STATS_FILE="$OUTPUT_FOLDER/runtime_stats.csv"
  63. JVM_INFO_FILE="$OUTPUT_FOLDER/jvm_info.txt"
  64. # Preparing output folders
  65. echo "Data will be saved to $OUTPUT_FOLDER"
  66. mkdir -p ${OUTPUT_FOLDER}
  67. mkdir -p ${THREAD_DUMP_FOLDER}
  68. mkdir -p ${HEAP_DUMP_FOLDER}
  69. # Dumping JVM INFO
  70. echo "JVM version $(jcmd ${PROCESS_IDENTIFIER} VM.version)" > ${JVM_INFO_FILE}
  71. echo "" >> ${JVM_INFO_FILE}
  72. echo "JVM uptime $(jcmd ${PROCESS_IDENTIFIER} VM.uptime)" >> ${JVM_INFO_FILE}
  73. echo "" >> ${JVM_INFO_FILE}
  74. echo "JVM command line $(jcmd ${PROCESS_IDENTIFIER} VM.command_line)" >> ${JVM_INFO_FILE}
  75. echo "" >> ${JVM_INFO_FILE}
  76. # Initializing files
  77. echo "Date,CPU%,S0C,S1C,S0U,S1U,EC,EU,OC,OU,MC,MU,CCSC,CCSU,YGC,YGCT,FGC,FGCT,GCT" > ${RUNTIME_STATS_FILE}
  78. START=$(date +"%s")
  79. GC_LOGS_FILE=$(jcmd ${PROCESS_IDENTIFIER} VM.command_line | grep jvm_args | sed 's/^.*Xlog[:]*gc:\([^\s]*\) .*$/\1/')
  80. if [[ -z "$GC_LOGS_FILE" ]]; then
  81. echo "GC logs file was not found, did you set jvm args for GC logs?: for JDK 9+ '-Xlog:gc:/path/to/gc.log', JDK 8 '-Xloggc:/path/to/gc.log -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps'"
  82. else
  83. echo "Found GC logs file: $GC_LOGS_FILE"
  84. fi
  85. LAST_GC=0
  86. LAST_CPU=0
  87. LAST_RUNTIME=0
  88. LAST_HEAP=${START}
  89. LAST_THREAD=${START}
  90. function tick() {
  91. NOW=$(date +"%s")
  92. NOW_HUMAN=$(date "${HUMAN_READABLE_FORMAT}")
  93. NOW_FILESYSTEM=$(date "${FILESYSTEM_READABLE_FORMAT}")
  94. # We can set a stop after a number of seconds
  95. if [[ "$STOP_AFTER_SECS" -gt "0" ]] && [[ "$NOW" -gt $(( $START + $STOP_AFTER_SECS )) ]]; then
  96. echo "Maximum running time reached: job completed successfully."
  97. exit 0
  98. fi
  99. # Runtime live
  100. if [[ "$NOW" -ge $(( $LAST_RUNTIME + $RUNTIME_INTERVAL_SECS )) ]]; then
  101. CPU_USAGE=$(ps -p ${PROCESS_IDENTIFIER} -o %cpu | grep '\.')
  102. MEMORY_USAGE=$(jstat -gc ${PROCESS_IDENTIFIER} | grep '\.' | sed -E 's/ +/,/g')
  103. echo "$NOW_HUMAN,$CPU_USAGE,$MEMORY_USAGE" >> ${RUNTIME_STATS_FILE}
  104. LAST_RUNTIME=${NOW}
  105. fi
  106. # GC logs
  107. if [[ "$NOW" -ge $(( $LAST_GC + $GC_INTERVAL_SECS )) ]] && [[ ! -z "$GC_LOGS_FILE" ]]; then
  108. cp ${GC_LOGS_FILE} ${GC_STATS_FILE}
  109. LAST_GC=${NOW}
  110. fi
  111. # Thread dump
  112. if [[ "$NOW" -ge $(( $LAST_THREAD + $THREAD_DUMP_INTERVAL_SECS )) ]]; then
  113. THREAD_FILE="${THREAD_DUMP_FOLDER}/thread-dump-${NOW_FILESYSTEM}"
  114. jcmd ${PROCESS_IDENTIFIER} Thread.print > ${THREAD_FILE}
  115. LAST_THREAD=${NOW}
  116. echo "${NOW_HUMAN} - Thread dump saved to: $THREAD_FILE"
  117. fi
  118. # Heap dump
  119. if [[ "$NOW" -ge $(( $LAST_HEAP + $HEAP_DUMP_INTERVAL_SECS )) ]]; then
  120. HEAP_FILE="${HEAP_DUMP_FOLDER}/heap-dump-${NOW_FILESYSTEM}.hprof"
  121. jcmd ${PROCESS_IDENTIFIER} GC.heap_dump ${HEAP_FILE}
  122. LAST_HEAP=${NOW}
  123. echo "${NOW_HUMAN} - Heap dump saved to: $HEAP_FILE"
  124. fi
  125. }
  126. while sleep 1; do tick; done