Skip to content

Commit

Permalink
Merge pull request #32 from linkedin/remove-replay
Browse files Browse the repository at this point in the history
temporarily removing 'replay' functionality.
  • Loading branch information
jesseward authored Nov 6, 2017
2 parents 9a64ec7 + 60bb88a commit 729030a
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 90 deletions.
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,10 @@ There are additional options that you can take advantage of. See below output:
How many iterations of jstat to run before stopping
-n, --no-jstat-output
Do not show jstat output - only print summary
-r [FILE], --replay [FILE]
Replay a previously saved default is
/tmp/jtune_data-{user}.bin.bz2 file
-p PID, --pid PID Which java PID should I attach to

* You can also have it stop after X number of YGCs, FGCs, or jstat iterations (-y, -s, -c respectively). If you want it to make tuning suggestions, you'll want to let it run for at least 3 FGCs (-s <#>) before exiting.
* There may be cases where you want jtune to optimize for a given number of CMS GCs, you can do this with the '-o #' parameter. Right now you can specify a range between 0 and 11 which corresponds to the 180 CMS/min to 1 CMS/min respectively. In most cases you can leave it as default. The way this parameter is used will likely change.
* There may be cases where you see something odd in the suggestions, or want to save the data structures jtune used for further analysis. By default jtune saves this data in /tmp/jtune_data-{user}.bin.bz2. JTune can replay this file by passing it a -r \<path\> parameter.

Command Output
--------------
Expand Down
140 changes: 55 additions & 85 deletions jtune/jtune.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
@author Eric Bullen <[email protected]>
@application jtune.py
@version 2.0.3
@version 3.0.0
@abstract This tool will give detailed information about the running
JVM in real-time. It produces useful information that can
further assist the user in debugging and optimization.
Expand All @@ -26,7 +26,6 @@
import logging
import math
import os
import pickle
import re
import resource
import shlex
Expand Down Expand Up @@ -459,7 +458,7 @@ def reduce_k(size=None, precision=2, short_form=True, _place_holder=0):
return "{0} {1}".format(value, iec_scale[_place_holder])


def _run_analysis(gc_data=None, jmap_data=None, jstat_data=None, proc_details=None, replay_file=None, optimized_for_ygcs_rate=None):
def _run_analysis(gc_data=None, jmap_data=None, jstat_data=None, proc_details=None, optimized_for_ygcs_rate=None):
"""The meat-and-potatoes of this tool. This takes in numerous data structures,
and prints out a report of the analysis of them."""

Expand Down Expand Up @@ -1676,7 +1675,7 @@ def _get_widths(jstat_data=None, short_fields=False):
return widths


def _at_exit(raw_gc_log=None, jmap_data=None, jstat_data=None, proc_details=None, replay_file=None, optimized_for_ygcs_rate=None):
def _at_exit(raw_gc_log=None, jmap_data=None, jstat_data=None, proc_details=None, optimized_for_ygcs_rate=None):
"""The exit function that is called when the user presses ctrl-c, or when it exits after X number
of jstat iterations. It calls various functions to display useful information to the end-user."""

