# Licensed to Cloudera, Inc. under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. Cloudera, Inc. licenses this file # to you 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. # Copyright (c) 2015 Cloudera, Inc. All rights reserved. import logging import time from cmf.monitor import schema from cmf.monitor.abstract_monitor import CollectionError from cmf.monitor.constants import WEB_METRICS_COLLECTION_GOOD, \ DEFAULT_MONITOR_LOG_RATE, WEB_METRICS_COLLECTION_COMMUNICATION_FAILURE from cmf.monitor.generic import AbstractMetricCollector from cmf.monitor.generic.adapter import Adapter from cmf.monitor.generic.service_defined_metrics import ServiceDefinedMetrics from cmf.monitor.generic.utils import SplittingSourceProcessor, visit_json, \ SimpleMetricsExtractor from cmf.throttling_logger import ThrottlingLogger from url_util import head_request_with_timeout logging.basicConfig() LOG = logging.getLogger('HueServerAdapter') THROTTLED_LOG = ThrottlingLogger(LOG, DEFAULT_MONITOR_LOG_RATE) class _HueIsAliveCollector(AbstractMetricCollector): """ A collector that generates web-metric collection metrics from the hue is alive end-point. """ def __init__(self, adapter): AbstractMetricCollector.__init__(self, adapter) if not isinstance(adapter, HueServerAdapter): raise Exception("Unsupported adapter type") def update_with_conf(self, conf): LOG.info("hue_adapters: _HueIsAliveCollector: update_with_conf: metrics_uri: %s" % self._adapter._get_is_alive_url(conf)) self._metrics_uri = self._adapter._get_is_alive_url(conf) def collect_and_parse(self, conf): start = time.time() try: LOG.info("hue_adapters: _HueIsAliveCollector: _call_is_alive: metrics_uri: %s" % self._metrics_uri) self._call_is_alive(self._metrics_uri) result = WEB_METRICS_COLLECTION_GOOD except Exception, ex: THROTTLED_LOG.exception("Error calling is alive at '%s'" % (self._metrics_uri,)) result = CollectionError(WEB_METRICS_COLLECTION_COMMUNICATION_FAILURE) now = time.time() self._metrics_tuple = result, now, int((now - start) * 1000) def _add_metrics(self, service_version, update, collected_metrics, duration): update.add_metric(schema.WEB_METRICS_COLLECTION_DURATION, duration) if isinstance(collected_metrics, CollectionError): update.add_metric(schema.WEB_METRICS_COLLECTION_STATUS, collected_metrics.code) else: update.add_metric(schema.WEB_METRICS_COLLECTION_STATUS, collected_metrics) return True def is_ready_to_report(self, conf, pid): if pid is None: raise Exception("Pid cannot be None") metrics_url = self._adapter._get_is_alive_url(conf) if metrics_url is None: return AbstractMetricCollector._COLLECTOR_NOT_SUPPORTED try: self._call_is_alive(metrics_url, 0.5) return True except Exception, ex: pass return False def _call_is_alive(self, is_alive_url, timeout=None): """ We need this function to make testing eaiser. """ head_request_with_timeout(is_alive_url, timeout=timeout) class HueServerAdapter(Adapter): """ An adapter that collects and generates metrics for the HUE_SERVER. The adapter collects metrics from a sample file generated by hue. It also generates web-metric-collection metrics using an is-alive endpoint exposed by hue. Note that the web-metric-collection metrics generation is done as part of the sample file metric collection. The reason is that the hue is-alive end-point accepts HTTP HEAD requests and we can't use the regular web metric collector to generate these metics. """ _HUE_SERVER_METRICS_SAMPLE_FILE_KEY = "location" _HUE_SERVER_HTTP_HOST_KEY = "http_host" _HUE_SERVER_HTTP_PORT_KEY = "http_port" _HUE_SERVER_SSL_ENABLED_KEY = "ssl_enable" _SERVICE_RELEASE = 'service_release' def __init__(self, safety_valve): Adapter.__init__(self, "HUE", "HUE_SERVER", safety_valve) self._metrics = None self._is_alive_collector = _HueIsAliveCollector(self) def read_service_defined_metrics(self, path): if path is None: raise Exception("A path is required!") self._metrics = ServiceDefinedMetrics(path, SplittingSourceProcessor('::')) def get_metrics_file(self, conf): if conf is None: raise Exception("A configuration is required!") try: return conf.get( self.section, HueServerAdapter._HUE_SERVER_METRICS_SAMPLE_FILE_KEY) except: LOG.exception("Failed to retrieve %s from monitoring configuration file" % HueServerAdapter._HUE_SERVER_METRICS_SAMPLE_FILE_KEY) def _get_is_alive_url(self, conf): if conf is None: raise Exception("A configuration is required!") if not self._is_alive_supported(conf): return None try: host = conf.get(self.section, HueServerAdapter._HUE_SERVER_HTTP_HOST_KEY) if host is None: LOG.error("%s entry missing from monitoring configuration file" % (HueServerAdapter._HUE_SERVER_HTTP_HOST_KEY, )) return None port = conf.getint(self.section, HueServerAdapter._HUE_SERVER_HTTP_PORT_KEY) if port is None: LOG.error("%s entry missing from monitoring configuration file" % (HueServerAdapter._HUE_SERVER_HTTP_PORT_KEY, )) return None if conf.getboolean_with_default( self.section, HueServerAdapter._HUE_SERVER_SSL_ENABLED_KEY, False): url_format = "https://%s:%s/desktop/debug/is_alive" else: url_format = "http://%s:%s/desktop/debug/is_alive" return url_format % (host, port) except: LOG.exception("Failed to read monitoring configuration file") return None def parse_metrics_from_file(self, conf, json): if json is None: raise Exception("a json sample is required") if self._metrics is None: raise Exception("No metrics have been loaded!") role_extractor = SimpleMetricsExtractor( self._metrics.get_sources(self._role_type)) visit_json(json, [role_extractor]) return role_extractor.metrics def add_sample_file_metrics(self, version, update, metrics, accessors): for metric_id, value in metrics.iteritems(): update.add_metric(metric_id, value) def get_metrics_sample(self, version): return _HUE_SERVER_METRICS_SAMPLE def _is_alive_supported(self, conf): return True def get_adapter_specific_collectors(self): return [self._is_alive_collector] _HUE_SERVER_METRICS_SAMPLE=""" { "desktop.auth.oauth.authentication-time": { "1m_rate": 0, "999_percentile": 0, "15m_rate": 0, "99_percentile": 1234567, "mean_rate": 0, "max": 0, "sum": 0, "min": 0, "5m_rate": 0, "count": 0, "75_percentile": 0, "std_dev": 0, "95_percentile": 0, "avg": 0 }, "desktop.auth.saml2.authentication-time": { "1m_rate": 0, "999_percentile": 0, "15m_rate": 0, "99_percentile": 0, "mean_rate": 0, "max": 0, "sum": 0, "min": 0, "5m_rate": 0, "count": 0, "75_percentile": 0, "std_dev": 0, "95_percentile": 0, "avg": 0 }, "python.threads.count": { "value": 42.5 }, "desktop.users.logged-in.count": { "count": 1 }, "python.gc.referrers.count": { "value": 0 }, "desktop.requests.exceptions.count": { "count": 7 }, "python.multiprocessing.active": { "value": 0 }, "desktop.users.count": { "value": 2 }, "python.gc.referents.count": { "value": 0 }, "python.threads.active": { "value": 52 }, "python.threads.daemon": { "value": 1 }, "desktop.auth.pam.authentication-time": { "1m_rate": 0, "999_percentile": 0, "15m_rate": 0, "99_percentile": 0, "mean_rate": 0, "max": 0, "sum": 0, "min": 0, "5m_rate": 0, "count": 0, "75_percentile": 0, "std_dev": 0, "95_percentile": 0, "avg": 0 }, "desktop.auth.spnego.authentication-time": { "1m_rate": 0, "999_percentile": 0, "15m_rate": 0, "99_percentile": 0, "mean_rate": 0, "max": 0, "sum": 0, "min": 0, "5m_rate": 0, "count": 0, "75_percentile": 0, "std_dev": 0, "95_percentile": 0, "avg": 0 }, "desktop.auth.ldap.authentication-time": { "1m_rate": 0, "999_percentile": 0, "15m_rate": 0, "99_percentile": 0, "mean_rate": 0, "max": 0, "sum": 0, "min": 0, "5m_rate": 0, "count": 0, "75_percentile": 0, "std_dev": 0, "95_percentile": 0, "avg": 0 }, "desktop.requests.aggregate-response-time": { "1m_rate": 2.5683487079247715e-101, "999_percentile": 11.331326007843018, "15m_rate": 4.33344429167876e-8, "99_percentile": 11.331326007843018, "mean_rate": 0.0005057717284166405, "max": 11.331326007843018, "sum": 15.07522988319397, "min": 0.007016897201538086, "5m_rate": 2.0635466335636803e-21, "count": 7, "75_percentile": 2.34737491607666, "std_dev": 4.1352759732033935, "95_percentile": 11.331326007843018, "avg": 2.15360426902771 }, "python.gc.objects.count": { "value": 218408 }, "python.multiprocessing.count": { "value": 0 }, "desktop.auth.openid.authentication-time": { "1m_rate": 0, "999_percentile": 0, "15m_rate": 0, "99_percentile": 0, "mean_rate": 0, "max": 0, "sum": 0, "min": 0, "5m_rate": 0, "count": 0, "75_percentile": 0, "std_dev": 0, "95_percentile": 0, "avg": 0 }, "python.gc.collection.count2": { "value": 10 }, "python.gc.collection.count0": { "value": 10 }, "python.gc.collection.count1": { "value": 10 }, "python.multiprocessing.daemon": { "value": 0 }, "desktop.requests.active.count": { "count": 0 } } """