/**
* Copyright 2014 Alexey Ragozin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gridkit.jvmtool.cmd;
import java.util.concurrent.TimeUnit;
import org.gridkit.jvmtool.cli.CommandLauncher;
import org.gridkit.jvmtool.cli.TimeIntervalConverter;
import org.gridkit.jvmtool.cli.CommandLauncher.CmdRef;
import org.gridkit.lab.jvm.attach.HeapHisto;
import org.gridkit.lab.jvm.perfdata.JStatData;
import org.gridkit.lab.jvm.perfdata.JStatData.LongCounter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;
/**
* Heap histogram command.
*
* @author Alexey Ragozin (alexey.ragozin@gmail.com)
*/
public class HeapHistoCmd implements CmdRef {
@Override
public String getCommandName() {
return "hh";
}
@Override
public Runnable newCommand(CommandLauncher host) {
return new Histo(host);
}
@Parameters(commandDescription = "[Heap Histo] Prints class histogram, similar to jmap -histo")
public static class Histo implements Runnable {
@ParametersDelegate
private CommandLauncher host;
@Parameter(names = {"-p", "--pid"}, description = "Process ID")
private int pid;
@Parameter(names = "--live", description = "Live objects histogram")
private boolean live = false;
@Parameter(names = "--dead", description = "Dead objects histogram")
private boolean dead = false;
@Parameter(names = "--dead-young", description = "Histogram for sample of dead young objects")
private boolean deadYoung = false;
@Parameter(names = "--young", description = "Histogram for sample of new objects")
private boolean young = false;
@Parameter(names = {"-d", "--sample-depth"}, converter = TimeIntervalConverter.class, description = "Used with --dead-young and --young options. Specific time duration to collect young population.")
private long youngSampleDepth = 10000;
@Parameter(names = {"-n", "--top-number"}, description = "Show only N top buckets")
private int n = Integer.MAX_VALUE;
public Histo(CommandLauncher host) {
this.host = host;
}
@Override
public void run() {
try {
int x = 0;
x += live ? 1 : 0;
x += dead ? 1 : 0;
x += deadYoung ? 1 : 0;
x += young ? 1 : 0;
if (x > 1) {
host.failAndPrintUsage("--live, --dead, --young and --deadYoung are mutually exclusive");
}
// else if (x < 1) {
// host.failAndPrintUsage("--live, --dead, --young, --deadYoung - one of options is required");
// }
HeapHisto histo;
if (live) {
histo = HeapHisto.getHistoLive(pid, 300000);
}
else if (dead) {
histo = HeapHisto.getHistoDead(pid, 300000);
}
else if (deadYoung) {
histo = collectDeadYoung();
}
else if (young) {
histo = collectYoung();
}
else {
// all histo - default behavior
histo = HeapHisto.getHistoAll(pid, 300000);
}
System.out.println(String.format("%4s %14s%15s %s", "#", "Instances", "Bytes", "Type"));
System.out.println(histo.print(n));
} catch (Exception e) {
host.fail(e.toString(), e);
}
}
private HeapHisto collectDeadYoung() throws InterruptedException {
// Force full GC
long ygc = 0;
LongCounter youngGcCnt = null;
try {
JStatData jsd = JStatData.connect(pid);
youngGcCnt = (LongCounter) jsd.getAllCounters().get("sun.gc.collector.0.invocations");
ygc = youngGcCnt == null ? 0 : youngGcCnt.getLong();
}
catch(Exception e) {
// ignore
}
HeapHisto.getHistoLive(pid, 3000000);
System.out.println("Gathering young garbage ...");
long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(youngSampleDepth);
while(System.nanoTime() < deadline) {
long sleepTime = TimeUnit.NANOSECONDS.toMillis(deadline - System.nanoTime());
if (sleepTime > 0) {
Thread.sleep(sleepTime);
}
else {
break;
}
}
HeapHisto histo = HeapHisto.getHistoDead(pid, 300000);
if (youngGcCnt != null) {
if (ygc != youngGcCnt.getLong()) {
System.out.println("Warning: one or more young collections have occured during sampling.");
System.out.println("Use --sample-depth option to reduce time to sample if needed.");
}
}
if (youngSampleDepth % 1000 == 0) {
System.out.println("Garbage histogram for last " + (youngSampleDepth/1000) + "s");
}
else {
System.out.println("Garbage histogram for last " + youngSampleDepth + "ms");
}
return histo;
}
private HeapHisto collectYoung() throws InterruptedException {
// Force full GC
long ygc = 0;
LongCounter youngGcCnt = null;
try {
JStatData jsd = JStatData.connect(pid);
youngGcCnt = (LongCounter) jsd.getAllCounters().get("sun.gc.collector.0.invocations");
ygc = youngGcCnt == null ? 0 : youngGcCnt.getLong();
}
catch(Exception e) {
// ignore
}
HeapHisto retained = HeapHisto.getHistoLive(pid, 3000000);
System.out.println("Gathering young population ...");
long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(youngSampleDepth);
while(System.nanoTime() < deadline) {
long sleepTime = TimeUnit.NANOSECONDS.toMillis(deadline - System.nanoTime());
if (sleepTime > 0) {
Thread.sleep(sleepTime);
}
else {
break;
}
}
HeapHisto histo = HeapHisto.getHistoAll(pid, 300000);
if (youngGcCnt != null) {
if (ygc != youngGcCnt.getLong()) {
System.out.println("Warning: one or more young collections have occured during sampling.");
System.out.println("Use --sample-depth option to reduce time to sample if needed.");
}
}
if (youngSampleDepth % 1000 == 0) {
System.out.println("Garbage histogram for last " + (youngSampleDepth/1000) + "s");
}
else {
System.out.println("Garbage histogram for last " + youngSampleDepth + "ms");
}
return HeapHisto.subtract(histo, retained);
}
}
}