Skip to content

Commit 90c3826

Browse files
committed
Add time-limited logs to Epp Login flow to test increased latency
1 parent bdaab9d commit 90c3826

File tree

5 files changed

+68
-3
lines changed

5 files changed

+68
-3
lines changed

core/src/main/java/google/registry/flows/EppRequestHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import google.registry.model.eppoutput.EppOutput;
2727
import google.registry.request.Response;
2828
import google.registry.util.ProxyHttpHeaders;
29+
import google.registry.util.StopwatchLogger;
2930
import jakarta.inject.Inject;
3031

3132
/** Handle an EPP request and response. */
@@ -55,7 +56,10 @@ public void executeEpp(
5556
eppController.handleEppCommand(
5657
sessionMetadata, credentials, eppRequestSource, isDryRun, isSuperuser, inputXmlBytes);
5758
response.setContentType(APPLICATION_EPP_XML);
59+
final StopwatchLogger stopwatch = new StopwatchLogger();
5860
byte[] eppResponseXmlBytes = marshalWithLenientRetry(eppOutput);
61+
stopwatch.tick("Completed EPP output marshaling.");
62+
5963
response.setPayload(new String(eppResponseXmlBytes, UTF_8));
6064
logger.atInfo().log(
6165
"EPP response: %s", prettyPrint(EppXmlSanitizer.sanitizeEppXml(eppResponseXmlBytes)));

core/src/main/java/google/registry/flows/FlowRunner.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import google.registry.monitoring.whitebox.EppMetric;
3030
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
3131
import google.registry.persistence.transaction.JpaTransactionManager;
32+
import google.registry.util.StopwatchLogger;
3233
import jakarta.inject.Inject;
3334
import jakarta.inject.Provider;
3435
import java.util.Optional;
@@ -77,23 +78,32 @@ public EppOutput run(final EppMetric.Builder eppMetricBuilder) throws EppExcepti
7778
flowReporter.recordToLogs();
7879
}
7980
eppMetricBuilder.setCommandNameFromFlow(flowClass.getSimpleName());
81+
final StopwatchLogger stopwatch = new StopwatchLogger();
82+
8083
// We may already be in a transaction, e.g., when invoked by DeleteExpiredDomainsAction.
8184
if (!isTransactional || jpaTransactionManager.inTransaction()) {
85+
stopwatch.tick("We're in transaction, running the flow now.");
8286
return EppOutput.create(flowProvider.get().run());
8387
}
88+
89+
stopwatch.tick("We're not in transaction, calling transact.");
8490
try {
8591
return jpaTransactionManager.transact(
8692
isolationLevelOverride.orElse(null),
8793
() -> {
8894
try {
95+
stopwatch.tick("Running the flow in transaction.");
8996
EppOutput output = EppOutput.create(flowProvider.get().run());
97+
stopwatch.tick("Completed the flow in transaction.");
9098
if (isDryRun) {
9199
throw new DryRunException(output);
92100
}
93101
if (flowClass.equals(LoginFlow.class)) {
94102
// In LoginFlow, registrarId isn't known until after the flow executes, so save
95103
// it then.
104+
stopwatch.tick("Login flow started setting registrar id.");
96105
eppMetricBuilder.setRegistrarId(sessionMetadata.getRegistrarId());
106+
stopwatch.tick("Login flow finished setting registrar id.");
97107
}
98108
return output;
99109
} catch (EppException e) {

core/src/main/java/google/registry/flows/session/LoginFlow.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import google.registry.model.eppinput.EppInput.Services;
4848
import google.registry.model.eppoutput.EppResponse;
4949
import google.registry.model.registrar.Registrar;
50+
import google.registry.util.StopwatchLogger;
5051
import jakarta.inject.Inject;
5152
import java.util.Optional;
5253
import java.util.Set;
@@ -97,19 +98,25 @@ public final EppResponse run() throws EppException {
9798

9899
/** Run the flow without bothering to log errors. The {@link #run} method will do that for us. */
99100
private EppResponse runWithoutLogging() throws EppException {
101+
final StopwatchLogger stopwatch = new StopwatchLogger();
100102
extensionManager.validate(); // There are no legal extensions for this flow.
103+
stopwatch.tick("LoginFlow extension validate");
101104
Login login = (Login) eppInput.getCommandWrapper().getCommand();
105+
stopwatch.tick("LoginFlow getCommand");
102106
if (!registrarId.isEmpty()) {
103107
throw new AlreadyLoggedInException();
104108
}
105109
Options options = login.getOptions();
110+
stopwatch.tick("LoginFlow getOptions");
106111
if (!ProtocolDefinition.LANGUAGE.equals(options.getLanguage())) {
107112
throw new UnsupportedLanguageException();
108113
}
109114
Services services = login.getServices();
115+
stopwatch.tick("LoginFlow getServices");
110116
Set<String> unsupportedObjectServices = difference(
111117
nullToEmpty(services.getObjectServices()),
112118
ProtocolDefinition.SUPPORTED_OBJECT_SERVICES);
119+
stopwatch.tick("LoginFlow difference unsupportedObjectServices");
113120
if (!unsupportedObjectServices.isEmpty()) {
114121
throw new UnimplementedObjectServiceException();
115122
}
@@ -121,11 +128,12 @@ private EppResponse runWithoutLogging() throws EppException {
121128
}
122129
serviceExtensionUrisBuilder.add(uri);
123130
}
131+
stopwatch.tick("LoginFlow serviceExtensionUrisBuilder");
124132
Optional<Registrar> registrar = Registrar.loadByRegistrarIdCached(login.getClientId());
125133
if (registrar.isEmpty()) {
126134
throw new BadRegistrarIdException(login.getClientId());
127135
}
128-
136+
stopwatch.tick("LoginFlow loadByRegistrarIdCached");
129137
// AuthenticationErrorExceptions will propagate up through here.
130138
try {
131139
credentials.validate(registrar.get(), login.getPassword());
@@ -137,6 +145,7 @@ private EppResponse runWithoutLogging() throws EppException {
137145
throw e;
138146
}
139147
}
148+
stopwatch.tick("LoginFlow credentials.validate");
140149
if (!registrar.get().isLive()) {
141150
throw new RegistrarAccountNotActiveException();
142151
}
@@ -145,17 +154,24 @@ private EppResponse runWithoutLogging() throws EppException {
145154
String newPassword = login.getNewPassword().get();
146155
// Load fresh from database (bypassing the cache) to ensure we don't save stale data.
147156
Optional<Registrar> freshRegistrar = Registrar.loadByRegistrarId(login.getClientId());
157+
stopwatch.tick("LoginFlow reload freshRegistrar");
148158
if (freshRegistrar.isEmpty()) {
149159
throw new BadRegistrarIdException(login.getClientId());
150160
}
151161
tm().put(freshRegistrar.get().asBuilder().setPassword(newPassword).build());
162+
stopwatch.tick("LoginFlow updated password");
152163
}
153164

154165
// We are in!
155166
sessionMetadata.resetFailedLoginAttempts();
167+
stopwatch.tick("LoginFlow resetFailedLoginAttempts");
156168
sessionMetadata.setRegistrarId(login.getClientId());
169+
stopwatch.tick("LoginFlow setRegistrarId");
157170
sessionMetadata.setServiceExtensionUris(serviceExtensionUrisBuilder.build());
158-
return responseBuilder.setIsLoginResponse().build();
171+
stopwatch.tick("LoginFlow setServiceExtensionUris");
172+
EppResponse eppResponse = responseBuilder.setIsLoginResponse().build();
173+
stopwatch.tick("LoginFlow eppResponse build()");
174+
return eppResponse;
159175
}
160176

161177
/** Registrar with this ID could not be found. */

core/src/main/java/google/registry/request/auth/OidcTokenAuthenticationMechanism.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import google.registry.request.auth.AuthModule.RegularOidc;
3131
import google.registry.request.auth.AuthSettings.AuthLevel;
3232
import google.registry.util.RegistryEnvironment;
33+
import google.registry.util.StopwatchLogger;
3334
import jakarta.inject.Inject;
3435
import jakarta.servlet.http.HttpServletRequest;
3536
import java.util.Optional;
@@ -77,7 +78,9 @@ public AuthResult authenticate(HttpServletRequest request) {
7778
logger.atWarning().log("Using AuthResult %s for testing.", authResultForTesting);
7879
return authResultForTesting;
7980
}
81+
final StopwatchLogger stopwatch = new StopwatchLogger();
8082
String rawIdToken = tokenExtractor.extract(request);
83+
stopwatch.tick("OidcTokenAuthenticationMechanism tokenExtractor extracted");
8184
if (rawIdToken == null) {
8285
return AuthResult.NOT_AUTHENTICATED;
8386
}
@@ -99,7 +102,7 @@ public AuthResult authenticate(HttpServletRequest request) {
99102
? "Raw token redacted in prod"
100103
: rawIdToken);
101104
}
102-
105+
stopwatch.tick("OidcTokenAuthenticationMechanism token verified");
103106
if (token == null) {
104107
return AuthResult.NOT_AUTHENTICATED;
105108
}
@@ -111,6 +114,7 @@ public AuthResult authenticate(HttpServletRequest request) {
111114
}
112115
Optional<User> maybeUser =
113116
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(User.class, email)));
117+
stopwatch.tick("OidcTokenAuthenticationMechanism maybeUser loaded");
114118
if (maybeUser.isPresent()) {
115119
return AuthResult.createUser(maybeUser.get());
116120
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package google.registry.util;
2+
3+
import com.google.common.flogger.FluentLogger;
4+
import java.time.Duration;
5+
6+
/**
7+
* A helper class to log only if the time elapsed between calls is more than a specified threshold.
8+
*/
9+
public final class StopwatchLogger {
10+
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
11+
Duration threshold = Duration.ofMillis(400);
12+
private final long thresholdNanos;
13+
private long lastTickNanos;
14+
15+
public StopwatchLogger() {
16+
this.thresholdNanos = threshold.toNanos();
17+
this.lastTickNanos = System.nanoTime();
18+
}
19+
20+
public void tick(String message) {
21+
long currentNanos = System.nanoTime();
22+
long elapsedNanos = currentNanos - lastTickNanos;
23+
24+
// Only log if the elapsed time is over the threshold.
25+
if (elapsedNanos > thresholdNanos) {
26+
logger.atInfo().log("%s (took %d ms)", message, Duration.ofNanos(elapsedNanos).toMillis());
27+
}
28+
29+
this.lastTickNanos = currentNanos;
30+
}
31+
}

0 commit comments

Comments
 (0)