1 /*
2  * Copyright 2010-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15
16 package com.amazonaws.metrics;
17 import static com.amazonaws.SDKGlobalConfiguration.DEFAULT_METRICS_SYSTEM_PROPERTY;
18
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.net.InetAddress;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashSet;
26 import java.util.Set;
27
28 import com.amazonaws.regions.Region;
29 import com.amazonaws.regions.RegionUtils;
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32
33 import com.amazonaws.SDKGlobalConfiguration;
34 import com.amazonaws.auth.AWSCredentials;
35 import com.amazonaws.auth.AWSCredentialsProvider;
36 import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
37 import com.amazonaws.auth.PropertiesCredentials;
38 import com.amazonaws.jmx.spi.SdkMBeanRegistry;
39 import com.amazonaws.regions.Regions;
40 import com.amazonaws.util.AWSRequestMetrics;
41 import com.amazonaws.util.AWSRequestMetrics.Field;
42 import com.amazonaws.util.AWSServiceMetrics;
43
44 /**
45  * Used to control the default AWS SDK metric collection system.
46  * <p>
47  * The default metric collection of the Java AWS SDK is disabled by default. To
48  * enable it, simply specify the system property
49  * <b>"com.amazonaws.sdk.enableDefaultMetrics"</b> when starting up the JVM.
50  * When the system property is specified, a default metric collector will be
51  * started at the AWS SDK level. The default implementation uploads the
52  * request/response metrics captured to Amazon CloudWatch using AWS credentials
53  * obtained via the {@link DefaultAWSCredentialsProviderChain}.
54  * <p>
55  * By default, the metrics are uploaded to the us-east-1 region. You can
56  * change the region by changing the system property as
57  * -Dcom.amazonaws.sdk.enableDefaultMetrics=cloudwatchRegion={newregion}
58  * See #CLOUDWATCH_REGION
59  * </p>
60  * <p>
61  * For additional optional attributes that can be specified for the system
62  * property, please read the javadoc of the individual fields of
63  * this class for more details.
64  * <p>
65  * Instead of via system properties, the default AWS SDK metric collection can
66  * also be enabled programmatically via {@link #enableDefaultMetrics()}.
67  * Similarly, metric collection at the AWS SDK level can be disabled via
68  * {@link #disableMetrics()}.
69  * <p>
70  * Clients who needs to fully customize the metric collection can implement the
71  * SPI {@link MetricCollector}, and then replace the default AWS SDK
72  * implementation of the collector via
73  * {@link #setMetricCollector(MetricCollector)}.
74  * <p>
75  * Alternatively, for limited customization of the internal collector
76  * implementation provided by the AWS SDK, one can extend the internal Amazon
77  * CloudWatch metric collector. See the javadoc at
78  * com.amazonaws.metrics.internal.cloudwatch.CloudWatchMetricConfig for more
79  * details.
80  */

