| // 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); | |
| } | |
| } |