Expand Down Expand Up @@ -1728,7 +1727,7 @@ def _at_exit(raw_gc_log=None, jmap_data=None, jstat_data=None, proc_details=None
if in_stanza:
entry.append(line)

_run_analysis(gc_data, jmap_data, jstat_data, proc_details, replay_file, optimized_for_ygcs_rate)
_run_analysis(gc_data, jmap_data, jstat_data, proc_details, optimized_for_ygcs_rate)


def get_rotated_log_file(gc_log_file):
Expand Down Expand Up @@ -1808,25 +1807,18 @@ def main():
else:
group = parser.add_mutually_exclusive_group(required=True)

group.add_argument('-r', '--replay', dest="replay_file", const="/tmp/jtune_data-{0}.bin.bz2".format(user), help="Replay a previously saved default is /tmp/jtune_data-{0}.bin.bz2 file".format(user), metavar="FILE", nargs="?", default=None)
group.add_argument('-p', '--pid', help='Which java PID should I attach to', type=int)
group.add_argument('--gc-stdin', help='Read GC log data from stdin', action="store_true")

cmd_args = parser.parse_args()

replay_file = cmd_args.replay_file
raw_gc_log_data = list()
jmap_data = list()
jstat_data = list()
proc_details = list()

if DEBUG:
# Need to define it here for Pycharm (so I don't
# have to set up arguments).
replay_file = "/tmp/some_file"

if not (cmd_args.pid or cmd_args.gc_stdin) and not os.path.isfile(replay_file):
logger.error("The replay file '{0}' does not exist, or is not a file.".format(replay_file))
if not (cmd_args.pid or cmd_args.gc_stdin):
logger.error("Please specify -p (pid) or --gc-stdin")
sys.exit(1)

# A ygc of 1/min
Expand All @@ -1853,96 +1845,74 @@ def main():
logger.error("You must specify -s, -y, or -c arguments for this option to work.")
sys.exit(1)

if replay_file:
if not cmd_args.gc_stdin:
try:
with open(replay_file, "rb") as _file:
proc_details, jstat_data, display.display_output, jmap_data, raw_gc_log_data = pickle.loads(_file.read().decode('bz2'))
except (ValueError, IOError):
logger.error("I was not able to read the replay file. Exiting.")
sys.exit(1)
else:
print "* Note: Used cached data found in {0}.".format(replay_file)
else:
if not cmd_args.gc_stdin:
try:
config_error = False
proc_details = get_proc_info(cmd_args.pid)

java_path, proc_uptime = proc_details['java_path'], proc_details['proc_uptime_seconds']
config_error = False
proc_details = get_proc_info(cmd_args.pid)

if proc_details.get("min_heap_size", 0) != proc_details.get("max_heap_size", 1):
config_error = True
logger.error(
"It looks like either you didn't specify your min and max heap size (-Xms & -Xmx respectively), or they are set to two different sizes. They need to be set to the same for jtune.py to work properly.")
java_path, proc_uptime = proc_details['java_path'], proc_details['proc_uptime_seconds']

if not proc_details.get("print_gc_date_stamps", False):
config_error = True
logger.error("You need to include the '-XX:PrintGCDateStamps' option to the JVM for JTune to work correctly.")
if proc_details.get("min_heap_size", 0) != proc_details.get("max_heap_size", 1):
config_error = True
logger.error(
"It looks like either you didn't specify your min and max heap size (-Xms & -Xmx respectively), or they are set to two different sizes. They need to be set to the same for jtune.py to work properly.")

if not proc_details.get("print_gc_details", False):
config_error = True
logger.error("You need to include the '-XX:PrintGCDetails' option to the JVM for JTune to work correctly.")
if not proc_details.get("print_gc_date_stamps", False):
config_error = True
logger.error("You need to include the '-XX:PrintGCDateStamps' option to the JVM for JTune to work correctly.")

if not proc_details.get("print_tenuring_distribution", False):
config_error = True
logger.error("You need to include the '-XX:+PrintTenuringDistribution' option to the JVM for JTune to work correctly.")
if not proc_details.get("print_gc_details", False):
config_error = True
logger.error("You need to include the '-XX:PrintGCDetails' option to the JVM for JTune to work correctly.")

if not proc_details.get("survivor_ratio", False):
logger.warning("You probably want to include the '-XX:SurvivorRatio=<num>' option to the JVM for JTune to work correctly.")
if not proc_details.get("print_tenuring_distribution", False):
config_error = True
logger.error("You need to include the '-XX:+PrintTenuringDistribution' option to the JVM for JTune to work correctly.")

if not proc_details.get("use_cms", False):
config_error = True
logger.error("You need to include the '-XX:+UseConcMarkSweepGC' option to the JVM for JTune to work correctly.")
if not proc_details.get("survivor_ratio", False):
logger.warning("You probably want to include the '-XX:SurvivorRatio=<num>' option to the JVM for JTune to work correctly.")

if not proc_details.get("use_parnew", False):
config_error = True
logger.error("You need to include the '-XX:+UseParNewGC' option to the JVM for JTune to work correctly.")
if not proc_details.get("use_cms", False):
config_error = True
logger.error("You need to include the '-XX:+UseConcMarkSweepGC' option to the JVM for JTune to work correctly.")

if config_error:
logger.error("Exiting.")
sys.exit(1)
if not proc_details.get("use_parnew", False):
config_error = True
logger.error("You need to include the '-XX:+UseParNewGC' option to the JVM for JTune to work correctly.")

except (TypeError, KeyError):
logger.error("I was not able to get the process data for pid {0}".format(cmd_args.pid))
if config_error:
logger.error("Exiting.")
sys.exit(1)

###########################################
# Start the gc log watching in a subprocess
back_secs = 300
gc_log_file = get_gc_log_file(proc_details)
except (TypeError, KeyError):
logger.error("I was not able to get the process data for pid {0}".format(cmd_args.pid))
sys.exit(1)

if not gc_log_file:
logger.error("\n".join(textwrap.wrap("I was not able to find a GC log for this process. Is the instance up?", display.textwrap_offset)))
sys.exit(1)
###########################################
# Start the gc log watching in a subprocess
back_secs = 300
gc_log_file = get_gc_log_file(proc_details)

####################################################
# Get the file offset before starting jstat, so
# I can use it after jstat runs to read the log file
gc_log_file_pos = os.stat(gc_log_file).st_size
if not gc_log_file:
logger.error("\n".join(textwrap.wrap("I was not able to find a GC log for this process. Is the instance up?", display.textwrap_offset)))
sys.exit(1)

jmap_data = get_jmap_data(cmd_args.pid, proc_details)
####################################################
# Get the file offset before starting jstat, so
# I can use it after jstat runs to read the log file
gc_log_file_pos = os.stat(gc_log_file).st_size

if cmd_args.no_jstat_output:
jstat_data = dict()
else:
jstat_data = run_jstat(cmd_args.pid, java_path, cmd_args.no_jstat_output, cmd_args.fgc_stop_count, cmd_args.stop_count, cmd_args.ygc_stop_count)
jmap_data = get_jmap_data(cmd_args.pid, proc_details)

# This basically hits after the user ctrl-c's
raw_gc_log_data = process_gclog(gc_log_file, gc_log_file_pos)
if cmd_args.no_jstat_output:
jstat_data = dict()
else:
jstat_data = run_jstat(cmd_args.pid, java_path, cmd_args.no_jstat_output, cmd_args.fgc_stop_count, cmd_args.stop_count, cmd_args.ygc_stop_count)

#####################################################
# Keep the last dump of data in case there's an issue
try:
with open("/tmp/jtune_data-{0}.bin.bz2".format(user), "wb") as _file:
os.chmod("/tmp/jtune_data-{0}.bin.bz2".format(user), 0666)
_file.write(pickle.dumps((proc_details, jstat_data, display.display_output, jmap_data, raw_gc_log_data), pickle.HIGHEST_PROTOCOL).encode('bz2'))
except IOError as msg:
logger.error("\n".join(textwrap.wrap("I was not able to write to /tmp/jtune_data-{0}.bin.bz2 (no saving of state): {1}".format(user, msg), display.textwrap_offset)))
# This basically hits after the user ctrl-c's
raw_gc_log_data = process_gclog(gc_log_file, gc_log_file_pos)

if DEBUG:
_at_exit(raw_gc_log_data, jmap_data, jstat_data, proc_details, replay_file, optimized_for_ygcs_rate)
else:
atexit.register(_at_exit, raw_gc_log_data, jmap_data, jstat_data, proc_details, replay_file, optimized_for_ygcs_rate)
atexit.register(_at_exit, raw_gc_log_data, jmap_data, jstat_data, proc_details, optimized_for_ygcs_rate)


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

setup(
name='jtune',
version='2.0.3',
version='3.0.0',
description=description,
long_description=description,
url='https://github.com/linkedin/JTune',
Expand Down

0 comments on commit 729030a

Please sign in to comment.