# Copyright (c) 2016 Cisco and/or its affiliates.
# 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.
*** Settings ***
| Library | resources.libraries.python.NodePath
| Library | resources.libraries.python.Trace
| Resource | resources/libraries/robot/default.robot
| Resource | resources/libraries/robot/interfaces.robot
| Resource | resources/libraries/robot/ipv4.robot
| Force Tags | 3_NODE_SINGLE_LINK_TOPO | HW_ENV | SKIP_VPP_PATCH
| Suite Setup | Run Keywords
| ... | Setup all DUTs before test | AND
| ... | Setup all TGs before traffic script | AND
| ... | Update All Interface Data On All Nodes | ${nodes} | AND
| ... | Setup DUT nodes for IPv4 testing
| Test Setup | Run Keywords | Save VPP PIDs | AND
| ... |
@media only all and (prefers-color-scheme: dark) {
.highlight .hll { background-color: #49483e }
.highlight .c { color: #75715e } /* Comment */
.highlight .err { color: #960050; background-color: #1e0010 } /* Error */
.highlight .k { color: #66d9ef } /* Keyword */
.highlight .l { color: #ae81ff } /* Literal */
.highlight .n { color: #f8f8f2 } /* Name */
.highlight .o { color: #f92672 } /* Operator */
.highlight .p { color: #f8f8f2 } /* Punctuation */
.highlight .ch { color: #75715e } /* Comment.Hashbang */
.highlight .cm { color: #75715e } /* Comment.Multiline */
.highlight .cp { color: #75715e } /* Comment.Preproc */
.highlight .cpf { color: #75715e } /* Comment.PreprocFile */
.highlight .c1 { color: #75715e } /* Comment.Single */
.highlight .cs { color: #75715e } /* Comment.Special */
.highlight .gd { color: #f92672 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gi { color: #a6e22e } /* Generic.Inserted */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #75715e } /* Generic.Subheading */
.highlight .kc { color: #66d9ef } /* Keyword.Constant */
.highlight .kd { color: #66d9ef } /* Keyword.Declaration */
.highlight .kn { color: #f92672 } /* Keyword.Namespace */
.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */
.highlight .kr { color: #66d9ef } /* Keyword.Reserved */
.highlight .kt { color: #66d9ef } /* Keyword.Type */
.highlight .ld { color: #e6db74 } /* Literal.Date */
.highlight .m { color: #ae81ff } /* Literal.Number */
.highlight .s { color: #e6db74 } /* Literal.String */
.highlight .na { color: #a6e22e } /* Name.Attribute */
.highlight .nb { color: #f8f8f2 } /* Name.Builtin */
.highlight .nc { color: #a6e22e } /* Name.Class */
.highlight .no { color: #66d9ef } /* Name.Constant */
.highlight .nd { color: #a6e22e } /* Name.Decorator */
.highlight .ni { color: #f8f8f2 } /* Name.Entity */
.highlight .ne { color: #a6e22e } /* Name.Exception */
.highlight .nf { color: #a6e22e } /* Name.Function */
.highlight .nl { color: #f8f8f2 } /* Name.Label */
.highlight .nn { color: #f8f8f2 } /* Name.Namespace */
.highlight .nx { color: #a6e22e } /* Name.Other */
.highlight .py { color: #f8f8f2 } /* Name.Property */
.highlight .nt { color: #f92672 } /* Name.Tag */
.highlight .nv { color: #f8f8f2 } /* Name.Variable */
.highlight .ow { color: #f92672 } /* Operator.Word */
.highlight .w { color: #f8f8f2 } /* Text.Whitespace */
.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */
.highlight .mf { color: #ae81ff } /* Literal.Number.Float */
.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */
.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */
.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */
.highlight .sa { color: #e6db74 } /* Literal.String.Affix */
.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */
.highlight .sc { color: #e6db74 } /* Literal.String.Char */
.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */
.highlight .sd { color: #e6db74 } /* Literal.String.Doc */
.highlight .s2 { color: #e6db74 } /* Literal.String.Double */
.highlight .se { color: #ae81ff } /* Literal.String.Escape */
.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */
.highlight .si { color: #e6db74 } /* Literal.String.Interpol */
.highlight .sx { color: #e6db74 } /* Literal.String.Other */
.highlight .sr { color: #e6db74 } /* Literal.String.Regex */
.highlight .s1 { color: #e6db74 } /* Literal.String.Single */
.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */
.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #a6e22e } /* Name.Function.Magic */
.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */
.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */
.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */
}
@media (prefers-color-scheme: light) {
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
}
# Copyright (c) 2018 Cisco and/or its affiliates.
# 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.
"""Generation of Continuous Performance Trending and Analysis.
"""
import datetime
import logging
import csv
import prettytable
import plotly.offline as ploff
import plotly.graph_objs as plgo
import plotly.exceptions as plerr
import numpy as np
import pandas as pd
from collections import OrderedDict
from utils import find_outliers, archive_input_data, execute_command
# Command to build the html format of the report
HTML_BUILDER = 'sphinx-build -v -c conf_cpta -a ' \
'-b html -E ' \
'-t html ' \
'-D version="Generated on {date}" ' \
'{working_dir} ' \
'{build_dir}/'
# .css file for the html format of the report
THEME_OVERRIDES = """/* override table width restrictions */
.wy-nav-content {
max-width: 1200px !important;
}
"""
COLORS = ["SkyBlue", "Olive", "Purple", "Coral", "Indigo", "Pink",
"Chocolate", "Brown", "Magenta", "Cyan", "Orange", "Black",
"Violet", "Blue", "Yellow"]
def generate_cpta(spec, data):
"""Generate all formats and versions of the Continuous Performance Trending
and Analysis.
:param spec: Specification read from the specification file.
:param data: Full data set.
:type spec: Specification
:type data: InputData
"""
logging.info("Generating the Continuous Performance Trending and Analysis "
"...")
ret_code = _generate_all_charts(spec, data)
cmd = HTML_BUILDER.format(
date=datetime.date.today().strftime('%d-%b-%Y'),
working_dir=spec.environment["paths"]["DIR[WORKING,SRC]"],
build_dir=spec.environment["paths"]["DIR[BUILD,HTML]"])
execute_command(cmd)
with open(spec.environment["paths"]["DIR[CSS_PATCH_FILE]"], "w") as \
css_file:
css_file.write(THEME_OVERRIDES)
with open(spec.environment["paths"]["DIR[CSS_PATCH_FILE2]"], "w") as \
css_file:
css_file.write(THEME_OVERRIDES)
archive_input_data(spec)
logging.info("Done.")
return ret_code
def _select_data(in_data, period, fill_missing=False, use_first=False):
"""Select the data from the full data set. The selection is done by picking
the samples depending on the period: period = 1: All, period = 2: every
second sample, period = 3: every third sample ...
:param in_data: Full set of data.
:param period: Sampling period.
:param fill_missing: If the chosen sample is missing in the full set, its
nearest neighbour is used.
:param use_first: Use the first sample even though it is not chosen.
:type in_data: OrderedDict
:type period: int
:type fill_missing: bool
:type use_first: bool
:returns: Reduced data.
:rtype: OrderedDict
"""
first_idx = min(in_data.keys())
last_idx = max(in_data.keys())
idx = last_idx
data_dict = dict()
if use_first:
data_dict[first_idx] = in_data[first_idx]
while idx >= first_idx:
data = in_data.get(idx, None)
if data is None:
if fill_missing:
threshold = int(round(idx - period / 2)) + 1 - period % 2
idx_low = first_idx if threshold < first_idx else threshold
threshold = int(round(idx + period / 2))
idx_high = last_idx if threshold > last_idx else threshold
flag_l = True
flag_h = True
idx_lst = list()
inc = 1
while flag_l or flag_h:
if idx + inc > idx_high:
flag_h = False
else:
idx_lst.append(idx + inc)
if idx - inc < idx_low:
flag_l = False
else:
idx_lst.append(idx - inc)
inc += 1
for i in idx_lst:
if i in in_data.keys():
data_dict[i] = in_data[i]
break
else:
data_dict[idx] = data
idx -= period
return OrderedDict(sorted(data_dict.items(), key=lambda t: t[0]))
def _evaluate_results(in_data, trimmed_data, window=10):
"""Evaluates if the sample value is regress, normal or progress compared to
previous data within the window.
We use the intervals defined as:
- regress: less than median - 3 * stdev
- normal: between median - 3 * stdev and median + 3 * stdev
- progress: more than median + 3 * stdev
:param in_data: Full data set.
:param trimmed_data: Full data set without the outliers.
:param window: Window size used to calculate moving median and moving stdev.
:type in_data: pandas.Series
:type trimmed_data: pandas.Series
:type window: int
:returns: Evaluated results.
:rtype: list
"""
if len(in_data) > 2:
win_size = in_data.size if in_data.size < window else window
results = [0.0, ] * win_size
median = in_data.rolling(window=win_size).median()
stdev_t = trimmed_data.rolling(window=win_size, min_periods=2).std()
m_vals = median.values
s_vals = stdev_t.values
d_vals = in_data.values
for day in range(win_size, in_data.size):
if np.isnan(m_vals[day - 1]) or np.isnan(s_vals[day - 1]):
results.append(0.0)
elif d_vals[day] < (m_vals[day - 1] - 3 * s_vals[day - 1]):
results.append(0.33)
elif (m_vals[day - 1] - 3 * s_vals[day - 1]) <= d_vals[day] <= \
(m_vals[day - 1] + 3 * s_vals[day - 1]):
results.append(0.66)
else:
results.append(1.0)
else:
results = [0.0, ]
try:
median = np.median(in_data)
stdev = np.std(in_data)
if in_data.values[-1] < (median - 3 * stdev):
results.append(0.33)
elif (median - 3 * stdev) <= in_data.values[-1] <= (
median + 3 * stdev):
results.append(0.66)
else:
results.append(1.0)
except TypeError:
results.append(None)
return results
def _generate_trending_traces(in_data, period, moving_win_size=10,
fill_missing=True, use_first=False,
show_moving_median=True, name="", color=""):
"""Generate the trending traces:
- samples,
- moving median (trending plot)
- outliers, regress, progress
:param in_data: Full data set.
:param period: Sampling period.
:param moving_win_size: Window size.
:param fill_missing: If the chosen sample is missing in the full set, its
nearest neighbour is used.
:param use_first: Use the first sample even though it is not chosen.
:param show_moving_median: Show moving median (trending plot).
:param name: Name of the plot
:param color: Name of the color for the plot.
:type in_data: OrderedDict
:type period: int
:type moving_win_size: int
:type fill_missing: bool
:type use_first: bool
:type show_moving_median: bool
:type name: str
:type color: str
:returns: Generated traces (list) and the evaluated result (float).
:rtype: tuple(traces, result)
"""
if period > 1:
in_data = _select_data(in_data, period,
fill_missing=fill_missing,
use_first=use_first)
data_x = [key for key in in_data.keys()]
data_y = [val for val in in_data.values()]
data_pd = pd.Series(data_y, index=data_x)
t_data, outliers = find_outliers(data_pd)
results = _evaluate_results(data_pd, t_data, window=moving_win_size)
anomalies = pd.Series()
anomalies_res = list()
for idx, item in enumerate(in_data.items()):
item_pd = pd.Series([item[1], ], index=[item[0], ])
if item[0] in outliers.keys():
anomalies = anomalies.append(item_pd)
anomalies_res.append(0.0)
elif results[idx] in (0.33, 1.0):
anomalies = anomalies.append(item_pd)
anomalies_res.append(results[idx])
anomalies_res.extend([0.0, 0.33, 0.66, 1.0])
# Create traces
color_scale = [[0.00, "grey"],
[0.25, "grey"],
[0.25, "red"],
[0.50, "red"],
[0.50, "white"],
[0.75, "white"],
[0.75, "green"],
[1.00, "green"]]
trace_samples = plgo.Scatter(
x=data_x,
y=data_y,
mode='markers',
line={
"width": 1
},
name="{name}-thput".format(name=name),
marker={
"size": 5,
"color": color,
"symbol": "circle",
},
)
traces = [trace_samples, ]
trace_anomalies = plgo.Scatter(
x=anomalies.keys(),
y=anomalies.values,
mode='markers',
hoverinfo="none",
showlegend=False,
legendgroup=name,
name="{name}: outliers".format(name=name),
marker={
"size": 15,
"symbol": "circle-open",
"color": anomalies_res,
"colorscale": color_scale,
"showscale": True,
"line": {
"width": 2
},
"colorbar": {
"y": 0.5,
"len": 0.8,
"title": "Circles Marking Data Classification",
"titleside": 'right',
"titlefont": {
"size": 14
},
"tickmode": 'array',
"tickvals": [0.125, 0.375, 0.625, 0.875],
"ticktext": ["Outlier", "Regression", "Normal", "Progression"],
"ticks": "",
"ticklen": 0,
"tickangle": -90,
"thickness": 10
}
}
)
traces.append(trace_anomalies)
if show_moving_median:
data_mean_y = pd.Series(data_y).rolling(
window=moving_win_size, min_periods=2).median()
trace_median = plgo.Scatter(
x=data_x,
y=data_mean_y,
mode='lines',
line={
"shape": "spline",
"width": 1,
"color": color,
},
name='{name}-trend'.format(name=name)
)
traces.append(trace_median)
return traces, results[-1]
def _generate_chart(traces, layout, file_name):
"""Generates the whole chart using pre-generated traces.
:param traces: Traces for the chart.
:param layout: Layout of the chart.
:param file_name: File name for the generated chart.
:type traces: list
:type layout: dict
:type file_name: str
"""
# Create plot
logging.info(" Writing the file '{0}' ...".format(file_name))
plpl = plgo.Figure(data=traces, layout=layout)
try:
ploff.plot(plpl, show_link=False, auto_open=False, filename=file_name)
except plerr.PlotlyEmptyDataError:
logging.warning(" No data for the plot. Skipped.")
def _generate_all_charts(spec, input_data):
"""Generate all charts specified in the specification file.
:param spec: Specification.
:param input_data: Full data set.
:type spec: Specification
:type input_data: InputData
"""
builds = spec.cpta["data"].values()[0]
job_name = spec.cpta["data"].keys()[0]
builds_lst = [str(build) for build in range(builds[0], builds[-1] + 1)]
# Get "build ID": "date" dict:
build_dates = dict()
for build in builds_lst:
try:
build_dates[build] = \
input_data.metadata(job_name, build)["generated"][:14]
except KeyError:
pass
# Create the header:
csv_table = list()
header = "Build Number:," + ",".join(builds_lst) + '\n'
csv_table.append(header)
header = "Build Date:," + ",".join(build_dates.values()) + '\n'
csv_table.append(header)
results = list()
for chart in spec.cpta["plots"]:
logging.info(" Generating the chart '{0}' ...".
format(chart.get("title", "")))
# Transform the data
data = input_data.filter_data(chart, continue_on_error=True)
if data is None:
logging.error("No data.")
return
chart_data = dict()
for job in data:
for idx, build in job.items():
for test_name, test in build.items():
if chart_data.get(test_name, None) is None:
chart_data[test_name] = OrderedDict()
try:
chart_data[test_name][int(idx)] = \
test["result"]["throughput"]
except (KeyError, TypeError):
pass
# Add items to the csv table:
for tst_name, tst_data in chart_data.items():
tst_lst = list()
for build in builds_lst:
item = tst_data.get(int(build), '')
tst_lst.append(str(item) if item else '')
csv_table.append("{0},".format(tst_name) + ",".join(tst_lst) + '\n')
for period in chart["periods"]:
# Generate traces:
traces = list()
win_size = 10 if period == 1 else 5 if period < 20 else 3
idx = 0
for test_name, test_data in chart_data.items():
if not test_data:
logging.warning("No data for the test '{0}'".
format(test_name))
continue
test_name = test_name.split('.')[-1]
trace, result = _generate_trending_traces(
test_data,
period=period,
moving_win_size=win_size,
fill_missing=True,
use_first=False,
name='-'.join(test_name.split('-')[3:-1]),
color=COLORS[idx])
traces.extend(trace)
results.append(result)
idx += 1
# Generate the chart:
chart["layout"]["xaxis"]["title"] = \
chart["layout"]["xaxis"]["title"].format(job=job_name)
_generate_chart(traces,
chart["layout"],
file_name="{0}-{1}-{2}{3}".format(
spec.cpta["output-file"],
chart["output-file-name"],
period,
spec.cpta["output-file-type"]))
logging.info(" Done.")
# Write the tables:
file_name = spec.cpta["output-file"] + "-trending"
with open("{0}.csv".format(file_name), 'w') as file_handler:
file_handler.writelines(csv_table)
txt_table = None
with open("{0}.csv".format(file_name), 'rb') as csv_file:
csv_content = csv.reader(csv_file, delimiter=',', quotechar='"')
line_nr = 0
for row in csv_content:
if txt_table is None:
txt_table = prettytable.PrettyTable(row)
else:
if line_nr > 1:
for idx, item in enumerate(row):
try:
row[idx] = str(round(float(item) / 1000000, 2))
except ValueError:
pass
txt_table.add_row(row)
line_nr += 1
txt_table.align["Build Number:"] = "l"
with open("{0}.txt".format(file_name), "w") as txt_file:
txt_file.write(str(txt_table))
# Evaluate result:
result = "PASS"
for item in results:
if item is None:
result = "FAIL"
break
if item == 0.66 and result == "PASS":
result = "PASS"
elif item == 0.33 or item == 0.0:
result = "FAIL"
logging.info("Partial results: {0}".format(results))
logging.info("Result: {0}".format(result))
return result