blob: 5d1a402790d7c7d5d98b05446f9d30ef5b0102e2 [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 static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
import static com.google.gerrit.common.data.GlobalCapability.VIEW_CACHES;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.base.Strings;
import com.google.gerrit.common.Version;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.cache.CacheDisplay;
import com.google.gerrit.server.cache.CacheInfo;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.restapi.config.GetSummary;
import com.google.gerrit.server.restapi.config.GetSummary.JvmSummaryInfo;
import com.google.gerrit.server.restapi.config.GetSummary.MemSummaryInfo;
import com.google.gerrit.server.restapi.config.GetSummary.SummaryInfo;
import com.google.gerrit.server.restapi.config.GetSummary.TaskSummaryInfo;
import com.google.gerrit.server.restapi.config.GetSummary.ThreadSummaryInfo;
import com.google.gerrit.server.restapi.config.ListCaches;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.SshDaemon;
import com.google.inject.Inject;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Map;
import org.apache.sshd.common.io.IoAcceptor;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.mina.MinaSession;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.channel.ChannelSession;
import org.kohsuke.args4j.Option;
/** Show the current cache states. */
@RequiresAnyCapability({VIEW_CACHES, MAINTAIN_SERVER})
@CommandMetaData(
name = "show-caches",
description = "Display current cache statistics",
runsAt = MASTER_OR_SLAVE)
final class ShowCaches extends SshCommand {
private static volatile long serverStarted;
static class StartupListener implements LifecycleListener {
@Override
public void start() {
serverStarted = TimeUtil.nowMs();
}
@Override
public void stop() {}
}
@Option(name = "--show-jvm", usage = "show details about the JVM")
private boolean showJVM;
@Option(name = "--show-threads", usage = "show detailed thread counts")
private boolean showThreads;
@Inject private SshDaemon daemon;
@Inject private ListCaches listCaches;
@Inject private GetSummary getSummary;
@Inject private CurrentUser self;
@Inject private PermissionBackend permissionBackend;
@Option(
name = "--width",
aliases = {"-w"},
metaVar = "COLS",
usage = "width of output table")
private int columns = 80;
private int nw;
@Override
public void start(ChannelSession channel, 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(channel, env);
}
@Override
protected void run() throws Failure {
enableGracefulStop();
nw = columns - 50;
Instant now = Instant.now();
DateTimeFormatter fmt =
DateTimeFormatter.ofPattern("HH:mm:ss zzz").withZone(ZoneId.of("UTC"));
stdout.format(
"%-25s %-20s now %16s\n",
"Gerrit Code Review",
Version.getVersion() != null ? Version.getVersion() : "",
fmt.format(now));
stdout.format(
"%-25s %-20s uptime %16s\n", "", "", uptime(now.toEpochMilli() - serverStarted));
stdout.print('\n');
try {
new CacheDisplay(stdout, nw, getCaches()).displayCaches();
boolean showJvm;
try {
permissionBackend.user(self).check(GlobalPermission.MAINTAIN_SERVER);
showJvm = true;
} catch (AuthException | PermissionBackendException e) {
// Silently ignore and do not display detailed JVM information.
showJvm = false;
}
if (showJvm) {
sshSummary();
SummaryInfo summary = getSummary.setJvm(showJVM).apply(new ConfigResource()).value();
taskSummary(summary.taskSummary);
memSummary(summary.memSummary);
threadSummary(summary.threadSummary);
if (showJVM && summary.jvmSummary != null) {
jvmSummary(summary.jvmSummary);
}
}
} catch (Exception e) {
throw new Failure(1, "unavailable", e);
}
stdout.flush();
}
private Collection<CacheInfo> getCaches() {
@SuppressWarnings("unchecked")
Map<String, CacheInfo> caches =
(Map<String, CacheInfo>) listCaches.apply(new ConfigResource()).value();
for (Map.Entry<String, CacheInfo> entry : caches.entrySet()) {
CacheInfo cache = entry.getValue();
cache.name = entry.getKey();
}
return caches.values();
}
private void memSummary(MemSummaryInfo memSummary) {
stdout.format(
"Mem: %s total = %s used + %s free + %s buffers\n",
memSummary.total, memSummary.used, memSummary.free, memSummary.buffers);
stdout.format(" %s max\n", memSummary.max);
stdout.format(" %8d open files\n", nullToZero(memSummary.openFiles));
stdout.print('\n');
}
private void threadSummary(ThreadSummaryInfo threadSummary) {
stdout.format(
"Threads: %d CPUs available, %d threads\n", threadSummary.cpus, threadSummary.threads);
if (showThreads) {
stdout.print(String.format(" %22s", ""));
for (Thread.State s : Thread.State.values()) {
stdout.print(String.format(" %14s", s.name()));
}
stdout.print('\n');
for (Map.Entry<String, Map<Thread.State, Integer>> e : threadSummary.counts.entrySet()) {
stdout.print(String.format(" %-22s", e.getKey()));
for (Thread.State s : Thread.State.values()) {
stdout.print(String.format(" %14d", nullToZero(e.getValue().get(s))));
}
stdout.print('\n');
}
}
stdout.print('\n');
}
private void taskSummary(TaskSummaryInfo taskSummary) {
stdout.format(
"Tasks: %4d total = %4d running + %4d ready + %4d sleeping\n",
nullToZero(taskSummary.total),
nullToZero(taskSummary.running),
nullToZero(taskSummary.ready),
nullToZero(taskSummary.sleeping));
}
private static int nullToZero(Integer i) {
return i != null ? i : 0;
}
private static long nullToZero(Long i) {
return i != null ? i : 0;
}
private void sshSummary() {
IoAcceptor acceptor = daemon.getIoAcceptor();
if (acceptor == null) {
return;
}
long now = TimeUtil.nowMs();
Collection<IoSession> list = acceptor.getManagedSessions().values();
long oldest = now;
for (IoSession s : list) {
if (s instanceof MinaSession) {
MinaSession minaSession = (MinaSession) s;
oldest = Math.min(oldest, minaSession.getSession().getCreationTime());
}
}
stdout.format(
"SSH: %4d users, oldest session started %s ago\n", list.size(), uptime(now - oldest));
}
private void jvmSummary(JvmSummaryInfo jvmSummary) {
stdout.format("JVM: %s %s %s\n", jvmSummary.vmVendor, jvmSummary.vmName, jvmSummary.vmVersion);
stdout.format(" on %s %s %s\n", jvmSummary.osName, jvmSummary.osVersion, jvmSummary.osArch);
stdout.format(" running as %s on %s\n", jvmSummary.user, Strings.nullToEmpty(jvmSummary.host));
stdout.format(" cwd %s\n", jvmSummary.currentWorkingDirectory);
stdout.format(" site %s\n", jvmSummary.site);
}
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);
}
}