Add metrics for registrar console requests

Cardinality of this metric:

clientId: there are currently 650 (on sandbox, because of OTE), and 200 on production.
explicitClientId: 2
roles: 2 now, might be 3 soon if we add vendors
status: 2

So we're talking about a cardinality of 2,000-8,000. Less when you consider that registrars only seldom actually need to access the console (certainly not daily or even weekly).

Compare with, e.g., the /epp/processing_time from the above EppMetrics.java which has:
Epp commands: 26 (manual counting)
client IDs: 200 on prod
status: the actual status CODE of the command. Can have many values, but looking at the past few weeks' metrics I counted 20
Note that not every command results in every status. Looking a few weeks back we can see around 80-100 (commands+status) combination.
buckets: 16

so that's over 250,000-1,000,000 cardinality, on a very high-volume metric.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=218699280
This commit is contained in:
guyben 2018-10-25 09:34:21 -07:00 committed by jianglai
parent f6d9b46622
commit 97aa98eb35
11 changed files with 223 additions and 25 deletions

View file

@ -26,11 +26,13 @@ java_library(
"//java/google/registry/util",
"//third_party/objectify:objectify-v4_1",
"@com_google_appengine_api_1_0_sdk",
"@com_google_auto_value",
"@com_google_code_findbugs_jsr305",
"@com_google_dagger",
"@com_google_flogger",
"@com_google_flogger_system_backend",
"@com_google_guava",
"@com_google_monitoring_client_metrics",
"@com_google_re2j",
"@io_bazel_rules_closure//closure/templates",
"@javax_inject",

View file

@ -75,6 +75,7 @@ public final class ConsoleUiAction implements Runnable {
@Inject HttpServletRequest req;
@Inject Response response;
@Inject RegistrarConsoleMetrics registrarConsoleMetrics;
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
@Inject UserService userService;
@Inject XsrfTokenManager xsrfTokenManager;
@ -134,15 +135,19 @@ public final class ConsoleUiAction implements Runnable {
data.put("username", user.getNickname());
data.put("logoutUrl", userService.createLogoutURL(PATH));
data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmail()));
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllClientIdWithRoles();
ImmutableSetMultimap<Role, String> roleMapInverse = roleMap.inverse();
// TODO(guyben):just return all the clientIDs in a single list, and add an "isAdmin" or "roles"
// item
data.put("ownerClientIds", roleMapInverse.get(OWNER));
data.put(
"adminClientIds", Sets.difference(roleMapInverse.get(ADMIN), roleMapInverse.get(OWNER)));
// We set the initual value to the value that will show if guessClientId throws.
String clientId = "<null>";
try {
String clientId = paramClientId.orElse(registrarAccessor.guessClientId());
clientId = paramClientId.orElse(registrarAccessor.guessClientId());
data.put("clientId", clientId);
ImmutableSetMultimap<Role, String> roleMap =
registrarAccessor.getAllClientIdWithRoles().inverse();
data.put("ownerClientIds", roleMap.get(OWNER));
data.put("adminClientIds", Sets.difference(roleMap.get(ADMIN), roleMap.get(OWNER)));
// We want to load the registrar even if we won't use it later (even if we remove the
// requireFeeExtension) - to make sure the user indeed has access to the guessed registrar.
//
@ -162,7 +167,13 @@ public final class ConsoleUiAction implements Runnable {
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(data)
.render());
registrarConsoleMetrics.registerConsoleRequest(
clientId, paramClientId.isPresent(), roleMap.get(clientId), "FORBIDDEN");
return;
} catch (Exception e) {
registrarConsoleMetrics.registerConsoleRequest(
clientId, paramClientId.isPresent(), roleMap.get(clientId), "UNEXPECTED ERROR");
throw e;
}
String payload = TOFU_SUPPLIER.get()
@ -171,5 +182,7 @@ public final class ConsoleUiAction implements Runnable {
.setData(data)
.render();
response.setPayload(payload);
registrarConsoleMetrics.registerConsoleRequest(
clientId, paramClientId.isPresent(), roleMap.get(clientId), "SUCCESS");
}
}

View file

