Skip to content

Commit 1a48a5e

Browse files
committed
Add time-limited logs to Epp Login flow to test increased latency
1 parent 4738b97 commit 1a48a5e

File tree

4 files changed

+67
-2
lines changed

4 files changed

+67
-2
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
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;
31+
import java.time.Duration;
3032

3133
/** Handle an EPP request and response. */
3234
public class EppRequestHandler {
@@ -55,7 +57,10 @@ public void executeEpp(
5557
eppController.handleEppCommand(
5658
sessionMetadata, credentials, eppRequestSource, isDryRun, isSuperuser, inputXmlBytes);
5759
response.setContentType(APPLICATION_EPP_XML);
60+
final StopwatchLogger stopwatch = new StopwatchLogger(logger, Duration.ofMillis(400));
5861
byte[] eppResponseXmlBytes = marshalWithLenientRetry(eppOutput);
62+
stopwatch.tick("Completed EPP output marshaling.");
63+
5964
response.setPayload(new String(eppResponseXmlBytes, UTF_8));
6065
logger.atInfo().log(
6166
"EPP response: %s", prettyPrint(EppXmlSanitizer.sanitizeEppXml(eppResponseXmlBytes)));

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@
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;
35+
import java.time.Duration;
3436
import java.util.Optional;
3537

3638
/** Run a flow, either transactionally or not, with logging and retrying as needed. */
@@ -77,23 +79,32 @@ public EppOutput run(final EppMetric.Builder eppMetricBuilder) throws EppExcepti
7779
flowReporter.recordToLogs();
7880
}
7981
eppMetricBuilder.setCommandNameFromFlow(flowClass.getSimpleName());
82+
final StopwatchLogger stopwatch = new StopwatchLogger(logger, Duration.ofMillis(400));
83+
8084
// We may already be in a transaction, e.g., when invoked by DeleteExpiredDomainsAction.
8185
if (!isTransactional || jpaTransactionManager.inTransaction()) {
86+
stopwatch.tick("We're in transaction, running the flow now.");
8287
return EppOutput.create(flowProvider.get().run());
8388
}
89+
90+
stopwatch.tick("We're not in transaction, calling transact.");
8491
try {
8592
return jpaTransactionManager.transact(
8693
isolationLevelOverride.orElse(null),
8794
() -> {
8895
try {
96+
stopwatch.tick("Running the flow in transaction.");
8997
EppOutput output = EppOutput.create(flowProvider.get().run());
98+
stopwatch.tick("Completed the flow in transaction.");
9099
if (isDryRun) {
91100
throw new DryRunException(output);
92101
}
93102
if (flowClass.equals(LoginFlow.class)) {
94103
// In LoginFlow, registrarId isn't known until after the flow executes, so save
95104
// it then.
105+
stopwatch.tick("Login flow started setting registrar id.");
96106
eppMetricBuilder.setRegistrarId(sessionMetadata.getRegistrarId());
107+
stopwatch.tick("Login flow finished setting registrar id.");
97108
}
98109
return output;
99110
} catch (EppException e) {

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@
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;
52+
import java.time.Duration;
5153
import java.util.Optional;
5254
import java.util.Set;
5355

@@ -97,19 +99,25 @@ public final EppResponse run() throws EppException {
9799

98100
/** Run the flow without bothering to log errors. The {@link #run} method will do that for us. */
99101
private EppResponse runWithoutLogging() throws EppException {
102+
final StopwatchLogger stopwatch = new StopwatchLogger(logger, Duration.ofMillis(400));
100103
extensionManager.validate(); // There are no legal extensions for this flow.
104+
stopwatch.tick("LoginFlow extension validate");
101105
Login login = (Login) eppInput.getCommandWrapper().getCommand();
106+
stopwatch.tick("LoginFlow getCommand");
102107
if (!registrarId.isEmpty()) {
103108
throw new AlreadyLoggedInException();
104109
}
105110
Options options = login.getOptions();
111+
stopwatch.tick("LoginFlow getOptions");
106112
if (!ProtocolDefinition.LANGUAGE.equals(options.getLanguage())) {
107113
throw new UnsupportedLanguageException();
108114
}
109115
Services services = login.getServices();
116+
stopwatch.tick("LoginFlow getServices");
110117
Set<String> unsupportedObjectServices = difference(
111118
nullToEmpty(services.getObjectServices()),
112119
ProtocolDefinition.SUPPORTED_OBJECT_SERVICES);
120+
stopwatch.tick("LoginFlow difference unsupportedObjectServices");
113121
if (!unsupportedObjectServices.isEmpty()) {
114122
throw new UnimplementedObjectServiceException();
115123
}
@@ -121,11 +129,12 @@ private EppResponse runWithoutLogging() throws EppException {
121129
}
122130
serviceExtensionUrisBuilder.add(uri);
123131
}
132+
stopwatch.tick("LoginFlow serviceExtensionUrisBuilder");
124133
Optional<Registrar> registrar = Registrar.loadByRegistrarIdCached(login.getClientId());
125134
if (registrar.isEmpty()) {
126135
throw new BadRegistrarIdException(login.getClientId());
127136
}
128-
137+
stopwatch.tick("LoginFlow loadByRegistrarIdCached");
129138
// AuthenticationErrorExceptions will propagate up through here.
130139
try {
131140
credentials.validate(registrar.get(), login.getPassword());
@@ -137,6 +146,7 @@ private EppResponse runWithoutLogging() throws EppException {
137146
throw e;
138147
}
139148
}
149+
stopwatch.tick("LoginFlow credentials.validate");
140150
if (!registrar.get().isLive()) {
141151
throw new RegistrarAccountNotActiveException();
142152
}
@@ -145,17 +155,24 @@ private EppResponse runWithoutLogging() throws EppException {
145155
String newPassword = login.getNewPassword().get();
146156
// Load fresh from database (bypassing the cache) to ensure we don't save stale data.
147157
Optional<Registrar> freshRegistrar = Registrar.loadByRegistrarId(login.getClientId());
158+
stopwatch.tick("LoginFlow reload freshRegistrar");
148159
if (freshRegistrar.isEmpty()) {
149160
throw new BadRegistrarIdException(login.getClientId());
150161
}
151162
tm().put(freshRegistrar.get().asBuilder().setPassword(newPassword).build());
163+
stopwatch.tick("LoginFlow updated password");
152164
}
153165

154166
// We are in!
155167
sessionMetadata.resetFailedLoginAttempts();
168+
stopwatch.tick("LoginFlow resetFailedLoginAttempts");
156169
sessionMetadata.setRegistrarId(login.getClientId());
170+
stopwatch.tick("LoginFlow setRegistrarId");
157171
sessionMetadata.setServiceExtensionUris(serviceExtensionUrisBuilder.build());
158-
return responseBuilder.setIsLoginResponse().build();
172+
stopwatch.tick("LoginFlow setServiceExtensionUris");
173+
EppResponse eppResponse = responseBuilder.setIsLoginResponse().build();
174+
stopwatch.tick("LoginFlow eppResponse build()");
175+
return eppResponse;
159176
}
160177

161178
/** Registrar with this ID could not be found. */
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
11+
private final FluentLogger logger;
12+
private final long thresholdNanos;
13+
private long lastTickNanos;
14+
15+
public StopwatchLogger(FluentLogger logger, Duration threshold) {
16+
this.logger = logger;
17+
this.thresholdNanos = threshold.toNanos();
18+
this.lastTickNanos = System.nanoTime();
19+
}
20+
21+
public void tick(String message) {
22+
long currentNanos = System.nanoTime();
23+
long elapsedNanos = currentNanos - lastTickNanos;
24+
25+
// Only log if the elapsed time is over the threshold.
26+
if (elapsedNanos > thresholdNanos) {
27+
logger.atInfo().log("%s (took %d ms)", message, Duration.ofNanos(elapsedNanos).toMillis());
28+
}
29+
30+
this.lastTickNanos = currentNanos;
31+
}
32+
}

0 commit comments

Comments
 (0)