Add metrics for the command used in the registry CLI tool

Puts the metric in <project>/tools/commands_called

It counts the use of the tool, with the following labels:
- environment
- tool (nomulus/gtech)
- command called (class name)
- success true/false
- from the shell true/false

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=212879670
This commit is contained in:
guyben 2018-09-13 14:31:39 -07:00 committed by Ben McIlwain
parent e72f5c09a2
commit 414b2e4db1
6 changed files with 145 additions and 4 deletions

View file

@ -70,6 +70,7 @@ java_library(
"@com_google_api_client", "@com_google_api_client",
"@com_google_apis_google_api_services_bigquery", "@com_google_apis_google_api_services_bigquery",
"@com_google_apis_google_api_services_dns", "@com_google_apis_google_api_services_dns",
"@com_google_apis_google_api_services_monitoring",
"@com_google_appengine_api_1_0_sdk", "@com_google_appengine_api_1_0_sdk",
"@com_google_appengine_remote_api", "@com_google_appengine_remote_api",
"@com_google_appengine_remote_api//:link", "@com_google_appengine_remote_api//:link",
@ -81,6 +82,8 @@ java_library(
"@com_google_guava", "@com_google_guava",
"@com_google_http_client", "@com_google_http_client",
"@com_google_http_client_jackson2", "@com_google_http_client_jackson2",
"@com_google_monitoring_client_metrics",
"@com_google_monitoring_client_stackdriver",
"@com_google_oauth_client", "@com_google_oauth_client",
"@com_google_oauth_client_java6", "@com_google_oauth_client_java6",
"@com_google_oauth_client_jetty", "@com_google_oauth_client_jetty",

View file

@ -0,0 +1,53 @@
// 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.tools;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.services.monitoring.v3.Monitoring;
import com.google.api.services.monitoring.v3.model.MonitoredResource;
import com.google.monitoring.metrics.MetricWriter;
import com.google.monitoring.metrics.stackdriver.StackdriverWriter;
import dagger.Module;
import dagger.Provides;
import google.registry.config.CredentialModule.DefaultCredential;
import google.registry.config.RegistryConfig.Config;
/** Dagger module for metrics on the client tool. */
@Module
public final class MetricToolModule {
@Provides
static Monitoring provideMonitoring(
@DefaultCredential GoogleCredential credential, @Config("projectId") String projectId) {
return new Monitoring.Builder(
credential.getTransport(), credential.getJsonFactory(), credential)
.setApplicationName(projectId)
.build();
}
@Provides
static MetricWriter provideMetricWriter(
Monitoring monitoringClient,
@Config("projectId") String projectId,
@Config("stackdriverMaxQps") int maxQps,
@Config("stackdriverMaxPointsPerRequest") int maxPointsPerRequest) {
return new StackdriverWriter(
monitoringClient,
projectId,
new MonitoredResource().setType("global"),
maxQps,
maxPointsPerRequest);
}
}

View file

@ -26,10 +26,19 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException; import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate; import com.beust.jcommander.ParametersDelegate;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.monitoring.metrics.IncrementableMetric;
import com.google.monitoring.metrics.LabelDescriptor;
import com.google.monitoring.metrics.Metric;
import com.google.monitoring.metrics.MetricPoint;
import com.google.monitoring.metrics.MetricRegistryImpl;
import com.google.monitoring.metrics.MetricWriter;
import google.registry.model.ofy.ObjectifyService; import google.registry.model.ofy.ObjectifyService;
import google.registry.tools.params.ParameterFactory; import google.registry.tools.params.ParameterFactory;
import java.io.IOException;
import java.security.Security; import java.security.Security;
import java.util.Map; import java.util.Map;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
@ -38,6 +47,11 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
@Parameters(separators = " =", commandDescription = "Command-line interface to the registry") @Parameters(separators = " =", commandDescription = "Command-line interface to the registry")
final class RegistryCli implements AutoCloseable, CommandRunner { final class RegistryCli implements AutoCloseable, CommandRunner {
// The environment parameter is parsed twice: once here, and once with {@link
// RegistryToolEnvironment#parseFromArgs} in the {@link RegistryTool#main} or {@link
// GtechTool#main} functions.
//
// The flag names must be in sync between the two, and also - this is ugly and we should feel bad.
@Parameter( @Parameter(
names = {"-e", "--environment"}, names = {"-e", "--environment"},
description = "Sets the default environment to run the command.") description = "Sets the default environment to run the command.")
@ -48,6 +62,9 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
description = "Returns all command names.") description = "Returns all command names.")
private boolean showAllCommands; private boolean showAllCommands;
@VisibleForTesting
boolean uploadMetrics = true;
// Do not make this final - compile-time constant inlining may interfere with JCommander. // Do not make this final - compile-time constant inlining may interfere with JCommander.
@ParametersDelegate @ParametersDelegate
private AppEngineConnectionFlags appEngineConnectionFlags = private AppEngineConnectionFlags appEngineConnectionFlags =
@ -68,6 +85,24 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
// "shell". // "shell".
private boolean isFirstUse = true; private boolean isFirstUse = true;
private static final ImmutableSet<LabelDescriptor> LABEL_DESCRIPTORS_FOR_COMMANDS =
ImmutableSet.of(
LabelDescriptor.create("program", "The program used - e.g. nomulus or gtech_tool"),
LabelDescriptor.create("environment", "The environment used - e.g. sandbox"),
LabelDescriptor.create("command", "The command used"),
LabelDescriptor.create("success", "Whether the command succeeded"),
LabelDescriptor.create("shell", "Whether the command was called from the nomulus shell"));
private static final IncrementableMetric commandsCalledCount =
MetricRegistryImpl.getDefault()
.newIncrementableMetric(
"/tools/commands_called",
"Count of tool commands called",
"count",
LABEL_DESCRIPTORS_FOR_COMMANDS);
private MetricWriter metricWriter = null;
Map<String, ? extends Class<? extends Command>> commands; Map<String, ? extends Class<? extends Command>> commands;
String programName; String programName;
@ -89,9 +124,13 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
// http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ104 // http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ104
@Override @Override
public void run(String[] args) throws Exception { public void run(String[] args) throws Exception {
boolean inShell = !isFirstUse;
isFirstUse = false;
// Create the JCommander instance. // Create the JCommander instance.
JCommander jcommander = new JCommander(this); // If we're in the shell, we don't want to update the RegistryCli's parameters (so we give a
// dummy object to update)
JCommander jcommander = new JCommander(inShell ? new Object() : this);
jcommander.addConverterFactory(new ParameterFactory()); jcommander.addConverterFactory(new ParameterFactory());
jcommander.setProgramName(programName); jcommander.setProgramName(programName);
@ -110,8 +149,8 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
// Create the "help" and "shell" commands (these are special in that they don't have a default // Create the "help" and "shell" commands (these are special in that they don't have a default
// constructor). // constructor).
jcommander.addCommand("help", new HelpCommand(jcommander)); jcommander.addCommand("help", new HelpCommand(jcommander));
if (isFirstUse) { if (!inShell) {
isFirstUse = false; // If we aren't inside a shell, then we want to add the shell command.
ShellCommand shellCommand = new ShellCommand(this); ShellCommand shellCommand = new ShellCommand(this);
// We have to build the completions based on the jcommander *before* we add the "shell" // We have to build the completions based on the jcommander *before* we add the "shell"
// command - to avoid completion for the "shell" command itself. // command - to avoid completion for the "shell" command itself.
@ -153,17 +192,31 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
jcommander.getCommands().get(jcommander.getParsedCommand()).getObjects()); jcommander.getCommands().get(jcommander.getParsedCommand()).getObjects());
loggingParams.configureLogging(); // Must be called after parameters are parsed. loggingParams.configureLogging(); // Must be called after parameters are parsed.
boolean success = false;
try { try {
runCommand(command); runCommand(command);
success = true;
} catch (AuthModule.LoginRequiredException ex) { } catch (AuthModule.LoginRequiredException ex) {
System.err.println("==================================================================="); System.err.println("===================================================================");
System.err.println("You must login using 'nomulus login' prior to running this command."); System.err.println("You must login using 'nomulus login' prior to running this command.");
System.err.println("==================================================================="); System.err.println("===================================================================");
} finally {
commandsCalledCount.increment(
programName,
environment.toString(),
command.getClass().getSimpleName(),
String.valueOf(success),
String.valueOf(inShell));
exportMetrics();
} }
} }
@Override @Override
public void close() { public void close() {
exportMetrics();
if (metricWriter != null) {
metricWriter = null;
}
if (installer != null) { if (installer != null) {
installer.uninstall(); installer.uninstall();
installer = null; installer = null;
@ -180,6 +233,9 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
private void runCommand(Command command) throws Exception { private void runCommand(Command command) throws Exception {
injectReflectively(RegistryToolComponent.class, component, command); injectReflectively(RegistryToolComponent.class, component, command);
if (metricWriter == null && uploadMetrics) {
metricWriter = component.metricWriter();
}
if (command instanceof CommandWithConnection) { if (command instanceof CommandWithConnection) {
((CommandWithConnection) command).setConnection(getConnection()); ((CommandWithConnection) command).setConnection(getConnection());
@ -211,6 +267,25 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
command.run(); command.run();
} }
private void exportMetrics() {
if (metricWriter == null) {
return;
}
try {
for (Metric<?> metric : MetricRegistryImpl.getDefault().getRegisteredMetrics()) {
for (MetricPoint<?> point : metric.getTimestampedValues()) {
metricWriter.write(point);
}
}
metricWriter.flush();
} catch (IOException e) {
System.err.format("Failed to export metrics. Got error:\n%s\n\n", e);
System.err.println("Maybe you need to login? Try calling:");
System.err.println(" gcloud auth application-default login");
}
}
@VisibleForTesting
void setEnvironment(RegistryToolEnvironment environment) { void setEnvironment(RegistryToolEnvironment environment) {
this.environment = environment; this.environment = environment;
} }

View file

@ -14,6 +14,7 @@
package google.registry.tools; package google.registry.tools;
import com.google.monitoring.metrics.MetricWriter;
import dagger.Component; import dagger.Component;
import google.registry.config.CredentialModule; import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig.ConfigModule; import google.registry.config.RegistryConfig.ConfigModule;
@ -27,6 +28,7 @@ import google.registry.request.Modules.AppIdentityCredentialModule;
import google.registry.request.Modules.DatastoreServiceModule; import google.registry.request.Modules.DatastoreServiceModule;
import google.registry.request.Modules.GoogleCredentialModule; import google.registry.request.Modules.GoogleCredentialModule;
import google.registry.request.Modules.Jackson2Module; import google.registry.request.Modules.Jackson2Module;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.URLFetchServiceModule; import google.registry.request.Modules.URLFetchServiceModule;
import google.registry.request.Modules.UrlFetchTransportModule; import google.registry.request.Modules.UrlFetchTransportModule;
import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule; import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule;
@ -63,6 +65,7 @@ import javax.inject.Singleton;
Jackson2Module.class, Jackson2Module.class,
KeyModule.class, KeyModule.class,
KmsModule.class, KmsModule.class,
NetHttpTransportModule.class,
RdeModule.class, RdeModule.class,
RegistryToolModule.class, RegistryToolModule.class,
SystemClockModule.class, SystemClockModule.class,
@ -74,6 +77,7 @@ import javax.inject.Singleton;
UserServiceModule.class, UserServiceModule.class,
VoidDnsWriterModule.class, VoidDnsWriterModule.class,
WhoisModule.class, WhoisModule.class,
MetricToolModule.class,
}) })
interface RegistryToolComponent { interface RegistryToolComponent {
void inject(CheckDomainClaimsCommand command); void inject(CheckDomainClaimsCommand command);
@ -111,4 +115,6 @@ interface RegistryToolComponent {
void inject(WhoisQueryCommand command); void inject(WhoisQueryCommand command);
AppEngineConnection appEngineConnection(); AppEngineConnection appEngineConnection();
MetricWriter metricWriter();
} }

View file

@ -2,4 +2,7 @@ handlers = java.util.logging.ConsoleHandler
.level = INFO .level = INFO
com.google.wrappers.base.GoogleInit.level = WARNING com.google.wrappers.base.GoogleInit.level = WARNING
com.google.monitoring.metrics.MetricRegistryImpl.level = WARNING com.google.monitoring.metrics.MetricRegistryImpl.level = WARNING
com.google.monitoring.metrics.MetricReporter.level = WARNING
com.google.monitoring.metrics.MetricExporter.level = WARNING
com.google.monitoring.metrics.stackdriver.StackdriverWriter.level = WARNING

View file

@ -152,6 +152,7 @@ public class ShellCommandTest {
public void testMultipleCommandInvocations() throws Exception { public void testMultipleCommandInvocations() throws Exception {
try (RegistryCli cli = try (RegistryCli cli =
new RegistryCli("unittest", ImmutableMap.of("test_command", TestCommand.class))) { new RegistryCli("unittest", ImmutableMap.of("test_command", TestCommand.class))) {
cli.uploadMetrics = false;
RegistryToolEnvironment.UNITTEST.setup(); RegistryToolEnvironment.UNITTEST.setup();
cli.setEnvironment(RegistryToolEnvironment.UNITTEST); cli.setEnvironment(RegistryToolEnvironment.UNITTEST);
cli.run(new String[] {"test_command", "-x", "xval", "arg1", "arg2"}); cli.run(new String[] {"test_command", "-x", "xval", "arg1", "arg2"});
@ -169,7 +170,7 @@ public class ShellCommandTest {
public void testNonExistentCommand() { public void testNonExistentCommand() {
try (RegistryCli cli = try (RegistryCli cli =
new RegistryCli("unittest", ImmutableMap.of("test_command", TestCommand.class))) { new RegistryCli("unittest", ImmutableMap.of("test_command", TestCommand.class))) {
cli.uploadMetrics = false;
cli.setEnvironment(RegistryToolEnvironment.UNITTEST); cli.setEnvironment(RegistryToolEnvironment.UNITTEST);
assertThrows(MissingCommandException.class, () -> cli.run(new String[] {"bad_command"})); assertThrows(MissingCommandException.class, () -> cli.run(new String[] {"bad_command"}));
} }