@ -0,0 +1,69 @@
// Copyright 2018 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.ui.server.registrar;
import com.google.common.collect.ImmutableSet;
import com.google.monitoring.metrics.IncrementableMetric;
import com.google.monitoring.metrics.LabelDescriptor;
import com.google.monitoring.metrics.MetricRegistryImpl;
import google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role;
import javax.inject.Inject;
final class RegistrarConsoleMetrics {
private static final ImmutableSet<LabelDescriptor> CONSOLE_LABEL_DESCRIPTORS =
ImmutableSet.of(
LabelDescriptor.create("clientId", "target registrar client ID"),
LabelDescriptor.create("explicitClientId", "whether the client ID is set explicitly"),
LabelDescriptor.create("role", "Role[s] of the user making the request"),
LabelDescriptor.create("status", "whether the request is successful"));
static final IncrementableMetric consoleRequestMetric =
MetricRegistryImpl.getDefault()
.newIncrementableMetric(
"/console/registrar/console_requests",
"Count of /registrar requests",
"count",
CONSOLE_LABEL_DESCRIPTORS);
private static final ImmutableSet<LabelDescriptor> SETTINGS_LABEL_DESCRIPTORS =
ImmutableSet.of(
LabelDescriptor.create("clientId", "target registrar client ID"),
LabelDescriptor.create("action", "action performed"),
LabelDescriptor.create("role", "Role[s] of the user making the request"),
LabelDescriptor.create("status", "whether the request is successful"));
static final IncrementableMetric settingsRequestMetric =
MetricRegistryImpl.getDefault()
.newIncrementableMetric(
"/console/registrar/setting_requests",
"Count of /registrar-settings requests",
"count",
SETTINGS_LABEL_DESCRIPTORS);
@Inject
public RegistrarConsoleMetrics() {}
void registerConsoleRequest(
String clientId, boolean explicitClientId, ImmutableSet<Role> roles, String status) {
consoleRequestMetric.increment(
clientId, String.valueOf(explicitClientId), String.valueOf(roles), status);
}
void registerSettingsRequest(
String clientId, String action, ImmutableSet<Role> roles, String status) {
settingsRequestMetric.increment(clientId, action, String.valueOf(roles), status);
}
}

View file

@ -21,6 +21,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.security.JsonResponseHelper.Status.ERROR;
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
import com.google.auto.value.AutoValue;
import com.google.common.base.Ascii;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
@ -80,6 +81,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
@Inject JsonActionRunner jsonActionRunner;
@Inject AppEngineServiceUtils appEngineServiceUtils;
@Inject RegistrarConsoleMetrics registrarConsoleMetrics;
@Inject SendEmailUtils sendEmailUtils;
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
@Inject AuthResult authResult;
@ -114,34 +116,55 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
@SuppressWarnings("unchecked")
Map<String, ?> args = (Map<String, Object>)
Optional.<Object>ofNullable(input.get(ARGS_PARAM)).orElse(ImmutableMap.of());
logger.atInfo().log("Received request '%s' on registrar '%s' with args %s", op, clientId, args);
String status = "SUCCESS";
try {
switch (op) {
case "update":
return update(args, clientId);
return update(args, clientId).toJsonResponse();
case "read":
return read(clientId);
return read(clientId).toJsonResponse();
default:
return JsonResponseHelper.create(ERROR, "Unknown or unsupported operation: " + op);
throw new IllegalArgumentException("Unknown or unsupported operation: " + op);
}
} catch (FormFieldException e) {
logger.atWarning().withCause(e).log(
"Failed to perform operation '%s' on registrar '%s' for args %s", op, clientId, args);
return JsonResponseHelper.createFormFieldError(e.getMessage(), e.getFieldName());
} catch (Throwable e) {
logger.atWarning().withCause(e).log(
"Failed to perform operation '%s' on registrar '%s' for args %s", op, clientId, args);
status = "ERROR: " + e.getClass().getSimpleName();
if (e instanceof FormFieldException) {
FormFieldException formFieldException = (FormFieldException) e;
return JsonResponseHelper.createFormFieldError(
formFieldException.getMessage(), formFieldException.getFieldName());
}
return JsonResponseHelper.create(
ERROR, Optional.ofNullable(e.getMessage()).orElse("Unspecified error"));
} finally {
registrarConsoleMetrics.registerSettingsRequest(
clientId, op, registrarAccessor.getAllClientIdWithRoles().get(clientId), status);
}
}
Map<String, Object> read(String clientId) {
return JsonResponseHelper.create(
SUCCESS, "Success", registrarAccessor.getRegistrar(clientId).toJsonMap());
@AutoValue
abstract static class RegistrarResult {
abstract String message();
abstract Registrar registrar();
Map<String, Object> toJsonResponse() {
return JsonResponseHelper.create(SUCCESS, message(), registrar().toJsonMap());
}
static RegistrarResult create(String message, Registrar registrar) {
return new AutoValue_RegistrarSettingsAction_RegistrarResult(message, registrar);
}
}
Map<String, Object> update(final Map<String, ?> args, String clientId) {
private RegistrarResult read(String clientId) {
return RegistrarResult.create("Success", registrarAccessor.getRegistrar(clientId));
}
private RegistrarResult update(final Map<String, ?> args, String clientId) {
return ofy()
.transact(
() -> {
@ -196,8 +219,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
// Email and return update.
sendExternalUpdatesIfNecessary(
registrar, contacts, updatedRegistrar, updatedContacts);
return JsonResponseHelper.create(
SUCCESS, "Saved " + clientId, updatedRegistrar.toJsonMap());
return RegistrarResult.create("Saved " + clientId, updatedRegistrar);
});
}