blob: b02d9c857419566d78575ca907057166cfe27154 [file] [log] [blame]
// Copyright (C) 2009 The Android Open Source Project
//
// 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 com.google.gerrit.sshd.commands;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;
import com.google.common.collect.Maps;
import com.google.gerrit.common.Version;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.server.cache.h2.H2CacheImpl;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.Task;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshDaemon;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IoSession;
import org.apache.sshd.server.Environment;
import org.eclipse.jgit.internal.storage.file.WindowCacheStatAccessor;
import org.kohsuke.args4j.Option;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.SortedMap;
/** Show the current cache states. */
@RequiresCapability(GlobalCapability.VIEW_CACHES)
@CommandMetaData(name = "show-caches", description = "Display current cache statistics")
final class ShowCaches extends CacheCommand {
private static volatile long serverStarted;
static class StartupListener implements LifecycleListener {
@Override
public void start() {
serverStarted = System.currentTimeMillis();
}
@Override
public void stop() {
}
}
@Option(name = "--gc", usage = "perform Java GC before printing memory stats")
private boolean gc;
@Option(name = "--show-jvm", usage = "show details about the JVM")
private boolean showJVM;
@Inject
private WorkQueue workQueue;
@Inject
private SshDaemon daemon;
@Inject
@SitePath
private File sitePath;
@Option(name = "--width", aliases = {"-w"}, metaVar = "COLS", usage = "width of output table")
private int columns = 80;
private int nw;
@Override
public void start(Environment env) throws IOException {
String s = env.getEnv().get(Environment.ENV_COLUMNS);
if (s != null && !s.isEmpty()) {
try {
columns = Integer.parseInt(s);
} catch (NumberFormatException err) {
columns = 80;
}
}
super.start(env);
}
@Override
protected void run() {
nw = columns - 50;
Date now = new Date();
stdout.format(
"%-25s %-20s now %16s\n",
"Gerrit Code Review",
Version.getVersion() != null ? Version.getVersion() : "",
new SimpleDateFormat("HH:mm:ss zzz").format(now));
stdout.format(
"%-25s %-20s uptime %16s\n",
"", "",
uptime(now.getTime() - serverStarted));
stdout.print('\n');
stdout.print(String.format(//
"%1s %-"+nw+"s|%-21s| %-5s |%-9s|\n" //
, "" //
, "Name" //
, "Entries" //
, "AvgGet" //
, "Hit Ratio" //
));
stdout.print(String.format(//
"%1s %-"+nw+"s|%6s %6s %7s| %-5s |%-4s %-4s|\n" //
, "" //
, "" //
, "Mem" //
, "Disk" //
, "Space" //
, "" //
, "Mem" //
, "Disk" //
));
stdout.print("--");
for (int i = 0; i < nw; i++) {
stdout.print('-');
}
stdout.print("+---------------------+---------+---------+\n");
Map<String, H2CacheImpl<?, ?>> disks = Maps.newTreeMap();
printMemoryCaches(disks, sortedCoreCaches());
printMemoryCaches(disks, sortedPluginCaches());
for (Map.Entry<String, H2CacheImpl<?, ?>> entry : disks.entrySet()) {
H2CacheImpl<?, ?> cache = entry.getValue();
CacheStats stat = cache.stats();
H2CacheImpl.DiskStats disk = cache.diskStats();
stdout.print(String.format(
"D %-"+nw+"s|%6s %6s %7s| %7s |%4s %4s|\n",
entry.getKey(),
count(cache.size()),
count(disk.size()),
bytes(disk.space()),
duration(stat.averageLoadPenalty()),
percent(stat.hitCount(), stat.requestCount()),
percent(disk.hitCount(), disk.requestCount())));
}
stdout.print('\n');
if (gc) {
System.gc();
System.runFinalization();
System.gc();
}
sshSummary();
taskSummary();
memSummary();
if (showJVM) {
jvmSummary();
}
stdout.flush();
}
private void printMemoryCaches(
Map<String, H2CacheImpl<?, ?>> disks,
Map<String, Cache<?,?>> caches) {
for (Map.Entry<String, Cache<?,?>> entry : caches.entrySet()) {
Cache<?,?> cache = entry.getValue();
if (cache instanceof H2CacheImpl) {
disks.put(entry.getKey(), (H2CacheImpl<?,?>)cache);
continue;
}
CacheStats stat = cache.stats();
stdout.print(String.format(
" %-"+nw+"s|%6s %6s %7s| %7s |%4s %4s|\n",
entry.getKey(),
count(cache.size()),
"",
"",
duration(stat.averageLoadPenalty()),
percent(stat.hitCount(), stat.requestCount()),
""));
}
}
private Map<String, Cache<?, ?>> sortedCoreCaches() {
SortedMap<String, Cache<?, ?>> m = Maps.newTreeMap();
for (Map.Entry<String, Provider<Cache<?, ?>>> entry :
cacheMap.byPlugin("gerrit").entrySet()) {
m.put(cacheNameOf("gerrit", entry.getKey()), entry.getValue().get());
}
return m;
}
private Map<String, Cache<?, ?>> sortedPluginCaches() {
SortedMap<String, Cache<?, ?>> m = Maps.newTreeMap();
for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
if (!"gerrit".equals(e.getPluginName())) {
m.put(cacheNameOf(e.getPluginName(), e.getExportName()),
e.getProvider().get());
}
}
return m;
}
private void memSummary() {
final Runtime r = Runtime.getRuntime();
final long mMax = r.maxMemory();
final long mFree = r.freeMemory();
final long mTotal = r.totalMemory();
final long mInuse = mTotal - mFree;
final int jgitOpen = WindowCacheStatAccessor.getOpenFiles();
final long jgitBytes = WindowCacheStatAccessor.getOpenBytes();
stdout.format("Mem: %s total = %s used + %s free + %s buffers\n",
bytes(mTotal),
bytes(mInuse - jgitBytes),
bytes(mFree),
bytes(jgitBytes));
stdout.format(" %s max\n", bytes(mMax));
stdout.format(" %8d open files, %8d cpus available, %8d threads\n",
jgitOpen,
r.availableProcessors(),
ManagementFactory.getThreadMXBean().getThreadCount());
stdout.print('\n');
}
private void taskSummary() {
Collection<Task<?>> pending = workQueue.getTasks();
int tasksTotal = pending.size();
int tasksRunning = 0, tasksReady = 0, tasksSleeping = 0;
for (Task<?> task : pending) {
switch (task.getState()) {
case RUNNING: tasksRunning++; break;
case READY: tasksReady++; break;
case SLEEPING: tasksSleeping++; break;
case CANCELLED:
case DONE:
case OTHER:
break;
}
}
stdout.format(
"Tasks: %4d total = %4d running + %4d ready + %4d sleeping\n",
tasksTotal,
tasksRunning,
tasksReady,
tasksSleeping);
}
private void sshSummary() {
IoAcceptor acceptor = daemon.getIoAcceptor();
if (acceptor == null) {
return;
}
long now = System.currentTimeMillis();
Collection<IoSession> list = acceptor.getManagedSessions().values();
long oldest = now;
for (IoSession s : list) {
oldest = Math.min(oldest, s.getCreationTime());
}
stdout.format(
"SSH: %4d users, oldest session started %s ago\n",
list.size(),
uptime(now - oldest));
}
private void jvmSummary() {
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
stdout.format("JVM: %s %s %s\n",
runtimeBean.getVmVendor(),
runtimeBean.getVmName(),
runtimeBean.getVmVersion());
stdout.format(" on %s %s %s\n",
osBean.getName(),
osBean.getVersion(),
osBean.getArch());
try {
stdout.format(" running as %s on %s\n",
System.getProperty("user.name"),
InetAddress.getLocalHost().getHostName());
} catch (UnknownHostException e) {
}
stdout.format(" cwd %s\n", path(new File(".").getAbsoluteFile().getParentFile()));
stdout.format(" site %s\n", path(sitePath));
}
private String path(File file) {
try {
return file.getCanonicalPath();
} catch (IOException err) {
return file.getAbsolutePath();
}
}
private String uptime(long uptimeMillis) {
if (uptimeMillis < 1000) {
return String.format("%3d ms", uptimeMillis);
}
long uptime = uptimeMillis / 1000L;
long min = uptime / 60;
if (min < 60) {
return String.format("%2d min %2d sec", min, uptime - min * 60);
}
long hr = uptime / 3600;
if (hr < 24) {
min = (uptime - hr * 3600) / 60;
return String.format("%2d hrs %2d min", hr, min);
}
long days = uptime / (24 * 3600);
hr = (uptime - (days * 24 * 3600)) / 3600;
return String.format("%4d days %2d hrs", days, hr);
}
private String bytes(double value) {
value /= 1024;
String suffix = "k";
if (value > 1024) {
value /= 1024;
suffix = "m";
}
if (value > 1024) {
value /= 1024;
suffix = "g";
}
return String.format("%1$6.2f%2$s", value, suffix);
}
private String count(long cnt) {
if (cnt == 0) {
return "";
}
return String.format("%6d", cnt);
}
private String duration(double ns) {
if (ns < 0.5) {
return "";
}
String suffix = "ns";
if (ns >= 1000.0) {
ns /= 1000.0;
suffix = "us";
}
if (ns >= 1000.0) {
ns /= 1000.0;
suffix = "ms";
}
if (ns >= 1000.0) {
ns /= 1000.0;
suffix = "s ";
}
return String.format("%4.1f%s", ns, suffix);
}
private String percent(final long value, final long total) {
if (total <= 0) {
return "";
}
final long pcent = (100 * value) / total;
return String.format("%3d%%", (int) pcent);
}
}