Create utils module for processing task results

Here we create a special rally.benchmark.processing.utils module for
computing a couple of useful functions on the benchmark data.

Change-Id: I9f3e6e50b9ba37946fbb56d7546b87b30beb0233
This commit is contained in:
Mikhail Dubov 2014-04-22 13:48:49 +04:00
parent 621e89b895
commit 019eccb3d3
5 changed files with 117 additions and 39 deletions

View File

@ -20,6 +20,7 @@ import os
import mako.template
from rally.benchmark.processing.charts import histogram as histo
from rally.benchmark.processing import utils
def _process_main_duration(result):
@ -72,7 +73,7 @@ def _process_atomic(result):
def avg(lst, key=None):
lst = lst if not key else map(lambda x: x[key], lst)
return sum(lst) / float(len(lst))
return utils.mean(lst)
# NOTE(boris-42): In our result["result"] we have next structure:
# {"error": NoneOrDict,

View File

@ -0,0 +1,67 @@
# Copyright 2014: Mirantis Inc.
# 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.
import math
from rally import exceptions
def mean(values):
"""Find the simple average of a list of values.
:parameter values: non-empty list of numbers
:returns: float value
"""
if not values:
raise exceptions.InvalidArgumentsException(
message="the list should be non-empty")
return math.fsum(values) / len(values)
def percentile(values, percent):
"""Find the percentile of a list of values.
:parameter values: list of numbers
:parameter percent: float value from 0.0 to 1.0
:returns: the percentile of values
"""
if not values:
return None
values.sort()
k = (len(values) - 1) * percent
f = math.floor(k)
c = math.ceil(k)
if f == c:
return values[int(k)]
d0 = values[int(f)] * (c - k)
d1 = values[int(c)] * (k - f)
return (d0 + d1)
def get_durations(raw_data, get_duration, is_successful):
"""Retrieve the benchmark duration data from a list of records.
:parameter raw_data: list of records
:parameter get_duration: function that retrieves the duration data from
a given record
:parameter is_successful: function that returns True if the record contains
a successful benchmark result, False otherwise
:returns: list of float values corresponding to benchmark durations
"""
data = [get_duration(run) for run in raw_data if is_successful(run)]
return data

View File

@ -19,7 +19,6 @@ from __future__ import print_function
import collections
import json
import math
import os
import pprint
import prettytable
@ -30,6 +29,7 @@ import yaml
from oslo.config import cfg
from rally.benchmark.processing import plot
from rally.benchmark.processing import utils
from rally.cmd import cliutils
from rally.cmd.commands import use
from rally.cmd import envutils
@ -156,10 +156,10 @@ class TaskCommands(object):
atomic_action_table.add_row([k,
len(v),
max(v),
sum(v) / len(v),
utils.mean(v),
min(v),
percentile(v, 0.90),
percentile(v, 0.95)])
utils.percentile(v, 0.90),
utils.percentile(v, 0.95)])
print(atomic_action_table)
print()
@ -210,8 +210,8 @@ class TaskCommands(object):
_print_atomic_actions(result["data"]["raw"])
raw = result["data"]["raw"]
durations = map(lambda x: x['duration'],
filter(lambda r: not r['error'], raw))
durations = utils.get_durations(raw, lambda x: x['duration'],
lambda r: not r['error'])
table = prettytable.PrettyTable(["max (sec)",
"avg (sec)",
"min (sec)",
@ -221,10 +221,10 @@ class TaskCommands(object):
"total times"])
if durations:
table.add_row([max(durations),
sum(durations) / len(durations),
utils.mean(durations),
min(durations),
percentile(durations, 0.90),
percentile(durations, 0.95),
utils.percentile(durations, 0.90),
utils.percentile(durations, 0.95),
float(len(durations)) / len(raw),
len(raw)])
else:
@ -256,10 +256,10 @@ class TaskCommands(object):
if values:
row = [str(key),
max(values),
sum(values) / len(values),
utils.mean(values),
min(values),
percentile(values, 0.90),
percentile(values, 0.95)]
utils.percentile(values, 0.90),
utils.percentile(values, 0.95)]
else:
row = [str(key)] + ['n/a'] * 5
ssr_table.add_row(row)
@ -352,24 +352,3 @@ class TaskCommands(object):
api.delete_task(tid, force=force)
else:
api.delete_task(task_id, force=force)
def percentile(N, percent):
"""Find the percentile of a list of values.
@parameter N - is a list of values.
@parameter percent - a float value from 0.0 to 1.0.
@return - the percentile of the values
"""
if not N:
return None
N.sort()
k = (len(N) - 1) * percent
f = math.floor(k)
c = math.ceil(k)
if f == c:
return N[int(k)]
d0 = N[int(f)] * (c - k)
d1 = N[int(c)] * (k - f)
return (d0 + d1)

View File

@ -0,0 +1,36 @@
# Copyright 2014: Mirantis Inc.
# 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.
from rally.benchmark.processing import utils
from rally import exceptions
from tests import test
class ProcessingUtilsTestCase(test.TestCase):
def test_percentile(self):
lst = range(1, 101)
result = utils.percentile(lst, 0.1)
self.assertTrue(result == 10.9)
def test_mean(self):
lst = range(1, 100)
result = utils.mean(lst)
self.assertTrue(result == 50.0)
def test_mean_empty_list(self):
lst = []
self.assertRaises(exceptions.InvalidArgumentsException,
utils.mean, lst)

View File

@ -151,8 +151,3 @@ class TaskCommandsTestCase(test.TestCase):
expected_calls = [mock.call(task_uuid, force=force) for task_uuid
in task_uuids]
self.assertTrue(mock_api.delete_task.mock_calls == expected_calls)
def test_percentile(self):
l = range(1, 101)
result = task.percentile(l, 0.1)
self.assertTrue(result == 10.9)