81 public enum AwsSdkMetrics {
82     ;
83     private static final Log log = LogFactory.getLog(AwsSdkMetrics.class);
84     public static final String DEFAULT_METRIC_NAMESPACE = "AWSSDK/Java";
85     private static final String MBEAN_OBJECT_NAME =
86         "com.amazonaws.management:type=" + AwsSdkMetrics.class.getSimpleName();
87
88     /**
89      * Object name under which the Admin Mbean of the current classloader is
90      * registered.
91      */

92     private static volatile String registeredAdminMbeanName;
93     /**
94      * Used to enable the use of a single metric namespace for all levels of SDK
95      * generated CloudWatch metrics such as JVM level, host level, etc.
96      *
97      * <pre>
98      * Example:
99      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=useSingleMetricNamespace
100      * </pre>
101      */

102     public static final String USE_SINGLE_METRIC_NAMESPACE = "useSingleMetricNamespace";
103     /**
104      * Used to exclude the generation of JVM metrics when the AWS SDK default
105      * metrics is enabled.
106      * By default, jvm metrics is included.
107      *
108      * <pre>
109      * Example:
110      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=excludeJvmMetrics
111      * </pre>
112      */

113     public static final String EXCLUDE_MACHINE_METRICS = "excludeMachineMetrics";
114
115     /**
116      * Used to generate per host level metrics when the AWS SDK default
117      * metrics is enabled.
118      * By default, per-host level metrics is excluded.
119      *
120      * <pre>
121      * Example:
122      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=includePerHostMetrics
123      * </pre>
124      */

125     public static final String INCLUDE_PER_HOST_METRICS = "includePerHostMetrics";
126
127     /**
128      * Used to specify an AWS credential property file.
129      * By default, the {@link DefaultAWSCredentialsProviderChain} is used.
130      *
131      * <pre>
132      * Example:
133      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=credentialFile=/path/aws.properties
134      * </pre>
135      */

136     public static final String AWS_CREDENTIAL_PROPERTIES_FILE= "credentialFile";
137
138     /**
139      * Used to specify an AWS credential property file.
140      * By default, the {@link DefaultAWSCredentialsProviderChain} is used.
141      *
142      * <pre>
143      * Example:
144      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=credentialFile=/path/aws.properties
145      * </pre>
146      * @deprecated in favor of {@link AWS_CREDENTIAL_PROPERTIES_FILE}
147      */

148     @Deprecated
149     public static final String AWS_CREDENTAIL_PROPERTIES_FILE = AWS_CREDENTIAL_PROPERTIES_FILE;
150
151     /**
152      * Used to specify the Amazon CloudWatch region for metrics uploading purposes.
153      * By default, metrics are uploaded to us-east-1.
154      *
155      * <pre>
156      * Example:
157      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=cloudwatchRegion=us-west-2
158      * </pre>
159      */

160     public static final String CLOUDWATCH_REGION = "cloudwatchRegion";
161
162     /**
163      * Used to specify the internal in-memory queue size for queuing metrics
164      * data points. The default size is 1,000.
165      *
166      * <pre>
167      * Example:
168      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=metricQueueSize=1000
169      * </pre>
170      */

171     public static final String METRIC_QUEUE_SIZE = "metricQueueSize";
172
173     /**
174      * Used to specify the internal queue polling timeout in millisecond.
175      * The default timeout is 1 minute, which is optimal for the default
176      * CloudWatch implementation.
177      *
178      * <pre>
179      * Example:
180      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=getQueuePollTimeoutMilli=60000
181      * </pre>
182      */

183     public static final String QUEUE_POLL_TIMEOUT_MILLI = "getQueuePollTimeoutMilli";
184
185     /**
186      * Used to specify a custom metric name space.
187      * The default name space is {@link #DEFAULT_METRIC_NAMESPACE}.
188      *
189      * <pre>
190      * Example:
191      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=metricNameSpace=MyNameSpace
192      * </pre>
193      */

194     public static final String METRIC_NAME_SPACE = "metricNameSpace";
195
196     /**
197      * Used to generate per JVM level metrics when the AWS SDK default
198      * metrics is enabled.
199      * By default, JVM level metrics are not generated.
200      *
201      * <pre>
202      * Example:
203      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=jvmMetricName=Tomcat1
204      * </pre>
205      */

206     public static final String JVM_METRIC_NAME = "jvmMetricName";
207
208     /**
209      * Used to explicitly specify the host name for metric purposes, instead of
210      * detecting the host name via {@link InetAddress} when the AWS SDK default
211      * metrics is enabled. Specifying the host name also has the side effecting
212      * of enabling per host level metrics.
213      *
214      * <pre>
215      * Example:
216      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=hostMetricName=MyHost
217      * </pre>
218      */

219     public static final String HOST_METRIC_NAME = "hostMetricName";
220
221     private static final String DEFAULT_METRIC_COLLECTOR_FACTORY =
222         "com.amazonaws.metrics.internal.cloudwatch.DefaultMetricCollectorFactory";
223
224     /**
225      * Used to explicitly enable {@link Field#HttpSocketReadTime} for recording socket read time.
226      *
227      * <pre>
228      * Example:
229      *  -Dcom.amazonaws.sdk.enableDefaultMetrics=enableHttpSocketReadMetric
230      * </pre>
231      */

232     private static final String ENABLE_HTTP_SOCKET_READ_METRIC = "enableHttpSocketReadMetric";
233     /**
234      * True if the system property {@link #DEFAULT_METRICS_SYSTEM_PROPERTY} has
235      * been set; false otherwise.
236      */

237     private static final boolean defaultMetricsEnabled;
238     private static volatile AWSCredentialsProvider credentialProvider;
239     /**
240      * True if machine metrics is to be excluded; false otherwise.
241      */

242     private static volatile boolean machineMetricsExcluded;
243     /**
244      * True if per-host metrics is to be included; false if per-host metrics is
245      * to be excluded when {@link #hostMetricName} is not specified. In the
246      * absence of {@link #hostMetricName}, the host name will be automatically
247      * detected via {@link InetAddress}.
248      */

249     private static volatile boolean perHostMetricsIncluded;
250
251     /**
252      * True if socket read time metric is enabled; false otherwise. The {@link Field#HttpSocketReadTime}
253      * could have a big impact to performance, disabled by default.
254      */

255     private static volatile boolean httpSocketReadMetricEnabled;
256
257     private static volatile Region region;
258     private static volatile Integer metricQueueSize;
259     private static volatile Long queuePollTimeoutMilli;
260     private static volatile String metricNameSpace = DEFAULT_METRIC_NAMESPACE;
261     private static volatile String credentialFile;
262
263     /**
264      * No JVM level metrics is generated if this field is set to null or blank.
265      * Otherwise, the value in this field is used to compose the metric name
266      * space.
267      *
268      * Example:
269      * <ol>
270      * <li>If jvmMetricName="Tomcat1" and host-level metrics is disabled, the
271      * metric name space will be something like: "AWSSDK/Java/Tomcat1".</li>
272      * <li>If jvmMetricName="Tomcat1" and host-level metrics is enabled, the
273      * metric name space will be something like:
274      * "AWSSDK/Java/myhost.mycompany.com/Tomcat1".</li>
275      * <li>If jvmMetricName="Tomcat1" and host-level metrics is enabled and the
276      * metricNameSpace="MyNameSpace", the metric name space will be something
277      * like: "MyNameSpace/myhost.mycompany.com/Tomcat1".</li>
278      * </ol>
279      */

280     private static volatile String jvmMetricName;
281     private static volatile String hostMetricName;
282     /**
283      * True if the same metric namespace is to be used for all levels (such as
284      * JVM level, host-level, etc.) of AWS Cloudwatch Metrics for the Java SDK;
285      * false otherwise.
286      */

287     private static volatile boolean singleMetricNamespace;
288
289     static {
290         String defaultMetrics = System.getProperty(DEFAULT_METRICS_SYSTEM_PROPERTY);
291         defaultMetricsEnabled = defaultMetrics != null;
292         if (defaultMetricsEnabled) {
293             String[] values = defaultMetrics.split(",");
294             boolean excludeMachineMetrics = false;
295             boolean includePerHostMetrics = false;
296             boolean useSingleMetricNamespace = false;
297             boolean enableHttpSocketReadMetric = false;
298             for (String s: values) {
299                 String part = s.trim();
300                 if (!excludeMachineMetrics && EXCLUDE_MACHINE_METRICS.equals(part)) {
301                     excludeMachineMetrics = true;
302                 } else if (!includePerHostMetrics && INCLUDE_PER_HOST_METRICS.equals(part)) {
303                     includePerHostMetrics = true;
304                 } else if (!useSingleMetricNamespace && USE_SINGLE_METRIC_NAMESPACE.equals(part)) {
305                     useSingleMetricNamespace = true;
306                 } else if (!enableHttpSocketReadMetric && ENABLE_HTTP_SOCKET_READ_METRIC.equals(part)) {
307                     enableHttpSocketReadMetric = true;
308                 } else {
309                     String[] pair = part.split("=");
310                     if (pair.length == 2) {
311                         String key = pair[0].trim();
312                         String value  = pair[1].trim();
313                         try {
314                             if (AWS_CREDENTIAL_PROPERTIES_FILE.equals(key)) {
315                                 setCredentialFile0(value);
316                             } else if (CLOUDWATCH_REGION.equals(key)) {
317                                 region = RegionUtils.getRegion(value);
318                             } else if (METRIC_QUEUE_SIZE.equals(key)) {
319                                 Integer i = Integer.valueOf(value);
320                                 if (i.intValue() < 1)
321                                     throw new IllegalArgumentException(METRIC_QUEUE_SIZE + " must be at least 1");
322                                 metricQueueSize = i;
323                             } else if (QUEUE_POLL_TIMEOUT_MILLI.equals(key)) {
324                                 Long i = Long.valueOf(value);
325                                 if (i.intValue() < 1000)
326                                     throw new IllegalArgumentException(QUEUE_POLL_TIMEOUT_MILLI + " must be at least 1000");
327                                 queuePollTimeoutMilli = i;
328                             } else if (METRIC_NAME_SPACE.equals(key)) {
329                                 metricNameSpace = value;
330                             } else if (JVM_METRIC_NAME.equals(key)) {
331                                 jvmMetricName = value;
332                             } else if (HOST_METRIC_NAME.equals(key)) {
333                                 hostMetricName = value;
334                             } else {
335                                 LogFactory.getLog(AwsSdkMetrics.class).debug("Ignoring unrecognized parameter: " + part);
336                             }
337                         } catch (Exception e) {
338                             LogFactory.getLog(AwsSdkMetrics.class).debug("Ignoring failure", e);
339                         }
340                     }
341                 }
342             }
343             machineMetricsExcluded = excludeMachineMetrics;
344             perHostMetricsIncluded = includePerHostMetrics;
345             singleMetricNamespace = useSingleMetricNamespace;
346             httpSocketReadMetricEnabled = enableHttpSocketReadMetric;
347         }
348     }
349
350     private static final MetricRegistry registry = new MetricRegistry();
351     private static volatile MetricCollector mc;
352     /**
353      * Used to disallow re-entrancy in enabling the default metric collection system.
354      */

355     private static boolean dirtyEnabling;
356     /** Exports AwsSdkMetrics for JMX access. */
357     static {
358         try {
359             registerMetricAdminMBean();
360         } catch(Exception ex) {
361             LogFactory.getLog(AwsSdkMetrics.class).warn("", ex);
362         }
363     }
364
365     /**
366      * Returns true if the metric admin MBean is currently registered for JMX
367      * access; false otherwise.
368      */

369     public static boolean isMetricAdminMBeanRegistered() {
370         SdkMBeanRegistry registry = SdkMBeanRegistry.Factory.getMBeanRegistry();
371         return registeredAdminMbeanName != null
372                && registry.isMBeanRegistered(registeredAdminMbeanName);
373     }
374
375     /**
376      * Returns the name of the registered admin mbean; or null if the admin
377      * mbean is not currently registered.
378      */

379     public static String getRegisteredAdminMbeanName() {
380         return registeredAdminMbeanName;
381     }
382
383     /**
384      * Registers the metric admin MBean for JMX access for the current
385      * classloader. If an AdminMbean is found to have been registered under a
386      * different class loader, the AdminMBean of the current class loader would
387      * be registered under the same name {@link #MBEAN_OBJECT_NAME} but with an
388      * additional suffix in the format of "/<count>", where count is a counter
389      * incrementing from 1.
390      *
391      * @return true if the registeration succeeded; false otherwise.
392      */

393     public static boolean registerMetricAdminMBean() {
394         SdkMBeanRegistry registry = SdkMBeanRegistry.Factory.getMBeanRegistry();
395         synchronized(AwsSdkMetrics.class) {
396             if (registeredAdminMbeanName != null)
397                 return false;   // already registered
398             boolean registered = registry.registerMetricAdminMBean(MBEAN_OBJECT_NAME);
399             if (registered) {
400                 registeredAdminMbeanName = MBEAN_OBJECT_NAME;
401             } else {
402                 String mbeanName = MBEAN_OBJECT_NAME;
403                 int count = 0;
404                 while (registry.isMBeanRegistered(mbeanName)) {
405                     mbeanName = MBEAN_OBJECT_NAME + "/" + ++count;
406                 }
407                 registered = registry.registerMetricAdminMBean(mbeanName);
408                 if (registered)
409                     registeredAdminMbeanName = mbeanName;
410             }
411             if (registered)
412                 log.debug("Admin mbean registered under " + registeredAdminMbeanName);
413             return registered;
414         }
415     }
416
417     /**
418      * Unregisters the metric admin MBean from JMX for the current classloader.
419      *
420      * @return true if the unregistration succeeded or if there is no admin
421      *         MBean registered; false otherwise.
422      */

423     public static boolean unregisterMetricAdminMBean() {
424         SdkMBeanRegistry registry = SdkMBeanRegistry.Factory.getMBeanRegistry();
425         synchronized(AwsSdkMetrics.class) {
426             if (registeredAdminMbeanName == null)
427                 return true;
428             boolean success = registry.unregisterMBean(registeredAdminMbeanName);
429             if (success)
430                 registeredAdminMbeanName = null;
431             return success;
432         }
433     }
434     /**
435      * Returns a non-null request metric collector for the SDK. If no custom
436      * request metric collector has previously been specified via
437      * {@link #setMetricCollector(MetricCollector)} and the
438      * {@link SDKGlobalConfiguration#DEFAULT_METRICS_SYSTEM_PROPERTY} has been set, then this method
439      * will initialize and return the default metric collector provided by the
440      * AWS SDK on a best-attempt basis.
441      */

442     public static <T extends RequestMetricCollector> T getRequestMetricCollector() {
443         if (mc == null) {
444             if (isDefaultMetricsEnabled())
445                 enableDefaultMetrics();
446         }
447         @SuppressWarnings("unchecked")
448         T t = (T)(mc == null ? RequestMetricCollector.NONE : mc.getRequestMetricCollector());
449         return t;
450     }
451
452     public static <T extends ServiceMetricCollector> T getServiceMetricCollector() {
453         if (mc == null) {
454             if (isDefaultMetricsEnabled())
455                 enableDefaultMetrics();
456         }
457         @SuppressWarnings("unchecked")
458         T t = (T)(mc == null ? ServiceMetricCollector.NONE : mc.getServiceMetricCollector());
459         return t;
460     }
461
462     /**
463      * This method should never be called by anyone except the JMX MBean used
464      * for administrative purposes only.
465      */

466     static MetricCollector getInternalMetricCollector() {
467         return mc;
468     }
469
470     public static <T extends MetricCollector> T getMetricCollector() {
471         if (mc == null) {
472             if (isDefaultMetricsEnabled())
473                 enableDefaultMetrics();
474         }
475         @SuppressWarnings("unchecked")
476         T t = (T)(mc == null ? MetricCollector.NONE : mc);
477         return t;
478     }
479
480     /**
481      * Sets the metric collector to be used by the AWS SDK, and stop the
482      * previously running collector used by the AWS SDK, if any. Note, however,
483      * a request metric collector specified at the web service client level or
484      * request level, if any, always takes precedence over the one specified at
485      * the AWS SDK level.
486      * <p>
487      * Caller of this method is responsible for starting the new metric
488      * collector specified as the input parameter.
489      *
490      * @param mc
491      *            the metric collector to be used by the AWS SDK; or
492      *            null if no metric collection is to be performed
493      *            at the AWS SDK level.
494      *
495      * @see RequestMetricCollector
496      * @see RequestMetricCollector#NONE
497      */

498     public static synchronized void setMetricCollector(MetricCollector mc) {
499         MetricCollector old = AwsSdkMetrics.mc;
500         AwsSdkMetrics.mc = mc;
501         if (old != null) {
502             old.stop();
503         }
504     }
505
506     /**
507      * Used to set whether the machine metrics is to be excluded.
508      *
509      * @param excludeMachineMetrics true if machine metrics is to be excluded;
510      * false otherwise.
511      */

512     public static void setMachineMetricsExcluded(boolean excludeMachineMetrics) {
513         AwsSdkMetrics.machineMetricsExcluded = excludeMachineMetrics;
514     }
515
516     /**
517      * Used to set whether the per-host metrics is to be included.
518      *
519      * @param includePerHostMetrics true if per-host metrics is to be included;
520      * false otherwise.
521      */

522     public static void setPerHostMetricsIncluded(boolean includePerHostMetrics) {
523         AwsSdkMetrics.perHostMetricsIncluded = includePerHostMetrics;
524     }
525
526     /**
527      * Used to enable {@link Field#HttpSocketReadTime} metric since by default it is disabled.
528      */

529     public static void enableHttpSocketReadMetric() {
530         AwsSdkMetrics.httpSocketReadMetricEnabled = true;
531     }
532
533     /**
534      * Returns true if the system property
535      * {@link SDKGlobalConfiguration#DEFAULT_METRICS_SYSTEM_PROPERTY} has been
536      * set; false otherwise.
537      */

538     public static boolean isDefaultMetricsEnabled() {
539         return defaultMetricsEnabled;
540     }
541
542     /**
543      * Returns true if a single metric name space is to be used for all
544      * levels of SDK generated CloudWatch metrics, including JVM level, host
545      * level, etc.; false otherwise.
546      */

547     public static boolean isSingleMetricNamespace() {
548         return singleMetricNamespace;
549     }
550
551     /**
552      * Used to set whether a single metric name space is to be used for all
553      * levels of SDK generated CloudWatch metrics, including JVM level, host
554      * level, etc.
555      *
556      * @param singleMetricNamespace
557      *            true if single metric name is to be used; false otherwise.
558      */

559     public static void setSingleMetricNamespace(boolean singleMetricNamespace) {
560         AwsSdkMetrics.singleMetricNamespace = singleMetricNamespace;
561     }
562
563     /**
564      * Returns true if metrics at the AWS SDK level is enabled; false
565      * if disabled.
566      */

567     public static boolean isMetricsEnabled() {
568         MetricCollector mc = AwsSdkMetrics.mc;
569         return mc != null && mc.isEnabled();
570     }
571
572     /**
573      * Returns true if machine metrics is to be excluded.
574      */

575     public static boolean isMachineMetricExcluded() {
576         return machineMetricsExcluded;
577     }
578
579     /**
580      * Returns true if the per-host metrics flag has been set; false otherwise.
581      */

582     public static boolean isPerHostMetricIncluded() {
583         return perHostMetricsIncluded;
584     }
585
586     /**
587      * Returns true if per-host metrics is enabled; false otherwise.
588      */

589     public static boolean isPerHostMetricEnabled() {
590         if (perHostMetricsIncluded)
591             return true;
592         String host = hostMetricName;
593         host = host == null ? "" : host.trim();
594         return host.length() > 0;
595     }
596
597     /**
598      * Returns true if HttpSocketReadMetric is enabled; false otherwise.
599      */

600     public static boolean isHttpSocketReadMetricEnabled() {
601         return httpSocketReadMetricEnabled;
602     }
603
604     /**
605      * Starts the default AWS SDK metric collector, but
606      * only if no metric collector is currently in use at the AWS SDK
607      * level.
608      *
609      * @return true if the default AWS SDK metric collector has been
610      *         successfully started by this call; false otherwise.
611      */

612     public static synchronized boolean enableDefaultMetrics() {
613         if (mc == null || !mc.isEnabled()) {
614             if (dirtyEnabling) {
615                 throw new IllegalStateException("Reentrancy is not allowed");
616             }
617             dirtyEnabling = true;
618             try {
619                 Class<?> c = Class.forName(DEFAULT_METRIC_COLLECTOR_FACTORY);
620                 MetricCollector.Factory f = (MetricCollector.Factory)c.newInstance();
621                 MetricCollector instance = f.getInstance();
622                 if (instance != null) {
623                     setMetricCollector(instance);
624                     return true;
625                 }
626             } catch (Exception e) {
627                 LogFactory.getLog(AwsSdkMetrics.class)
628                           .warn("Failed to enable the default metrics", e);
629             } finally {
630                 dirtyEnabling = false;
631             }
632         }
633         return false;
634     }
635
636     /**
637      * Convenient method to disable the metric collector at the AWS SDK
638      * level.
639      */

640     public static void disableMetrics() {
641         setMetricCollector(MetricCollector.NONE);
642     }
643
644     /**
645      * Adds the given metric type to the registry of predefined metrics to be
646      * captured at the AWS SDK level.
647      *
648      * @return true if the set of predefined metric types gets changed as a
649      *        result of the call
650      */

651     public static boolean add(MetricType type) {
652         return type == null ? false : registry.addMetricType(type);
653     }
654     /**
655      * Adds the given metric types to the registry of predefined metrics to be
656      * captured at the AWS SDK level.
657      *
658      * @return true if the set of predefined metric types gets changed as a
659      *        result of the call
660      */

661     public static <T extends MetricType> boolean addAll(Collection<T> types) {
662         return types == null || types.size() == 0
663                ? false
664                : registry.addMetricTypes(types);
665     }
666     /**
667      * Sets the given metric types to replace the registry of predefined metrics
668      * to be captured at the AWS SDK level.
669      */

670     public static <T extends MetricType> void set(Collection<T> types) {
671         registry.setMetricTypes(types);
672     }
673     /**
674      * Removes the given metric type from the registry of predefined metrics to
675      * be captured at the AWS SDK level.
676      *
677      * @return true if the set of predefined metric types gets changed as a
678      *        result of the call
679      */

680     public static boolean remove(MetricType type) {
681         return type == null ? false : registry.removeMetricType(type);
682     }
683     /**
684      * Returns an unmodifiable set of the current predefined metrics.
685      */

686     public static Set<MetricType> getPredefinedMetrics() {
687         return registry.predefinedMetrics();
688     }
689
690     /**
691      * Returns the credential provider for the default AWS SDK metric implementation.
692      * This method is restricted to calls from the default AWS SDK metric implementation.
693      *
694      * @throws SecurityException if called outside the default AWS SDK metric implementation.
695      */

696     public static AWSCredentialsProvider getCredentialProvider() {
697         StackTraceElement[] e = Thread.currentThread().getStackTrace();
698         for (int i=0; i < e.length; i++) {
699             if (e[i].getClassName().equals(DEFAULT_METRIC_COLLECTOR_FACTORY)) {
700                 return credentialProvider;
701             }
702         }
703         SecurityException ex = new SecurityException();
704         LogFactory.getLog(AwsSdkMetrics.class).warn("Illegal attempt to access the credential provider", ex);
705         throw ex;
706     }
707
708     /**
709      * Sets the credential provider for the default AWS SDK metric
710      * implementation; or null if the default is to be used. Calling this method
711      * may result in the credential provider being different from the credential
712      * file property.
713      */

714     public static synchronized void setCredentialProvider(
715         AWSCredentialsProvider provider) {
716         credentialProvider = provider;
717     }
718
719     /**
720      * Returns the region configured for the default AWS SDK metric collector;
721      * or null if the default is to be used.
722      *
723      * @throws IllegalArgumentException when using a region not included in
724      * {@link Regions}
725      *
726      * @deprecated Use {@link #getRegionName()}
727      */

728     public static Regions getRegion() throws IllegalArgumentException {
729         return Regions.fromName(region.getName());
730     }
731
732     /**
733      * Returns the region name configured for the default AWS SDK metric collector;
734      * or null if the default is to be used.
735      */

736     public static String getRegionName() {
737         return region == null ? null : region.getName();
738     }
739
740     /**
741      * Sets the region to be used for the default AWS SDK metric collector;
742      * or null if the default is to be used.
743      */

744     public static void setRegion(Regions region) {
745         AwsSdkMetrics.region = RegionUtils.getRegion(region.getName());
746     }
747
748     /**
749      * Sets the region to be used for the default AWS SDK metric collector;
750      * or null if the default is to be used.
751      */

752     public static void setRegion(String region) {
753         AwsSdkMetrics.region = RegionUtils.getRegion(region);
754     }
755
756     /**
757      * Returns the last set AWS credential file, or null if there is none.
758      * @deprecated use {@link AwsSdkMetrics#getCredentialFile()}
759      */

760     @Deprecated
761     public static String getCredentailFile() {
762         return credentialFile;
763     }
764
765     /**
766      * Returns the last set AWS credential file, or null if there is none.
767      */

768     public static String getCredentialFile() {
769         return credentialFile;
770     }
771
772     /**
773      * Sets the AWS credential file to be used for accessing Amazon CloudWatch.
774      * Successfully calling this method would result in the AWS credential
775      * provider to make use of the given credential file.
776      */

777     public static void setCredentialFile(String filepath)
778         throws FileNotFoundException, IOException {
779         setCredentialFile0(filepath);
780     }
781
782     /**
783      * Internal method to implement the {@link #setCredentialFile(String)}.
784      */

785     private static void setCredentialFile0(String filepath)
786         throws FileNotFoundException, IOException {
787         final PropertiesCredentials cred =
788             new PropertiesCredentials(new File(filepath));
789         synchronized(AwsSdkMetrics.class) {
790             credentialProvider = new AWSCredentialsProvider() {
791                 @Override public void refresh() {}
792                 @Override public AWSCredentials getCredentials() {
793                     return cred;
794                 }
795             };
796             AwsSdkMetrics.credentialFile = filepath;
797         }
798     }
799
800     /**
801      * Returns the internal metric queue size to be used for the default AWS SDK
802      * metric collector; or null if the default is to be used.
803      */

804     public static Integer getMetricQueueSize() {
805         return metricQueueSize;
806     }
807
808     /**
809      * Sets the metric queue size to be used for the default AWS SDK metric collector;
810      * or null if the default is to be used.
811      */

812     public static void setMetricQueueSize(Integer size) {
813         metricQueueSize = size;
814     }
815
816     /**
817      * Returns the internal metric queue timeout in millisecond to be used for
818      * the default AWS SDK metric collector; or null if the default is to be
819      * used.
820      */

821     public static Long getQueuePollTimeoutMilli() {
822         return queuePollTimeoutMilli;
823     }
824
825     /**
826      * Sets the queue poll time in millisecond to be used for the default AWS
827      * SDK metric collector; or null if the default is to be used.
828      */

829     public static void setQueuePollTimeoutMilli(Long timeoutMilli) {
830         queuePollTimeoutMilli = timeoutMilli;
831     }
832
833     /**
834      * Returns the metric name space, which is never null or blank.
835      */

836     public static String getMetricNameSpace() {
837         return metricNameSpace;
838     }
839
840     /**
841      * Sets the metric name space.
842      *
843      * @param metricNameSpace
844      *            metric name space which must neither be null or blank.
845      *
846      * @throws IllegalArgumentException
847      *             if the specified metric name space is either null or blank.
848      */

849     public static void setMetricNameSpace(String metricNameSpace) {
850         if (metricNameSpace == null || metricNameSpace.trim().length() == 0)
851             throw new IllegalArgumentException();
852         AwsSdkMetrics.metricNameSpace = metricNameSpace;
853     }
854
855     /**
856      * Returns the name of the JVM for generating per-JVM level metrics;
857      * or null or blank if per-JVM level metrics are disabled.
858      */

859     public static String getJvmMetricName() {
860         return jvmMetricName;
861     }
862
863     /**
864      * Sets the name of the JVM for generating per-JVM level metrics.
865      *
866      * @param jvmMetricName
867      *            name of the JVM for generating per-JVM level metrics; or null
868      *            or blank if per-JVM level metrics are to be disabled.
869      */

870     public static void setJvmMetricName(String jvmMetricName) {
871         AwsSdkMetrics.jvmMetricName = jvmMetricName;
872     }
873
874     /**
875      * Returns the host name for generating per-host level metrics; or
876      * null or blank if the host is to be automatically detected via
877      * {@link InetAddress}.
878      */

879     public static String getHostMetricName() {
880         return hostMetricName;
881     }
882
883     /**
884      * Sets the host name for generating per-host level metrics.
885      *
886      * @param hostMetricName
887      *            host name for generating per-host level metrics; or
888      *            null or blank if the host is to be automatically detected via
889      *            {@link InetAddress}.
890      */

891     public static void setHostMetricName(String hostMetricName) {
892         AwsSdkMetrics.hostMetricName = hostMetricName;
893     }
894
895     /**
896      * Used as a registry for the predefined metrics to be captured by the
897      * metric collector at the AWS SDK level.
898      */

899     private static class MetricRegistry {
900         private final Set<MetricType> metricTypes = new HashSet<MetricType>();
901         private volatile Set<MetricType> readOnly;
902
903         MetricRegistry() {
904             metricTypes.add(Field.ClientExecuteTime);
905             metricTypes.add(Field.Exception);
906             metricTypes.add(Field.ThrottleException);
907             metricTypes.add(Field.HttpClientRetryCount);
908             metricTypes.add(Field.HttpRequestTime);
909             metricTypes.add(Field.RequestCount);
910             //            metricTypes.add(Field.RequestSigningTime);
911             //            metricTypes.add(Field.ResponseProcessingTime);
912             metricTypes.add(Field.RetryCount);
913             metricTypes.add(Field.RetryCapacityConsumed);
914             metricTypes.add(Field.ThrottledRetryCount);
915             metricTypes.add(Field.HttpClientSendRequestTime);
916             metricTypes.add(Field.HttpClientReceiveResponseTime);
917             metricTypes.add(Field.HttpSocketReadTime);
918             metricTypes.add(Field.HttpClientPoolAvailableCount);
919             metricTypes.add(Field.HttpClientPoolLeasedCount);
920             metricTypes.add(Field.HttpClientPoolPendingCount);
921             metricTypes.add(AWSServiceMetrics.HttpClientGetConnectionTime);
922             syncReadOnly();
923         }
924
925         private void syncReadOnly() {
926             readOnly = Collections.unmodifiableSet(new HashSet<MetricType>(metricTypes));
927         }
928
929         public boolean addMetricType(MetricType type) {
930             synchronized(metricTypes) {
931                 boolean added = metricTypes.add(type);
932                 if (added)
933                     syncReadOnly();
934                 return added;
935             }
936         }
937         public <T extends MetricType> boolean addMetricTypes(Collection<T> types) {
938             synchronized(metricTypes) {
939                 boolean added = metricTypes.addAll(types);
940                 if (added)
941                     syncReadOnly();
942                 return added;
943             }
944         }
945         public <T extends MetricType> void setMetricTypes(Collection<T> types) {
946             synchronized(metricTypes) {
947                 if (types == null || types.size() == 0) {
948                     if (metricTypes.size() == 0)
949                         return;
950                     if (types == null)
951                         types = Collections.emptyList();
952                 }
953                 metricTypes.clear();
954                 if (!addMetricTypes(types)) {
955                     syncReadOnly(); // avoid missing sync
956                 }
957             }
958         }
959         public boolean removeMetricType(MetricType type) {
960             synchronized(metricTypes) {
961                 boolean removed = metricTypes.remove(type);
962                 if (removed)
963                     syncReadOnly();
964                 return removed;
965             }
966         }
967         public Set<MetricType> predefinedMetrics() {
968             return readOnly;
969         }
970     }
971 }
972