Line data Source code
1 : // Copyright (C) 2009 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.sshd.commands;
16 :
17 : import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
18 : import static com.google.gerrit.common.data.GlobalCapability.VIEW_CACHES;
19 : import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
20 :
21 : import com.google.common.base.Strings;
22 : import com.google.gerrit.common.Version;
23 : import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
24 : import com.google.gerrit.extensions.events.LifecycleListener;
25 : import com.google.gerrit.extensions.restapi.AuthException;
26 : import com.google.gerrit.server.CurrentUser;
27 : import com.google.gerrit.server.cache.CacheDisplay;
28 : import com.google.gerrit.server.cache.CacheInfo;
29 : import com.google.gerrit.server.config.ConfigResource;
30 : import com.google.gerrit.server.permissions.GlobalPermission;
31 : import com.google.gerrit.server.permissions.PermissionBackend;
32 : import com.google.gerrit.server.permissions.PermissionBackendException;
33 : import com.google.gerrit.server.restapi.config.GetSummary;
34 : import com.google.gerrit.server.restapi.config.GetSummary.JvmSummaryInfo;
35 : import com.google.gerrit.server.restapi.config.GetSummary.MemSummaryInfo;
36 : import com.google.gerrit.server.restapi.config.GetSummary.SummaryInfo;
37 : import com.google.gerrit.server.restapi.config.GetSummary.TaskSummaryInfo;
38 : import com.google.gerrit.server.restapi.config.GetSummary.ThreadSummaryInfo;
39 : import com.google.gerrit.server.restapi.config.ListCaches;
40 : import com.google.gerrit.server.util.time.TimeUtil;
41 : import com.google.gerrit.sshd.CommandMetaData;
42 : import com.google.gerrit.sshd.SshCommand;
43 : import com.google.gerrit.sshd.SshDaemon;
44 : import com.google.inject.Inject;
45 : import java.io.IOException;
46 : import java.time.Instant;
47 : import java.time.ZoneId;
48 : import java.time.format.DateTimeFormatter;
49 : import java.util.Collection;
50 : import java.util.Map;
51 : import org.apache.sshd.common.io.IoAcceptor;
52 : import org.apache.sshd.common.io.IoSession;
53 : import org.apache.sshd.mina.MinaSession;
54 : import org.apache.sshd.server.Environment;
55 : import org.apache.sshd.server.channel.ChannelSession;
56 : import org.kohsuke.args4j.Option;
57 :
58 : /** Show the current cache states. */
59 : @RequiresAnyCapability({VIEW_CACHES, MAINTAIN_SERVER})
60 : @CommandMetaData(
61 : name = "show-caches",
62 : description = "Display current cache statistics",
63 : runsAt = MASTER_OR_SLAVE)
64 1 : final class ShowCaches extends SshCommand {
65 : private static volatile long serverStarted;
66 :
67 17 : static class StartupListener implements LifecycleListener {
68 : @Override
69 : public void start() {
70 17 : serverStarted = TimeUtil.nowMs();
71 17 : }
72 :
73 : @Override
74 17 : public void stop() {}
75 : }
76 :
77 : @Option(name = "--gc", usage = "perform Java GC before printing memory stats")
78 : private boolean gc;
79 :
80 : @Option(name = "--show-jvm", usage = "show details about the JVM")
81 : private boolean showJVM;
82 :
83 : @Option(name = "--show-threads", usage = "show detailed thread counts")
84 : private boolean showThreads;
85 :
86 : @Inject private SshDaemon daemon;
87 : @Inject private ListCaches listCaches;
88 : @Inject private GetSummary getSummary;
89 : @Inject private CurrentUser self;
90 : @Inject private PermissionBackend permissionBackend;
91 :
92 1 : @Option(
93 : name = "--width",
94 : aliases = {"-w"},
95 : metaVar = "COLS",
96 : usage = "width of output table")
97 : private int columns = 80;
98 :
99 : private int nw;
100 :
101 : @Override
102 : public void start(ChannelSession channel, Environment env) throws IOException {
103 1 : String s = env.getEnv().get(Environment.ENV_COLUMNS);
104 1 : if (s != null && !s.isEmpty()) {
105 : try {
106 0 : columns = Integer.parseInt(s);
107 0 : } catch (NumberFormatException err) {
108 0 : columns = 80;
109 0 : }
110 : }
111 1 : super.start(channel, env);
112 1 : }
113 :
114 : @Override
115 : protected void run() throws Failure {
116 0 : enableGracefulStop();
117 0 : nw = columns - 50;
118 0 : Instant now = Instant.now();
119 0 : DateTimeFormatter fmt =
120 0 : DateTimeFormatter.ofPattern("HH:mm:ss zzz").withZone(ZoneId.of("UTC"));
121 0 : stdout.format(
122 : "%-25s %-20s now %16s\n",
123 : "Gerrit Code Review",
124 0 : Version.getVersion() != null ? Version.getVersion() : "",
125 0 : fmt.format(now));
126 0 : stdout.format(
127 0 : "%-25s %-20s uptime %16s\n", "", "", uptime(now.toEpochMilli() - serverStarted));
128 0 : stdout.print('\n');
129 :
130 : try {
131 0 : new CacheDisplay(stdout, nw, getCaches()).displayCaches();
132 :
133 : boolean showJvm;
134 : try {
135 0 : permissionBackend.user(self).check(GlobalPermission.MAINTAIN_SERVER);
136 0 : showJvm = true;
137 0 : } catch (AuthException | PermissionBackendException e) {
138 : // Silently ignore and do not display detailed JVM information.
139 0 : showJvm = false;
140 0 : }
141 0 : if (showJvm) {
142 0 : sshSummary();
143 :
144 0 : SummaryInfo summary =
145 0 : getSummary.setGc(gc).setJvm(showJVM).apply(new ConfigResource()).value();
146 0 : taskSummary(summary.taskSummary);
147 0 : memSummary(summary.memSummary);
148 0 : threadSummary(summary.threadSummary);
149 :
150 0 : if (showJVM && summary.jvmSummary != null) {
151 0 : jvmSummary(summary.jvmSummary);
152 : }
153 : }
154 0 : } catch (Exception e) {
155 0 : throw new Failure(1, "unavailable", e);
156 0 : }
157 :
158 0 : stdout.flush();
159 0 : }
160 :
161 : private Collection<CacheInfo> getCaches() {
162 : @SuppressWarnings("unchecked")
163 0 : Map<String, CacheInfo> caches =
164 0 : (Map<String, CacheInfo>) listCaches.apply(new ConfigResource()).value();
165 0 : for (Map.Entry<String, CacheInfo> entry : caches.entrySet()) {
166 0 : CacheInfo cache = entry.getValue();
167 0 : cache.name = entry.getKey();
168 0 : }
169 0 : return caches.values();
170 : }
171 :
172 : private void memSummary(MemSummaryInfo memSummary) {
173 0 : stdout.format(
174 : "Mem: %s total = %s used + %s free + %s buffers\n",
175 : memSummary.total, memSummary.used, memSummary.free, memSummary.buffers);
176 0 : stdout.format(" %s max\n", memSummary.max);
177 0 : stdout.format(" %8d open files\n", nullToZero(memSummary.openFiles));
178 0 : stdout.print('\n');
179 0 : }
180 :
181 : private void threadSummary(ThreadSummaryInfo threadSummary) {
182 0 : stdout.format(
183 : "Threads: %d CPUs available, %d threads\n", threadSummary.cpus, threadSummary.threads);
184 :
185 0 : if (showThreads) {
186 0 : stdout.print(String.format(" %22s", ""));
187 0 : for (Thread.State s : Thread.State.values()) {
188 0 : stdout.print(String.format(" %14s", s.name()));
189 : }
190 0 : stdout.print('\n');
191 0 : for (Map.Entry<String, Map<Thread.State, Integer>> e : threadSummary.counts.entrySet()) {
192 0 : stdout.print(String.format(" %-22s", e.getKey()));
193 0 : for (Thread.State s : Thread.State.values()) {
194 0 : stdout.print(String.format(" %14d", nullToZero(e.getValue().get(s))));
195 : }
196 0 : stdout.print('\n');
197 0 : }
198 : }
199 0 : stdout.print('\n');
200 0 : }
201 :
202 : private void taskSummary(TaskSummaryInfo taskSummary) {
203 0 : stdout.format(
204 : "Tasks: %4d total = %4d running + %4d ready + %4d sleeping\n",
205 0 : nullToZero(taskSummary.total),
206 0 : nullToZero(taskSummary.running),
207 0 : nullToZero(taskSummary.ready),
208 0 : nullToZero(taskSummary.sleeping));
209 0 : }
210 :
211 : private static int nullToZero(Integer i) {
212 0 : return i != null ? i : 0;
213 : }
214 :
215 : private static long nullToZero(Long i) {
216 0 : return i != null ? i : 0;
217 : }
218 :
219 : private void sshSummary() {
220 0 : IoAcceptor acceptor = daemon.getIoAcceptor();
221 0 : if (acceptor == null) {
222 0 : return;
223 : }
224 :
225 0 : long now = TimeUtil.nowMs();
226 0 : Collection<IoSession> list = acceptor.getManagedSessions().values();
227 0 : long oldest = now;
228 :
229 0 : for (IoSession s : list) {
230 0 : if (s instanceof MinaSession) {
231 0 : MinaSession minaSession = (MinaSession) s;
232 0 : oldest = Math.min(oldest, minaSession.getSession().getCreationTime());
233 : }
234 0 : }
235 :
236 0 : stdout.format(
237 0 : "SSH: %4d users, oldest session started %s ago\n", list.size(), uptime(now - oldest));
238 0 : }
239 :
240 : private void jvmSummary(JvmSummaryInfo jvmSummary) {
241 0 : stdout.format("JVM: %s %s %s\n", jvmSummary.vmVendor, jvmSummary.vmName, jvmSummary.vmVersion);
242 0 : stdout.format(" on %s %s %s\n", jvmSummary.osName, jvmSummary.osVersion, jvmSummary.osArch);
243 0 : stdout.format(" running as %s on %s\n", jvmSummary.user, Strings.nullToEmpty(jvmSummary.host));
244 0 : stdout.format(" cwd %s\n", jvmSummary.currentWorkingDirectory);
245 0 : stdout.format(" site %s\n", jvmSummary.site);
246 0 : }
247 :
248 : private String uptime(long uptimeMillis) {
249 0 : if (uptimeMillis < 1000) {
250 0 : return String.format("%3d ms", uptimeMillis);
251 : }
252 :
253 0 : long uptime = uptimeMillis / 1000L;
254 :
255 0 : long min = uptime / 60;
256 0 : if (min < 60) {
257 0 : return String.format("%2d min %2d sec", min, uptime - min * 60);
258 : }
259 :
260 0 : long hr = uptime / 3600;
261 0 : if (hr < 24) {
262 0 : min = (uptime - hr * 3600) / 60;
263 0 : return String.format("%2d hrs %2d min", hr, min);
264 : }
265 :
266 0 : long days = uptime / (24 * 3600);
267 0 : hr = (uptime - (days * 24 * 3600)) / 3600;
268 0 : return String.format("%4d days %2d hrs", days, hr);
269 : }
270 : }
|