예제 #1
0
class MetricHelper(object):

    def __init__(self, test):
        self.test = test
        self.seriesly = Seriesly(
            test.test_config.stats_settings.seriesly['host'])
        self.test_config = test.test_config
        self.metric_title = test.test_config.test_case.metric_title
        self.cluster_spec = test.cluster_spec
        self.cluster_names = test.cbagent.clusters.keys()
        self.build = test.build
        self.master_node = test.master_node
        self.in_bytes_transfer = []
        self.out_bytes_transfer = []

    @staticmethod
    def _get_query_params(metric, from_ts=None, to_ts=None):
        """Convert metric definition to Seriesly query params. E.g.:

        'avg_xdc_ops' -> {'ptr': '/xdc_ops',
                          'group': 1000000000000, 'reducer': 'avg'}

        Where group is constant."""
        params = {'ptr': '/{}'.format(metric[4:]),
                  'reducer': metric[:3],
                  'group': 1000000000000}
        if from_ts and to_ts:
            params.update({'from': from_ts, 'to': to_ts})
        return params

    def _get_metric_info(self, title, larger_is_better=False, level='Basic'):
        return {'title': title,
                'cluster': self.cluster_spec.name,
                'larger_is_better': str(larger_is_better).lower(),
                'level': level}

    def calc_ycsb_queries(self, value, name, larger_is_better=True):
        metric = '{}_{}_{}'.format(self.test_config.name, name,
                                   self.cluster_spec.name)
        title = '{} , {}'.format(name, self.metric_title)
        metric_info = self._get_metric_info(title, larger_is_better)
        return value, metric, metric_info

    def calc_avg_xdcr_ops(self):
        metric = '{}_avg_xdcr_ops_{}'.format(self.test_config.name,
                                             self.cluster_spec.name)
        title = 'Avg. XDCR ops/sec, {}'.format(self.metric_title)
        metric_info = self._get_metric_info(title, larger_is_better=True)
        query_params = self._get_query_params('avg_xdc_ops')

        xdcr_ops = 0
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[1], bucket)
            data = self.seriesly[db].query(query_params)
            xdcr_ops += data.values()[0][0]
        xdcr_ops = round(xdcr_ops, 1)

        return xdcr_ops, metric, metric_info

    def calc_avg_set_meta_ops(self):
        metric = '{}_avg_set_meta_ops_{}'.format(self.test_config.name,
                                                 self.cluster_spec.name)
        title = 'Avg. XDCR rate (items/sec), {}'.format(self.metric_title)
        metric_info = self._get_metric_info(title, larger_is_better=True)
        query_params = self._get_query_params('avg_ep_num_ops_set_meta')

        set_meta_ops = 0
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[1], bucket)
            data = self.seriesly[db].query(query_params)
            set_meta_ops += data.values()[0][0]
        set_meta_ops = round(set_meta_ops, 1)

        return set_meta_ops, metric, metric_info

    def calc_avg_n1ql_queries(self):
        metric = '{}_avg_query_requests_{}'.format(self.test_config.name,
                                                   self.cluster_spec.name)
        title = 'Avg. Query Throughput (queries/sec), {}'.format(self.metric_title)
        metric_info = self._get_metric_info(title, larger_is_better=True)
        query_params = self._get_query_params('avg_query_requests')

        db = 'n1ql_stats{}'.format(self.cluster_names[0])
        data = self.seriesly[db].query(query_params)
        queries = data.values()[0][0]
        queries = round(queries, 1)

        return queries, metric, metric_info

    def parse_log(self, test_config):
        cbagent = CbAgent(self.test)
        cbagent.prepare_fts_query_stats(cbagent.clusters.keys(), test_config)
        fts = cbagent.fts_stats
        '''
         we currently have the logs.
         From the logs we will get the latest result
        '''
        fts.collect_stats()
        total = fts.cbft_query_total()
        return total

    def calc_avg_fts_queries(self, name='FTS'):
        metric = '{}_avg_query_requests_{}'.format(self.test_config.name,
                                                   self.cluster_spec.name)
        title = 'Avg. {} Query Throughput (queries/sec), {}'.format(name, self.metric_title)
        metric_info = self._get_metric_info(title, larger_is_better=True)
        total_queries, failed_queries, timeout_queries = self.parse_log(self.test_config)
        time_taken = self.test_config.access_settings.time
        qps = total_queries / time_taken
        return round(qps, 1), metric, metric_info

    def calc_latency_ftses_queries(self, percentile, dbname,
                                   metrics, name='FTS'):
        metric = '{}_{}'.format(self.test_config.name, self.cluster_spec.name)
        title = '{}th percentile {} query latency (ms), {}'.\
            format(percentile, name, self.metric_title)
        metric_info = self._get_metric_info(title, larger_is_better=False)
        timings = []
        db = '{}{}'.format(dbname, self.cluster_names[0])
        data = self.seriesly[db].get_all()
        timings += [v[metrics] for v in data.values()]
        fts_latency = round(np.percentile(timings, percentile), 2)
        return round(fts_latency), metric, metric_info

    def calc_ftses_index(self, elapsedtime):
        metric = '{}_{}'.format(self.test_config.name, self.cluster_spec.name)
        title = 'Initial Index(sec), {}'.format(self.metric_title)
        metric_info = self._get_metric_info(title, larger_is_better=False)
        return round(elapsedtime, 1), metric, metric_info

    def calc_avg_ops(self):
        """Returns the average operations per second."""
        metric = '{}_avg_ops_{}'.format(self.test_config.name,
                                        self.cluster_spec.name)
        title = 'Average ops/sec, {}'.format(self.metric_title)
        metric_info = self._get_metric_info(title, larger_is_better=True)
        query_params = self._get_query_params('avg_ops')

        ops = 0
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(query_params)
            ops += data.values()[0][0]
        ops = round(ops, 1)

        return ops, metric, metric_info

    def calc_xdcr_lag(self, percentile=90):
        metric = '{}_{}th_xdc_lag_{}'.format(self.test_config.name,
                                             percentile,
                                             self.cluster_spec.name)
        title = '{}th percentile replication lag (ms), {}'.format(
            percentile, self.metric_title)
        metric_info = self._get_metric_info(title)
        query_params = self._get_query_params('avg_xdcr_lag')
        query_params.update({'group': 1000})

        timings = []
        for bucket in self.test_config.buckets:
            db = 'xdcr_lag{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(query_params)
            timings = [v[0] for v in data.values()]
        lag = round(np.percentile(timings, percentile))

        return lag, metric, metric_info

    def calc_replication_changes_left(self):
        metric = '{}_avg_replication_queue_{}'.format(self.test_config.name,
                                                      self.cluster_spec.name)
        title = 'Avg. replication queue, {}'.format(self.metric_title)
        metric_info = self._get_metric_info(title)
        query_params = self._get_query_params('avg_replication_changes_left')

        queues = 0
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(query_params)
            queues += data.values()[0][0]
        queue = round(queues)

        return queue, metric, metric_info

    def calc_avg_replication_rate(self, time_elapsed):
        initial_items = self.test_config.load_settings.ops or \
            self.test_config.load_settings.items
        num_buckets = self.test_config.cluster.num_buckets
        avg_replication_rate = num_buckets * initial_items / time_elapsed

        return round(avg_replication_rate)

    def calc_max_drain_rate(self, time_elapsed):
        items_per_node = self.test_config.load_settings.items / \
            self.test_config.cluster.initial_nodes[0]
        drain_rate = items_per_node / time_elapsed

        return round(drain_rate)

    def calc_avg_disk_write_queue(self):
        query_params = self._get_query_params('avg_disk_write_queue')

        disk_write_queue = 0
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(query_params)
            disk_write_queue += data.values()[0][0]
        disk_write_queue /= self.test_config.cluster.initial_nodes[0]

        return round(disk_write_queue / 10 ** 3)

    def calc_avg_ep_bg_fetched(self):
        query_params = self._get_query_params('avg_ep_bg_fetched')

        ep_bg_fetched = 0
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(query_params)
            ep_bg_fetched += data.values()[0][0]
        ep_bg_fetched /= self.test_config.cluster.initial_nodes[0]

        return round(ep_bg_fetched)

    def calc_avg_bg_wait_time(self):
        query_params = self._get_query_params('avg_avg_bg_wait_time')

        avg_bg_wait_time = []
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(query_params)
            avg_bg_wait_time.append(data.values()[0][0])
        avg_bg_wait_time = np.mean(avg_bg_wait_time) / 10 ** 3  # us -> ms

        return round(avg_bg_wait_time, 2)

    def calc_avg_couch_views_ops(self):
        query_params = self._get_query_params('avg_couch_views_ops')

        couch_views_ops = 0
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(query_params)
            couch_views_ops += data.values()[0][0]

        if self.build < '2.5.0':
            couch_views_ops /= self.test_config.cluster.initial_nodes[0]

        return round(couch_views_ops)

    def calc_avg_couch_spatial_ops(self):
        query_params = self._get_query_params('avg_couch_spatial_ops')

        couch_spatial_ops = 0
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(query_params)
            couch_spatial_ops += data.values()[0][0]

        return round(couch_spatial_ops)

    def calc_query_latency(self, percentile):
        metric = '{}_{}'.format(self.test_config.name, self.cluster_spec.name)
        if 'MG7' in metric:
            title = '{}th percentile query latency, {}'.format(percentile,
                                                               self.metric_title)
        else:
            title = '{}th percentile query latency (ms), {}'.format(percentile,
                                                                    self.metric_title)
        metric_info = self._get_metric_info(title)

        timings = []
        for bucket in self.test_config.buckets:
            db = 'spring_query_latency{}{}'.format(self.cluster_names[0],
                                                   bucket)
            data = self.seriesly[db].get_all()
            timings += [value['latency_query'] for value in data.values()]
        query_latency = np.percentile(timings, percentile)
        return round(query_latency, 2), metric, metric_info

    def calc_secondaryscan_latency(self, percentile):
        metric = '{}_{}'.format(self.test_config.name, self.cluster_spec.name)
        title = '{}th percentile secondary scan latency (ms), {}'.format(percentile,
                                                                         self.metric_title)
        metric_info = self._get_metric_info(title)

        timings = []
        db = 'secondaryscan_latency{}'.format(self.cluster_names[0])
        data = self.seriesly[db].get_all()
        timings += [value[' Nth-latency'] for value in data.values()]
        timings = map(int, timings)
        secondaryscan_latency = np.percentile(timings, percentile) / 1000000

        return round(secondaryscan_latency, 2), metric, metric_info

    def calc_kv_latency(self, operation, percentile, dbname='spring_latency'):
        """Calculate kv latency
        :param operation:
        :param percentile:
        :param dbname:  Same procedure is used for KV and subdoc .
            for KV dbname will spring_latency.For subdoc will be spring_subdoc_latency
        :return:
        """
        metric = '{}_{}_{}th_{}'.format(self.test_config.name,
                                        operation,
                                        percentile,
                                        self.cluster_spec.name
                                        )
        title = '{}th percentile {} {}'.format(percentile,
                                               operation.upper(),
                                               self.metric_title)
        metric_info = self._get_metric_info(title)

        timings = []
        for bucket in self.test_config.buckets:
            db = '{}{}{}'.format(dbname, self.cluster_names[0], bucket)
            data = self.seriesly[db].get_all()
            timings += [
                v['latency_{}'.format(operation)] for v in data.values()
            ]
        latency = round(np.percentile(timings, percentile), 1)

        return latency, metric, metric_info

    def calc_observe_latency(self, percentile):
        metric = '{}_{}th_{}'.format(self.test_config.name, percentile,
                                     self.cluster_spec.name)
        title = '{}th percentile {}'.format(percentile, self.metric_title)
        metric_info = self._get_metric_info(title)

        timings = []
        for bucket in self.test_config.buckets:
            db = 'observe{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].get_all()
            timings += [v['latency_observe'] for v in data.values()]
        latency = round(np.percentile(timings, percentile), 2)

        return latency, metric, metric_info

    def calc_cpu_utilization(self):
        metric = '{}_avg_cpu_{}'.format(self.test_config.name,
                                        self.cluster_spec.name)
        title = 'Avg. CPU utilization (%)'
        title = '{}, {}'.format(title, self.metric_title)
        metric_info = self._get_metric_info(title)

        cluster = self.cluster_names[0]
        bucket = self.test_config.buckets[0]

        query_params = self._get_query_params('avg_cpu_utilization_rate')
        db = 'ns_server{}{}'.format(cluster, bucket)
        data = self.seriesly[db].query(query_params)
        cpu_utilazion = round(data.values()[0][0])

        return cpu_utilazion, metric, metric_info

    def calc_views_disk_size(self, from_ts=None, to_ts=None, meta=None):
        metric = '{}_max_views_disk_size_{}'.format(
            self.test_config.name, self.cluster_spec.name
        )
        if meta:
            metric = '{}_{}'.format(metric, meta.split()[0].lower())
        title = 'Max. views disk size (GB)'
        if meta:
            title = '{}, {}'.format(title, meta)
        title = '{}, {}'.format(title, self.metric_title)
        title = title.replace(' (min)', '')  # rebalance tests
        metric_info = self._get_metric_info(title, level='Advanced')

        query_params = self._get_query_params('max_couch_views_actual_disk_size',
                                              from_ts, to_ts)

        disk_size = 0
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(query_params)
            disk_size += round(data.values()[0][0] / 1024 ** 3, 2)  # -> GB

        return disk_size, metric, metric_info

    def calc_mem_used(self, max_min='max'):
        metric = '{}_{}_mem_used_{}'.format(
            self.test_config.name, max_min, self.cluster_spec.name
        )
        title = '{}. mem_used (MB), {}'.format(max_min.title(),
                                               self.metric_title)
        metric_info = self._get_metric_info(title)

        query_params = self._get_query_params('max_mem_used')

        mem_used = []
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(query_params)

            mem_used.append(
                round(data.values()[0][0] / 1024 ** 2)  # -> MB
            )
        mem_used = eval(max_min)(mem_used)

        return mem_used, metric, metric_info

    def calc_max_beam_rss(self):
        metric = 'beam_rss_max_{}_{}'.format(self.test_config.name,
                                             self.cluster_spec.name)
        title = 'Max. beam.smp RSS (MB), {}'.format(self.metric_title)
        metric_info = self._get_metric_info(title)

        query_params = self._get_query_params('max_beam.smp_rss')

        max_rss = 0
        for cluster_name, servers in self.cluster_spec.yield_clusters():
            cluster = filter(lambda name: name.startswith(cluster_name),
                             self.cluster_names)[0]
            for server in servers:
                hostname = server.split(':')[0].replace('.', '')
                db = 'atop{}{}'.format(cluster, hostname)  # Legacy
                data = self.seriesly[db].query(query_params)
                rss = round(data.values()[0][0] / 1024 ** 2)
                max_rss = max(max_rss, rss)

        return max_rss, metric, metric_info

    def calc_max_memcached_rss(self):
        metric = '{}_{}_memcached_rss'.format(self.test_config.name,
                                              self.cluster_spec.name)
        title = 'Max. memcached RSS (MB),{}'.format(
            self.metric_title.split(',')[-1]
        )
        metric_info = self._get_metric_info(title)

        query_params = self._get_query_params('max_memcached_rss')

        max_rss = 0
        for (cluster_name, servers), initial_nodes in zip(
                self.cluster_spec.yield_clusters(),
                self.test_config.cluster.initial_nodes,
        ):
            cluster = filter(lambda name: name.startswith(cluster_name),
                             self.cluster_names)[0]
            for server in servers[:initial_nodes]:
                hostname = server.split(':')[0].replace('.', '')
                db = 'atop{}{}'.format(cluster, hostname)
                data = self.seriesly[db].query(query_params)
                rss = round(data.values()[0][0] / 1024 ** 2)
                max_rss = max(max_rss, rss)

        return max_rss, metric, metric_info

    def calc_avg_memcached_rss(self):
        metric = '{}_{}_avg_memcached_rss'.format(self.test_config.name,
                                                  self.cluster_spec.name)
        title = 'Avg. memcached RSS (MB),{}'.format(
            self.metric_title.split(',')[-1]
        )
        metric_info = self._get_metric_info(title)

        query_params = self._get_query_params('avg_memcached_rss')

        rss = list()
        for (cluster_name, servers), initial_nodes in zip(
                self.cluster_spec.yield_clusters(),
                self.test_config.cluster.initial_nodes,
        ):
            cluster = filter(lambda name: name.startswith(cluster_name),
                             self.cluster_names)[0]
            for server in servers[:initial_nodes]:
                hostname = server.split(':')[0].replace('.', '')
                db = 'atop{}{}'.format(cluster, hostname)
                data = self.seriesly[db].query(query_params)
                rss.append(round(data.values()[0][0] / 1024 ** 2))

        avg_rss = sum(rss) / len(rss)
        return avg_rss, metric, metric_info

    def get_indexing_meta(self, value, index_type):
        metric = '{}_{}_{}'.format(self.test_config.name,
                                   index_type.lower(),
                                   self.cluster_spec.name)
        title = '{} index (min), {}'.format(index_type,
                                            self.metric_title)
        metric_info = self._get_metric_info(title)

        return value, metric, metric_info

    def calc_compaction_speed(self, time_elapsed, bucket=True):
        if bucket:
            max_query_params = \
                self._get_query_params('max_couch_docs_actual_disk_size')
            min_query_params = \
                self._get_query_params('min_couch_docs_actual_disk_size')
        else:
            max_query_params = \
                self._get_query_params('max_couch_views_actual_disk_size')
            min_query_params = \
                self._get_query_params('min_couch_views_actual_disk_size')

        max_diff = 0
        for bucket in self.test_config.buckets:
            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(max_query_params)
            disk_size_before = data.values()[0][0]

            db = 'ns_server{}{}'.format(self.cluster_names[0], bucket)
            data = self.seriesly[db].query(min_query_params)
            disk_size_after = data.values()[0][0]

            max_diff = max(max_diff, disk_size_before - disk_size_after)

        diff = max_diff / 1024 ** 2 / time_elapsed  # Mbytes/sec
        return round(diff, 1)

    def failover_time(self, reporter):
        metric = '{}_{}_failover'.format(self.test_config.name,
                                         self.cluster_spec.name)
        _split = self.metric_title.split(', ')

        title = 'Graceful failover (min), {}, {}'.format(
            _split[1][-1] + _split[1][1:-1] + _split[1][0],
            ', '.join(_split[2:]))
        metric_info = self._get_metric_info(title, larger_is_better=False)

        rebalance_time = reporter.finish('Failover')

        return rebalance_time, metric, metric_info

    @property
    def calc_network_bandwidth(self):
        self.remote = RemoteHelper(self.cluster_spec, self.test_config, verbose=True)
        for cluster_name, servers in self.cluster_spec.yield_clusters():
            self.in_bytes_transfer += [self.remote.read_bandwidth_stats("to", servers)]
            self.out_bytes_transfer += [self.remote.read_bandwidth_stats("from", servers)]
        logger.info('in bytes', self.in_bytes_transfer)
        logger.info('out bytes', self.out_bytes_transfer)
        return OrderedDict((
            ('in bytes', sum(self.in_bytes_transfer[0].values())),
            ('out bytes', sum(self.out_bytes_transfer[0].values()))))

    @property
    def calc_network_throughput(self):
        in_bytes_per_sec = []
        out_bytes_per_sec = []
        for cluster_name, servers in self.cluster_spec.yield_clusters():
            cluster = filter(lambda name: name.startswith(cluster_name),
                             self.cluster_names)[0]
            for server in servers:
                hostname = server.split(':')[0].replace('.', '')
                db = 'net{}{}'.format(cluster, hostname)
                data = self.seriesly[db].get_all()
                in_bytes_per_sec += [
                    v['in_bytes_per_sec'] for v in data.values()
                ]
                out_bytes_per_sec += [
                    v['out_bytes_per_sec'] for v in data.values()
                ]
        # To prevent exception when the values may not be available during code debugging
        if not in_bytes_per_sec:
            in_bytes_per_sec.append(0)
        if not out_bytes_per_sec:
            out_bytes_per_sec.append(0)
        f = lambda v: format(int(v), ',d')
        return OrderedDict((
            ('min in_bytes  per sec', f(min(in_bytes_per_sec))),
            ('max in_bytes  per sec', f(max(in_bytes_per_sec))),
            ('avg in_bytes  per sec', f(np.mean(in_bytes_per_sec))),
            ('p50 in_bytes  per sec', f(np.percentile(in_bytes_per_sec, 50))),
            ('p95 in_bytes  per sec', f(np.percentile(in_bytes_per_sec, 95))),
            ('p99 in_bytes  per sec', f(np.percentile(in_bytes_per_sec, 99))),
            ('min out_bytes per sec', f(min(out_bytes_per_sec))),
            ('max out_bytes per sec', f(max(out_bytes_per_sec))),
            ('avg out_bytes per sec', f(np.mean(out_bytes_per_sec))),
            ('p50 out_bytes per sec', f(np.percentile(out_bytes_per_sec, 50))),
            ('p95 out_bytes per sec', f(np.percentile(out_bytes_per_sec, 95))),
            ('p99 out_bytes per sec', f(np.percentile(out_bytes_per_sec, 99))),
        ))