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; 16 : 17 : import com.google.gerrit.metrics.proc.ThreadMXBeanFactory; 18 : import com.google.gerrit.metrics.proc.ThreadMXBeanInterface; 19 : import com.google.gerrit.server.CurrentUser; 20 : import com.google.gerrit.server.IdentifiedUser; 21 : import com.google.gerrit.server.RequestCleanup; 22 : import com.google.gerrit.server.util.RequestContext; 23 : import com.google.gerrit.server.util.ThreadLocalRequestContext; 24 : import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator; 25 : import com.google.gerrit.server.util.time.TimeUtil; 26 : import com.google.inject.Inject; 27 : import com.google.inject.Key; 28 : import com.google.inject.OutOfScopeException; 29 : import com.google.inject.Provider; 30 : import com.google.inject.Scope; 31 : import java.util.HashMap; 32 : import java.util.Map; 33 : 34 : /** Guice scopes for state during an SSH connection. */ 35 : public class SshScope { 36 17 : private static final Key<RequestCleanup> RC_KEY = Key.get(RequestCleanup.class); 37 17 : private static final ThreadMXBeanInterface threadMxBean = ThreadMXBeanFactory.create(); 38 : 39 : class Context implements RequestContext { 40 : 41 17 : private final RequestCleanup cleanup = new RequestCleanup(); 42 17 : private final Map<Key<?>, Object> map = new HashMap<>(); 43 : private final SshSession session; 44 : private final String commandLine; 45 : 46 : private final long created; 47 : private volatile long started; 48 : private volatile long finished; 49 : private volatile long startedTotalCpu; 50 : private volatile long finishedTotalCpu; 51 : private volatile long startedUserCpu; 52 : private volatile long finishedUserCpu; 53 : private volatile long startedMemory; 54 : private volatile long finishedMemory; 55 : 56 : private IdentifiedUser identifiedUser; 57 : 58 17 : private Context(SshSession s, String c, long at) { 59 17 : session = s; 60 17 : commandLine = c; 61 17 : created = started = finished = at; 62 17 : startedTotalCpu = threadMxBean.getCurrentThreadCpuTime(); 63 17 : startedUserCpu = threadMxBean.getCurrentThreadUserTime(); 64 17 : startedMemory = threadMxBean.getCurrentThreadAllocatedBytes(); 65 17 : map.put(RC_KEY, cleanup); 66 17 : } 67 : 68 : private Context(Context p, SshSession s, String c) { 69 6 : this(s, c, p.created); 70 6 : started = p.started; 71 6 : finished = p.finished; 72 6 : startedTotalCpu = p.startedTotalCpu; 73 6 : finishedTotalCpu = p.finishedTotalCpu; 74 6 : startedUserCpu = p.startedUserCpu; 75 6 : finishedUserCpu = p.finishedUserCpu; 76 6 : startedMemory = p.startedMemory; 77 6 : finishedMemory = p.finishedMemory; 78 6 : } 79 : 80 : void start() { 81 9 : started = TimeUtil.nowMs(); 82 9 : startedTotalCpu = threadMxBean.getCurrentThreadCpuTime(); 83 9 : startedUserCpu = threadMxBean.getCurrentThreadUserTime(); 84 9 : startedMemory = threadMxBean.getCurrentThreadAllocatedBytes(); 85 9 : } 86 : 87 : void finish() { 88 9 : finished = TimeUtil.nowMs(); 89 9 : finishedTotalCpu = threadMxBean.getCurrentThreadCpuTime(); 90 9 : finishedUserCpu = threadMxBean.getCurrentThreadUserTime(); 91 9 : finishedMemory = threadMxBean.getCurrentThreadAllocatedBytes(); 92 9 : } 93 : 94 : public long getCreated() { 95 17 : return created; 96 : } 97 : 98 : public long getWait() { 99 9 : return started - created; 100 : } 101 : 102 : public long getExec() { 103 9 : return finished - started; 104 : } 105 : 106 : public long getTotalCpu() { 107 9 : return (finishedTotalCpu - startedTotalCpu) / 1_000_000; 108 : } 109 : 110 : public long getUserCpu() { 111 9 : return (finishedUserCpu - startedUserCpu) / 1_000_000; 112 : } 113 : 114 : public long getAllocatedMemory() { 115 9 : return finishedMemory - startedMemory; 116 : } 117 : 118 : String getCommandLine() { 119 5 : return commandLine; 120 : } 121 : 122 : SshSession getSession() { 123 17 : return session; 124 : } 125 : 126 : @Override 127 : public CurrentUser getUser() { 128 10 : CurrentUser user = session.getUser(); 129 10 : if (user != null && user.isIdentifiedUser()) { 130 10 : if (identifiedUser == null) { 131 10 : identifiedUser = userFactory.create(user.getAccountId()); 132 10 : identifiedUser.setAccessPath(user.getAccessPath()); 133 : } 134 10 : return identifiedUser; 135 : } 136 1 : return user; 137 : } 138 : 139 : synchronized <T> T get(Key<T> key, Provider<T> creator) { 140 : @SuppressWarnings("unchecked") 141 17 : T t = (T) map.get(key); 142 17 : if (t == null) { 143 17 : t = creator.get(); 144 17 : map.put(key, t); 145 : } 146 17 : return t; 147 : } 148 : 149 : synchronized Context subContext(SshSession newSession, String newCommandLine) { 150 6 : Context ctx = new Context(this, newSession, newCommandLine); 151 6 : ctx.cleanup.add(cleanup); 152 6 : return ctx; 153 : } 154 : } 155 : 156 17 : static class ContextProvider implements Provider<Context> { 157 : @Override 158 : public Context get() { 159 17 : return requireContext(); 160 : } 161 : } 162 : 163 17 : public static class SshSessionProvider implements Provider<SshSession> { 164 : @Override 165 : public SshSession get() { 166 17 : return requireContext().getSession(); 167 : } 168 : } 169 : 170 : static class Propagator extends ThreadLocalRequestScopePropagator<Context> { 171 : private final SshScope sshScope; 172 : 173 : @Inject 174 : Propagator(SshScope sshScope, ThreadLocalRequestContext local) { 175 2 : super(REQUEST, current, local); 176 2 : this.sshScope = sshScope; 177 2 : } 178 : 179 : @Override 180 : protected Context continuingContext(Context ctx) { 181 : // The cleanup is not chained, since the RequestScopePropagator executors 182 : // the Context's cleanup when finished executing. 183 2 : return sshScope.newContinuingContext(ctx); 184 : } 185 : } 186 : 187 17 : private static final ThreadLocal<Context> current = new ThreadLocal<>(); 188 : 189 : private static Context requireContext() { 190 17 : final Context ctx = current.get(); 191 17 : if (ctx == null) { 192 0 : throw new OutOfScopeException("Not in command/request"); 193 : } 194 17 : return ctx; 195 : } 196 : 197 : private final ThreadLocalRequestContext local; 198 : private final IdentifiedUser.RequestFactory userFactory; 199 : 200 : @Inject 201 17 : SshScope(ThreadLocalRequestContext local, IdentifiedUser.RequestFactory userFactory) { 202 17 : this.local = local; 203 17 : this.userFactory = userFactory; 204 17 : } 205 : 206 : Context newContext(SshSession s, String cmd) { 207 17 : return new Context(s, cmd, TimeUtil.nowMs()); 208 : } 209 : 210 : private Context newContinuingContext(Context ctx) { 211 2 : return new Context(ctx, ctx.getSession(), ctx.getCommandLine()); 212 : } 213 : 214 : Context set(Context ctx) { 215 17 : Context old = current.get(); 216 17 : current.set(ctx); 217 17 : local.setContext(ctx); 218 17 : return old; 219 : } 220 : 221 : /** Returns exactly one instance per command executed. */ 222 17 : public static final Scope REQUEST = 223 17 : new Scope() { 224 : @Override 225 : public <T> Provider<T> scope(Key<T> key, Provider<T> creator) { 226 17 : return new Provider<>() { 227 : @Override 228 : public T get() { 229 17 : return requireContext().get(key, creator); 230 : } 231 : 232 : @Override 233 : public String toString() { 234 0 : return String.format("%s[%s]", creator, REQUEST); 235 : } 236 : }; 237 : } 238 : 239 : @Override 240 : public String toString() { 241 0 : return "SshScopes.REQUEST"; 242 : } 243 : }; 244 : }