# 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."""Algorithms to generate plots."""importloggingimportpandasaspdimportplotly.offlineasploffimportplotly.graph_objsasplgofromplotly.exceptionsimportPlotlyErrorfromcollectionsimportOrderedDictfromcopyimportdeepcopyfromutilsimportmeanCOLORS=["SkyBlue","Olive","Purple","Coral","Indigo","Pink","Chocolate","Brown","Magenta","Cyan","Orange","Black","Violet","Blue","Yellow","BurlyWood","CadetBlue","Crimson","DarkBlue","DarkCyan","DarkGreen","Green","GoldenRod","LightGreen","LightSeaGreen","LightSkyBlue","Maroon","MediumSeaGreen","SeaGreen","LightSlateGrey"]defgenerate_plots(spec,data):"""Generate all plots specified in the specification file. :param spec: Specification read from the specification file. :param data: Data to process. :type spec: Specification :type data: InputData """logging.info("Generating the plots ...")forindex,plotinenumerate(spec.plots):try:logging.info(" Plot nr {0}: {1}".format(index+1,plot.get("title","")))plot["limits"]=spec.configuration["limits"]eval(plot["algorithm"])(plot,data)logging.info(" Done.")exceptNameErroraserr:logging.error("Probably algorithm '{alg}' is not defined: {err}".format(alg=plot["algorithm"],err=repr(err)))logging.info("Done.")defplot_performance_box(plot,input_data):"""Generate the plot(s) with algorithm: plot_performance_box specified in the specification file. :param plot: Plot to generate. :param input_data: Data to process. :type plot: pandas.Series :type input_data: InputData """# Transform the dataplot_title=plot.get("title","")logging.info(" Creating the data set for the {0} '{1}'.".format(plot.get("type",""),plot_title))data=input_data.filter_data(plot)ifdataisNone:logging.error("No data.")return# Prepare the data for the ploty_vals=dict()y_tags=dict()forjobindata:forbuildinjob:fortestinbuild:ify_vals.get(test["parent"],None)isNone:y_vals[test["parent"]]=list()y_tags[test["parent"]]=test.get("tags",None)try:iftest["type"]in("NDRPDR",):if"-pdr"inplot_title.lower():y_vals[test["parent"]].\
append(test["throughput"]["PDR"]["LOWER"])elif"-ndr"inplot_title.lower():y_vals[test["parent"]]. \
append(test["throughput"]["NDR"]["LOWER"])else:continueelse:continueexcept(KeyError,TypeError):y_vals[test["parent"]].append(None)# Sort the testsorder=plot.get("sort",None)iforderandy_tags:y_sorted=OrderedDict()y_tags_l={s:[t.lower()fortints]fors,tsiny_tags.items()}fortaginorder:logging.debug(tag)forsuite,tagsiny_tags_l.items():if"not "intag:tag=tag.split(" ")[-1]iftag.lower()intags:continueelse:iftag.lower()notintags:continuetry:y_sorted[suite]=y_vals.pop(suite)y_tags_l.pop(suite)logging.debug(suite)exceptKeyErroraserr:logging.error("Not found: {0}".format(repr(err)))finally:breakelse:y_sorted=y_vals# Add None to the lists with missing datamax_len=0nr_of_samples=list()forvaliny_sorted.values():iflen(val)>max_len:max_len=len(val)nr_of_samples.append(len(val))forkey,valiny_sorted.items():iflen(val)<max_len:val.extend([Nonefor_inrange(max_len-len(val))])# Add plot tracestraces=list()df=pd.DataFrame(y_sorted)df.head()y_max=list()fori,colinenumerate(df.columns):name="{0}. {1}".format(i+1,col.lower().replace('-ndrpdr',''))iflen(name)>60:name_lst=name.split('-')name=""split_name=Trueforsegmentinname_lst:if(len(name)+len(segment)+1)>60andsplit_name:name+="<br> "split_name=Falsename+=segment+'-'name=name[:-1]name="{name} ({samples} run{plural})".\
format(name=name,samples=nr_of_samples[i],plural='s'ifnr_of_samples[i]>1else'')logging.debug(name)traces.append(plgo.Box(x=[str(i+1)+'.']*len(df[col]),y=[y/1000000ifyelseNoneforyindf[col]],name=name,**plot["traces"]))try:val_max=max(df[col])exceptValueErroraserr:logging.error(repr(err))continueifval_max:y_max.append(int(val_max/1000000)+1)try:# Create plotlayout=deepcopy(plot["layout"])iflayout.get("title",None):layout["title"]="<b>Packet Throughput:</b> {0}". \
format(layout["title"])ify_max:layout["yaxis"]["range"]=[0,max(y_max)]plpl=plgo.Figure(data=traces,layout=layout)# Export Plotlogging.info(" Writing file '{0}{1}'.".format(plot["output-file"],plot["output-file-type"]))ploff.plot(plpl,show_link=False,auto_open=False,filename='{0}{1}'.format(plot["output-file"],plot["output-file-type"]))exceptPlotlyErroraserr:logging.error(" Finished with error: {}".format(repr(err).replace("\n"," ")))returndefplot_latency_error_bars(plot,input_data):"""Generate the plot(s) with algorithm: plot_latency_error_bars specified in the specification file. :param plot: Plot to generate. :param input_data: Data to process. :type plot: pandas.Series :type input_data: InputData """# Transform the dataplot_title=plot.get("title","")logging.info(" Creating the data set for the {0} '{1}'.".format(plot.get("type",""),plot_title))data=input_data.filter_data(plot)ifdataisNone:logging.error("No data.")return# Prepare the data for the ploty_tmp_vals=dict()y_tags=dict()forjobindata:forbuildinjob:fortestinbuild:try:logging.debug("test['latency']: {0}\n".format(test["latency"]))exceptValueErroraserr:logging.warning(repr(err))ify_tmp_vals.get(test["parent"],None)isNone:y_tmp_vals[test["parent"]]=[list(),# direction1, minlist(),# direction1, avglist(),# direction1, maxlist(),# direction2, minlist(),# direction2, avglist()# direction2, max]y_tags[test["parent"]]=test.get("tags",None)try:iftest["type"]in("NDRPDR",):if"-pdr"inplot_title.lower():ttype="PDR"elif"-ndr"inplot_title.lower():ttype="NDR"else:logging.warning("Invalid test type: {0}".format(test["type"]))continuey_tmp_vals[test["parent"]][0].append(test["latency"][ttype]["direction1"]["min"])y_tmp_vals[test["parent"]][1].append(test["latency"][ttype]["direction1"]["avg"])y_tmp_vals[test["parent"]][2].append(test["latency"][ttype]["direction1"]["max"])y_tmp_vals[test["parent"]][3].append(test["latency"][ttype]["direction2"]["min"])y_tmp_vals[test["parent"]][4].append(test["latency"][ttype]["direction2"]["avg"])y_tmp_vals[test["parent"]][5].append(test["latency"][ttype]["direction2"]["max"])else:logging.warning("Invalid test type: {0}".format(test["type"]))continueexcept(KeyError,TypeError)aserr:logging.warning(repr(err))logging.debug("y_tmp_vals: {0}\n".format(y_tmp_vals))# Sort the testsorder=plot.get("sort",None)iforderandy_tags:y_sorted=OrderedDict()y_tags_l={s:[t.lower()fortints]fors,tsiny_tags.items()}fortaginorder:logging.debug(tag)forsuite,tagsiny_tags_l.items():if"not "intag:tag=tag.split(" ")[-1]iftag.lower()intags:continueelse:iftag.lower()notintags:continuetry:y_sorted[suite]=y_tmp_vals.pop(suite)y_tags_l.pop(suite)logging.debug(suite)exceptKeyErroraserr:logging.error("Not found: {0}".format(repr(err)))finally:breakelse:y_sorted=y_tmp_valslogging.debug("y_sorted: {0}\n".format(y_sorted))x_vals=list()y_vals=list()y_mins=list()y_maxs=list()nr_of_samples=list()forkey,valiny_sorted.items():name="-".join(key.split("-")[1:-1])iflen(name)>60:name_lst=name.split('-')name=""split_name=Trueforsegmentinname_lst:if(len(name)+len(segment)+1)>60andsplit_name:name+="<br> "split_name=Falsename+=segment+'-'name=name[:-1]x_vals.append(name)# dir 1y_vals.append(mean(val[1])ifval[1]elseNone)y_mins.append(mean(val[0])ifval[0]elseNone)y_maxs.append(mean(val[2])ifval[2]elseNone)nr_of_samples.append(len(val[1])ifval[1]else0)x_vals.append(name)# dir 2y_vals.append(mean(val[4])ifval[4]elseNone)y_mins.append(mean(val[3])ifval[3]elseNone)y_maxs.append(mean(val[5])ifval[5]elseNone)nr_of_samples.append(len(val[3])ifval[3]else0)logging.debug("x_vals :{0}\n".format(x_vals))logging.debug("y_vals :{0}\n".format(y_vals))logging.debug("y_mins :{0}\n".format(y_mins))logging.debug("y_maxs :{0}\n".format(y_maxs))logging.debug("nr_of_samples :{0}\n".format(nr_of_samples))traces=list()annotations=list()foridxinrange(len(x_vals)):ifnotbool(int(idx%2)):direction="West-East"else:direction="East-West"hovertext=("Test: {test}<br>""Direction: {dir}<br>""No. of Runs: {nr}<br>".format(test=x_vals[idx],dir=direction,nr=nr_of_samples[idx]))ifisinstance(y_maxs[idx],float):hovertext+="Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])ifisinstance(y_vals[idx],float):hovertext+="Mean: {avg:.2f}uSec<br>".format(avg=y_vals[idx])ifisinstance(y_mins[idx],float):hovertext+="Min: {min:.2f}uSec".format(min=y_mins[idx])ifisinstance(y_maxs[idx],float)andisinstance(y_vals[idx],float):array=[y_maxs[idx]-y_vals[idx],]else:array=[None,]ifisinstance(y_mins[idx],float)andisinstance(y_vals[idx],float):arrayminus=[y_vals[idx]-y_mins[idx],]else:arrayminus=[None,]logging.debug("y_vals[{1}] :{0}\n".format(y_vals[idx],idx))logging.debug("array :{0}\n".format(array))logging.debug("arrayminus :{0}\n".format(arrayminus))traces.append(plgo.Scatter(x=[idx,],y=[y_vals[idx],],name=x_vals[idx],legendgroup=x_vals[idx],showlegend=bool(int(idx%2)),mode="markers",error_y=dict(type='data',symmetric=False,array=array,arrayminus=arrayminus,color=COLORS[int(idx/2)]),marker=dict(size=10,color=COLORS[int(idx/2)],),text=hovertext,hoverinfo="text",))annotations.append(dict(x=idx,y=0,xref="x",yref="y",xanchor="center",yanchor="top",text="E-W"ifbool(int(idx%2))else"W-E",font=dict(size=16,),align="center",showarrow=False))try:# Create plotlogging.info(" Writing file '{0}{1}'.".format(plot["output-file"],plot["output-file-type"]))layout=deepcopy(plot["layout"])iflayout.get("title",None):layout["title"]="<b>Packet Latency:</b> {0}".\
format(layout["title"])layout["annotations"]=annotationsplpl=plgo.Figure(data=traces,layout=layout)# Export Plotploff.plot(plpl,show_link=False,auto_open=False,filename='{0}{1}'.format(plot["output-file"],plot["output-file-type"]))exceptPlotlyErroraserr:logging.error(" Finished with error: {}".format(str(err).replace("\n"," ")))returndefplot_throughput_speedup_analysis(plot,input_data):"""Generate the plot(s) with algorithm: plot_throughput_speedup_analysis specified in the specification file. :param plot: Plot to generate. :param input_data: Data to process. :type plot: pandas.Series :type input_data: InputData """# Transform the dataplot_title=plot.get("title","")logging.info(" Creating the data set for the {0} '{1}'.".format(plot.get("type",""),plot_title))data=input_data.filter_data(plot)ifdataisNone:logging.error("No data.")returny_vals=dict()y_tags=dict()forjobindata:forbuildinjob:fortestinbuild:ify_vals.get(test["parent"],None)isNone:y_vals[test["parent"]]={"1":list(),"2":list(),"4":list()}y_tags[test["parent"]]=test.get("tags",None)try:iftest["type"]in("NDRPDR",):if"-pdr"inplot_title.lower():ttype="PDR"elif"-ndr"inplot_title.lower():ttype="NDR"else:continueif"1C"intest["tags"]:y_vals[test["parent"]]["1"]. \
append(test["throughput"][ttype]["LOWER"])elif"2C"intest["tags"]:y_vals[test["parent"]]["2"]. \
append(test["throughput"][ttype]["LOWER"])elif"4C"intest["tags"]:y_vals[test["parent"]]["4"]. \
append(test["throughput"][ttype]["LOWER"])except(KeyError,TypeError):passifnoty_vals:logging.warning("No data for the plot '{}'".format(plot.get("title","")))returny_1c_max=dict()fortest_name,test_valsiny_vals.items():forkey,test_valintest_vals.items():iftest_val:avg_val=sum(test_val)/len(test_val)y_vals[test_name][key]=(avg_val,len(test_val))ideal=avg_val/(int(key)*1000000.0)iftest_namenotiny_1c_maxorideal>y_1c_max[test_name]:y_1c_max[test_name]=idealvals=dict()y_max=list()nic_limit=0lnk_limit=0pci_limit=plot["limits"]["pci"]["pci-g3-x8"]fortest_name,test_valsiny_vals.items():try:iftest_vals["1"][1]:name="-".join(test_name.split('-')[1:-1])iflen(name)>60:name_lst=name.split('-')name=""split_name=Trueforsegmentinname_lst:if(len(name)+len(segment)+1)>60andsplit_name:name+="<br> "split_name=Falsename+=segment+'-'name=name[:-1]vals[name]=dict()y_val_1=test_vals["1"][0]/1000000.0y_val_2=test_vals["2"][0]/1000000.0iftest_vals["2"][0] \
elseNoney_val_4=test_vals["4"][0]/1000000.0iftest_vals["4"][0] \
elseNonevals[name]["val"]=[y_val_1,y_val_2,y_val_4]vals[name]["rel"]=[1.0,None,None]vals[name]["ideal"]=[y_1c_max[test_name],y_1c_max[test_name]*2,y_1c_max[test_name]*4]vals[name]["diff"]=[(y_val_1-y_1c_max[test_name])*100/y_val_1,None,None]vals[name]["count"]=[test_vals["1"][1],test_vals["2"][1],test_vals["4"][1]]try:val_max=max(max(vals[name]["val"],vals[name]["ideal"]))exceptValueErroraserr:logging.error(err)continueifval_max:y_max.append(int((val_max/10)+1)*10)ify_val_2:vals[name]["rel"][1]=round(y_val_2/y_val_1,2)vals[name]["diff"][1]= \
(y_val_2-vals[name]["ideal"][1])*100/y_val_2ify_val_4:vals[name]["rel"][2]=round(y_val_4/y_val_1,2)vals[name]["diff"][2]= \
(y_val_4-vals[name]["ideal"][2])*100/y_val_4exceptIndexErroraserr:logging.warning("No data for '{0}'".format(test_name))logging.warning(repr(err))# Limits:if"x520"intest_name:limit=plot["limits"]["nic"]["x520"]elif"x710"intest_name:limit=plot["limits"]["nic"]["x710"]elif"xxv710"intest_name:limit=plot["limits"]["nic"]["xxv710"]elif"xl710"intest_name:limit=plot["limits"]["nic"]["xl710"]else:limit=0iflimit>nic_limit:nic_limit=limitmul=2if"ge2p"intest_nameelse1if"10ge"intest_name:limit=plot["limits"]["link"]["10ge"]*mulelif"25ge"intest_name:limit=plot["limits"]["link"]["25ge"]*mulelif"40ge"intest_name:limit=plot["limits"]["link"]["40ge"]*mulelif"100ge"intest_name:limit=plot["limits"]["link"]["100ge"]*mulelse:limit=0iflimit>lnk_limit:lnk_limit=limit# Sort the testsorder=plot.get("sort",None)iforderandy_tags:y_sorted=OrderedDict()y_tags_l={s:[t.lower()fortints]fors,tsiny_tags.items()}fortaginorder:fortest,tagsiny_tags_l.items():iftag.lower()intags:name="-".join(test.split('-')[1:-1])try:y_sorted[name]=vals.pop(name)y_tags_l.pop(test)exceptKeyErroraserr:logging.error("Not found: {0}".format(err))finally:breakelse:y_sorted=valstraces=list()annotations=list()x_vals=[1,2,4]# Limits:try:threshold=1.1*max(y_max)# 10%exceptValueErroraserr:logging.error(err)returnnic_limit/=1000000.0ifnic_limit<threshold:traces.append(plgo.Scatter(x=x_vals,y=[nic_limit,]*len(x_vals),name="NIC: {0:.2f}Mpps".format(nic_limit),showlegend=False,mode="lines",line=dict(dash="dot",color=COLORS[-1],width=1),hoverinfo="none"))annotations.append(dict(x=1,y=nic_limit,xref="x",yref="y",xanchor="left",yanchor="bottom",text="NIC: {0:.2f}Mpps".format(nic_limit),font=dict(size=14,color=COLORS[-1],),align="left",showarrow=False))y_max.append(int((nic_limit/10)+1)*10)lnk_limit/=1000000.0iflnk_limit<threshold:traces.append(plgo.Scatter(x=x_vals,y=[lnk_limit,]*len(x_vals),name="Link: {0:.2f}Mpps".format(lnk_limit),showlegend=False,mode="lines",line=dict(dash="dot",color=COLORS[-2],width=1),hoverinfo="none"))annotations.append(dict(x=1,y=lnk_limit,xref="x",yref="y",xanchor="left",yanchor="bottom",text="Link: {0:.2f}Mpps".format(lnk_limit),font=dict(size=14,color=COLORS[-2],),align="left",showarrow=False))y_max.append(int((lnk_limit/10)+1)*10)pci_limit/=1000000.0ifpci_limit<threshold:traces.append(plgo.Scatter(x=x_vals,y=[pci_limit,]*len(x_vals),name="PCIe: {0:.2f}Mpps".format(pci_limit),showlegend=False,mode="lines",line=dict(dash="dot",color=COLORS[-3],width=1),hoverinfo="none"))annotations.append(dict(x=1,y=pci_limit,xref="x",yref="y",xanchor="left",yanchor="bottom",text="PCIe: {0:.2f}Mpps".format(pci_limit),font=dict(size=14,color=COLORS[-3],),align="left",showarrow=False))y_max.append(int((pci_limit/10)+1)*10)# Perfect and measured:cidx=0forname,valiny_sorted.iteritems():hovertext=list()try:foridxinrange(len(val["val"])):htext=""ifisinstance(val["val"][idx],float):htext+="Mean: {0:.2f}Mpps<br>" \
"No. of Runs: {1}<br>".format(val["val"][idx],val["count"][idx])ifisinstance(val["diff"][idx],float):htext+="Diff: {0:.0f}%<br>".format(round(val["diff"][idx]))ifisinstance(val["rel"][idx],float):htext+="Speedup: {0:.2f}".format(val["rel"][idx])hovertext.append(htext)traces.append(plgo.Scatter(x=x_vals,y=val["val"],name=name,legendgroup=name,mode="lines+markers",line=dict(color=COLORS[cidx],width=2),marker=dict(symbol="circle",size=10),text=hovertext,hoverinfo="text+name"))traces.append(plgo.Scatter(x=x_vals,y=val["ideal"],name="{0} perfect".format(name),legendgroup=name,showlegend=False,mode="lines",line=dict(color=COLORS[cidx],width=2,dash="dash"),text=["Perfect: {0:.2f}Mpps".format(y)foryinval["ideal"]],hoverinfo="text"))cidx+=1except(IndexError,ValueError,KeyError)aserr:logging.warning("No data for '{0}'".format(name))logging.warning(repr(err))try:# Create plotlogging.info(" Writing file '{0}{1}'.".format(plot["output-file"],plot["output-file-type"]))layout=deepcopy(plot["layout"])iflayout.get("title",None):layout["title"]="<b>Speedup Multi-core:</b> {0}". \
format(layout["title"])layout["annotations"].extend(annotations)plpl=plgo.Figure(data=traces,layout=layout)# Export Plotploff.plot(plpl,show_link=False,auto_open=False,filename='{0}{1}'.format(plot["output-file"],plot["output-file-type"]))exceptPlotlyErroraserr:logging.error(" Finished with error: {}".format(str(err).replace("\n"," ")))returndefplot_http_server_performance_box(plot,input_data):"""Generate the plot(s) with algorithm: plot_http_server_performance_box specified in the specification file. :param plot: Plot to generate. :param input_data: Data to process. :type plot: pandas.Series :type input_data: InputData """# Transform the datalogging.info(" Creating the data set for the {0} '{1}'.".format(plot.get("type",""),plot.get("title","")))data=input_data.filter_data(plot)ifdataisNone:logging.error("No data.")return# Prepare the data for the ploty_vals=dict()forjobindata:forbuildinjob:fortestinbuild:ify_vals.get(test["name"],None)isNone:y_vals[test["name"]]=list()try:y_vals[test["name"]].append(test["result"])except(KeyError,TypeError):y_vals[test["name"]].append(None)# Add None to the lists with missing datamax_len=0nr_of_samples=list()forvaliny_vals.values():iflen(val)>max_len:max_len=len(val)nr_of_samples.append(len(val))forkey,valiny_vals.items():iflen(val)<max_len:val.extend([Nonefor_inrange(max_len-len(val))])# Add plot tracestraces=list()df=pd.DataFrame(y_vals)df.head()fori,colinenumerate(df.columns):name="{0}. {1}".format(i+1,col.lower().replace('-ndrpdr',''))iflen(name)>60:name_lst=name.split('-')name=""split_name=Trueforsegmentinname_lst:if(len(name)+len(segment)+1)>60andsplit_name:name+="<br> "split_name=Falsename+=segment+'-'name=name[:-1]name="{name} ({samples} run{plural})".\
format(name=name,samples=nr_of_samples[i],plural='s'ifnr_of_samples[i]>1else'')traces.append(plgo.Box(x=[str(i+1)+'.']*len(df[col]),y=df[col],name=name,**plot["traces"]))try:# Create plotplpl=plgo.Figure(data=traces,layout=plot["layout"])# Export Plotlogging.info(" Writing file '{0}{1}'.".format(plot["output-file"],plot["output-file-type"]))ploff.plot(plpl,show_link=False,auto_open=False,filename='{0}{1}'.format(plot["output-file"],plot["output-file-type"]))exceptPlotlyErroraserr:logging.error(" Finished with error: {}".format(str(err).replace("\n"," ")))return