在拿到性能数据后需要将数据利用起来,下面对性能数据进行分析
实现:如果性能达到设定的阈值,那么这段时间执行的用例就是性能较差的用例
确定阈值
首先确定一个阈值来当做性能的告警值,暂定为以下算法
代码语言:javascript复制# threshold 阈值
# average 平均值
# max 最大值
threshold = average (max - average) * 0.8
计算各项值
在上一章已经拿到了性能数据,然后把他们插入到数据库中
由于平台是Java的所以用Java编写
构造一个HashMap
来存放全部的数据
Map<String, Map<String, List<Double>>> perfSummaryData = new HashMap<>();
最终返回的数据类型为:
代码语言:javascript复制{
"cpu":{"system":[,,121.43243243243244,314.6864864864865],"idle":[,,537.3716216216217,716.2743243243243],"pic_cpu":[,3.2,66.42430555555556,311.6848611111111],"device_cpu_rate":[,,245.6418918918919,772.3283783783784],"user":[,,124.20945945945945,546.4418918918919]},
"mem":{"free_ram":[2707.73,1720.11,2044.2376923076922,2575.0315384615387],"pid_pss":[452.18,231.72,313.105,424.365],"total_ram":[5478.93,5478.93,5478.929999999999,5478.93]},
"other":null,
"power":{"current":[,,,],"tempreture":[36.8,29.9,33.69638554216868,36.17927710843373],"voltage":[4.34,4.13,4.284096385542169,4.328819277108433]},
"pss":{"pssList":[],"java_heap":[],"pss_system":[],"native_heap":[]},
"thread":{"threadList":[,,191.46308724832215,255.89261744966444]},
"traffic":{"device_transport":[6283.15,433.69,3777.436774193548,5782.00735483871],"device_receive":[167860.62,11597.64,106851.58806451612,155658.81361290324],"device_total":[174143.77,12031.33,110629.02451612902,161440.8209032258],"pid_tx":[6283.15,474.8,3779.061612903226,5782.332322580645],"pid_total":[174143.71,13349.69,110675.72096774195,161450.11219354838],"pid_rx":[167860.55,12874.89,106896.65838709677,155667.77167741934]},
"version":"微医用户版4.6.5-内部版本号:268-灰度-待发布-创建时间:2022-06-23 21:18:10-最近修改时间:2022-06-23 21:18:10"
}
依次解析每个版本每次执行的性能数据然后根据性能类型放到对应的List中
然后计算一下最大、最小、平均、阈值
代码语言:javascript复制floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).max().orElse(0.0));
floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).min().orElse(0.0));
floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).average().orElse(0.0));
floats.add(floats.get() 0.8 * (floats.get() - floats.get())); // 阈值
完整代码
代码语言:javascript复制@Override
public List<UiPerfSummaryDo> getPerfDataSummary(UiPerfPageQuery pageQuery) {
List<UiPerfDo> perfData = uiReportMapper.getPerfData(pageQuery);
Map<String, Map<String, List<Double>>> perfSummaryData = new HashMap<>();
String[] cpuKeys = { "device_cpu_rate", "user", "system", "idle", "pic_cpu" };
String[] memKeys = { "total_ram", "free_ram", "pid_pss" };
String[] powerKeys = { "voltage", "tempreture", "current" };
String[] threadKeys = { "threadList" };
String[] pssKeys = { "pssList", "java_heap", "native_heap", "pss_system" };
String[] trafficKeys = { "device_total", "device_receive", "device_transport", "pid_rx", "pid_tx",
"pid_total" };
String[][] allKeys = { cpuKeys, memKeys, powerKeys, threadKeys, pssKeys, trafficKeys };
perfData.forEach(item -> {
String version = item.getVersion();
if (!perfSummaryData.containsKey(version)) {
HashMap<String, List<Double>> perfVersion = new HashMap<>();
for (String[] keys : allKeys) {
for (String key : keys) {
perfVersion.put(key, new ArrayList<>());
}
}
perfSummaryData.put(version, perfVersion);
}
Map<String, List<Double>> stringListMap = perfSummaryData.get(version);
List<String> cpus = JSONArray.parseArray(item.getCpu(), String.class);
List<String> mems = JSONArray.parseArray(item.getMem(), String.class);
List<String> powers = JSONArray.parseArray(item.getPower(), String.class);
List<String> threads = JSONArray.parseArray(item.getThread(), String.class);
List<String> psss = JSONArray.parseArray(item.getPss(), String.class);
List<String> traffics = JSONArray.parseArray(item.getTraffic(), String.class);
cpus.forEach(cpu -> {
List<String> oneCpuInfo = JSONArray.parseArray(cpu, String.class);
if (!oneCpuInfo.isEmpty()) {
for (int i = ; i < ; i ) {
List<Double> datalist = stringListMap.get(cpuKeys[i - ]);
datalist.add(Double.parseDouble(oneCpuInfo.get(i)));
}
}
});
mems.forEach(mem -> {
List<String> oneMemInfo = JSONArray.parseArray(mem, String.class);
if (!oneMemInfo.isEmpty()) {
for (int i = ; i < ; i ) {
List<Double> datalist = stringListMap.get(memKeys[i - ]);
datalist.add(Double.parseDouble(oneMemInfo.get(i)));
}
}
});
powers.forEach(power -> {
List<String> onePowerInfo = JSONArray.parseArray(power, String.class);
if (!onePowerInfo.isEmpty()) {
for (int i = ; i < ; i ) {
List<Double> datalist = stringListMap.get(powerKeys[i - ]);
datalist.add(Double.parseDouble(onePowerInfo.get(i)));
}
}
});
threads.forEach(thread -> {
List<String> oneThreadInfo = JSONArray.parseArray(thread, String.class);
if (!oneThreadInfo.isEmpty()) {
for (int i = ; i < ; i ) {
List<Double> datalist = stringListMap.get(threadKeys[i - ]);
datalist.add(Double.parseDouble(oneThreadInfo.get(i)));
}
}
});
psss.forEach(pss -> {
List<String> onePssInfo = JSONArray.parseArray(pss, String.class);
if (!onePssInfo.isEmpty()) {
for (int i = ; i < ; i ) {
List<Double> datalist = stringListMap.get(pssKeys[i - ]);
datalist.add(Double.parseDouble(onePssInfo.get(i)));
}
}
});
traffics.forEach(traffic -> {
List<String> oneTrafficInfo = JSONArray.parseArray(traffic, String.class);
if (!oneTrafficInfo.isEmpty()) {
for (int i = ; i < ; i ) {
List<Double> datalist = stringListMap.get(trafficKeys[i - ]);
datalist.add(Double.parseDouble(oneTrafficInfo.get(i)));
}
}
});
});
List<UiPerfSummaryDo> uiPerfSummaryDos = new ArrayList<>();
List<Double> floats;
for (String key : perfSummaryData.keySet()) {
UiPerfSummaryDo uiPerfSummaryDo = new UiPerfSummaryDo();
Map<String, List<Double>> stringListMap = perfSummaryData.get(key);
uiPerfSummaryDo.setVersion(key);
for (String[] all_key : allKeys) {
HashMap<String, List<Double>> dataDict = new HashMap<>();
for (String key1 : all_key) {
floats = new ArrayList<>();
List<Double> floats1 = stringListMap.get(key1);
if (!floats1.isEmpty()) {
floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).max().orElse(0.0));
floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).min().orElse(0.0));
floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).average().orElse(0.0));
floats.add(floats.get() 0.8 * (floats.get() - floats.get())); // 阈值
}
dataDict.put(key1, floats);
}
if (Arrays.equals(all_key, cpuKeys)) {
uiPerfSummaryDo.setCpu(dataDict);
}
if (Arrays.equals(all_key, memKeys)) {
uiPerfSummaryDo.setMem(dataDict);
}
if (Arrays.equals(all_key, powerKeys)) {
uiPerfSummaryDo.setPower(dataDict);
}
if (Arrays.equals(all_key, threadKeys)) {
uiPerfSummaryDo.setThread(dataDict);
}
if (Arrays.equals(all_key, pssKeys)) {
uiPerfSummaryDo.setPss(dataDict);
}
if (Arrays.equals(all_key, trafficKeys)) {
uiPerfSummaryDo.setTraffic(dataDict);
}
}
uiPerfSummaryDos.add(uiPerfSummaryDo);
}
return uiPerfSummaryDos;
}
解析Allure报告
拿到一个用例的开始时间和结束时间,方便确定用例执行的时间范围
在13.UI自动化测试框架搭建-处理Allure报告数据中有提到如何拿到allure的内容
代码语言:javascript复制data = {
"fullName": full_name,
"status": json_data.get("status"),
"labels": labels,
"start": json_data.get("start", ),
"stop": json_data.get("stop", ),
"duration": json_data.get("stop", ) - json_data.get("start", ),
"parameters": parameters,
"statusDetails": statusDetails1,
"kano_url": ",".join(kano_url),
"steps": ";".join(steps),
"desc": ";"
}
添加了
代码语言:javascript复制"start": json_data.get("start", )
"stop": json_data.get("stop", )
"desc": ";"
现在将用例执行的情况写入到一份csv文件中,方便与其他的性能数据进行比对
代码语言:javascript复制# 拼接csv文件路径
casesfile = os.path.join(PERF_PATH, 'cases.csv')
# csv文件头部
title = ["datetime", "flag"] list(allure_results[].keys())
with open(casesfile, "a ", encoding="utf-8") as f:
# 写入头部
csv.writer(f, lineterminator='n').writerow(title)
for i in allure_results:
allure_data1 = [timeoperator.get_localtime(i['start'], "%Y-%m-%d %H-%M-%S"), True] list(
i.values())
allure_data2 = [timeoperator.get_localtime(i['stop'], "%Y-%m-%d %H-%M-%S"), False] list(
i.values())
csv.writer(f, lineterminator='n').writerow(allure_data1)
csv.writer(f, lineterminator='n').writerow(allure_data2)
使得datetime等于start和stop,写入两遍,再使用flag来标记方便后面去除掉
这样就得到一份按照时间排序的用例执行结果了
代码语言:javascript复制datetime,flag,fullName,status,labels,start,stop,duration,parameters,statusDetails,kano_url,steps,desc
-08-01 -04-44,True,src.cases_android.wy.test_health.TestMedicationReminder#test_close_medication_reminder,passed,微医APP_健康_用药提醒-关闭用药提醒(@钟鑫),1659333884508,1659333917256,32748,,"{'message': '', 'trace': ''}",,1-查看提醒状态;1.1-查看「用药提醒_已开启提醒」是否存在;2-关闭用药提醒;2.1-点击「用药提醒_提醒滑块1」;3-点击确认;3.1-点击「通用_确认关闭」;4-查看提醒状态;4.1-查看「用药提醒_已关闭提醒」是否存在;4.2-查看「用药提醒_已关闭提醒1」是否存在;5-打开用药提醒;5.1-点击「用药提醒_提醒滑块2」;5.2-点击「用药提醒_已关闭提醒1」;6-查看提醒状态;6.1-查看「用药提醒_已开启提醒」是否存在,;
-08-01 -05-17,False,src.cases_android.wy.test_health.TestMedicationReminder#test_close_medication_reminder,passed,微医APP_健康_用药提醒-关闭用药提醒(@钟鑫),1659333884508,1659333917256,32748,,"{'message': '', 'trace': ''}",,1-查看提醒状态;1.1-查看「用药提醒_已开启提醒」是否存在;2-关闭用药提醒;2.1-点击「用药提醒_提醒滑块1」;3-点击确认;3.1-点击「通用_确认关闭」;4-查看提醒状态;4.1-查看「用药提醒_已关闭提醒」是否存在;4.2-查看「用药提醒_已关闭提醒1」是否存在;5-打开用药提醒;5.1-点击「用药提醒_提醒滑块2」;5.2-点击「用药提醒_已关闭提醒1」;6-查看提醒状态;6.1-查看「用药提醒_已开启提醒」是否存在,;
-08-01 -24-32,True,src.cases_android.wy.test_me.TestSetting#test_entrance_text,passed,微医APP_个人中心_设置-子项入口文案检查(@钟鑫),1659331472953,1659331473076,123,'settings_message',"{'message': '', 'trace': ''}",,1-查看是否存在我_设置-消息通知;1.1-查看「我_设置-消息通知」是否存在,;
-08-01 -24-33,False,src.cases_android.wy.test_me.TestSetting#test_entrance_text,passed,微医APP_个人中心_设置-子项入口文案检查(@钟鑫),1659331472953,1659331473076,123,'settings_message',"{'message': '', 'trace': ''}",,1-查看是否存在我_设置-消息通知;1.1-查看「我_设置-消息通知」是否存在,;
数据比对
读取用例执行结果
读取文件后将它的datetimes设置为datetime类型,方便后面的排序
代码语言:javascript复制def cases_handle(self, path=f"{PERF_PATH}/cases.csv"):
df = self.read_csv(path)
df['datetime'] = pd.to_datetime(df['datetime'], format="%Y-%m-%d %H-%M-%S")
df = df.sort_values("datetime", ascending=True)
return df
编写比较函数
代码语言:javascript复制def add_desc(self, **kwargs) -> [dict]:
...
**kwargs
动态输入键值对来进行筛选
记录原先的列
代码语言:javascript复制base_title = list(cases_df.columns)
先进行cpu性能数据的处理
代码语言:javascript复制cpu = ['device_cpu_rate%', 'user%', 'system%', 'idle%', 'pid_cpu%']
遍历输入的键值对,如果输入的key值加上%在cpu这个列表里面,那就筛选出cpu性能数据中符合条件的数据
代码语言:javascript复制for k, v in kwargs.items():
if f"{k}%" in cpu:
name = f"{k}%"
df1 = cpu_df[cpu_df[name] > v]
将两个表合并一下
代码语言:javascript复制cases_df = pd.concat([df1, cases_df]).sort_values("datetime", ascending=True)
拿到超过阈值的行号索引
代码语言:javascript复制error_index = list(cases_df[cases_df[name] > ].index)
在超过阈值索引的附近几行添加描述
代码语言:javascript复制for i in error_index:
cases_df.loc[i, 'desc'] = f"{k}超过阈值;"
try:
cases_df.loc[i - , 'desc'] = f"{k}超过阈值;"
except Exception:
pass
try:
cases_df.loc[i , 'desc'] = f"{k}超过阈值;"
except Exception:
pass
剔除性能数据的列
代码语言:javascript复制cases_df = cases_df[pd.notnull(cases_df['fullName'])]
根据flag去除重复添加的用例信息
代码语言:javascript复制cases_df = cases_df[cases_df['flag'] == True]
根据之前保留的原始列信息将性能数据列去除
代码语言:javascript复制new_df = pd.DataFrame(cases_df, columns=base_title)
返回[dict]
格式
return new_df.to_dict("records")
全部新增代码
代码语言:javascript复制def cases_handle(self, path=f"{PERF_PATH}/cases.csv"):
df = self.read_csv(path)
df['datetime'] = pd.to_datetime(df['datetime'], format="%Y-%m-%d %H-%M-%S")
df = df.sort_values("datetime", ascending=True)
return df
def add_desc(self, **kwargs) -> [dict]:
cpu = ['device_cpu_rate%', 'user%', 'system%', 'idle%', 'pid_cpu%']
mem = ['total_ram(MB)', 'free_ram(MB)', 'pid_pss(MB)']
power = ['voltage(V)', 'tempreture(C)', 'current(mA)']
traffic = ['device_total(KB)', 'device_receive(KB)',
'device_transport(KB)',
'pid_rx(KB)', 'pid_tx(KB)', 'pid_total(KB)']
cpu_df = self.cpu_handle()
mem_df = self.mem_handle()
power_df = self.power_handle()
thread_df = self.thread_num_handle()
traffic_df = self.traffic_handle()
cases_df = self.cases_handle()
base_title = list(cases_df.columns)
for k, v in kwargs.items():
if f"{k}%" in cpu:
name = f"{k}%"
df1 = cpu_df[cpu_df[name] > v]
if f"{k}(MB)" in mem:
name = f"{k}(MB)"
df1 = mem_df[mem_df[name] > v]
if any([f"{k}({i})" in power for i in ['V', 'C', 'mA']]):
if k == "voltage":
name = f"{k}(V)"
df1 = power_df[power_df[name] > v]
if k == "tempreture":
name = f"{k}(C)"
df1 = power_df[power_df[name] > v]
if k == "current":
name = f"{k}(mA)"
df1 = power_df[power_df[name] > v]
if k == "threadList":
name = "thread_num"
df1 = thread_df[thread_df[name] > v]
if f'{k}(KB)' in traffic:
name = f'{k}(KB)'
df1 = traffic_df[traffic_df[name] > v]
cases_df = pd.concat([df1, cases_df]).sort_values("datetime", ascending=True)
error_index = list(cases_df[cases_df[name] > ].index)
for i in error_index:
cases_df.loc[i, 'desc'] = f"{k}超过阈值;"
try:
cases_df.loc[i - , 'desc'] = f"{k}超过阈值;"
except Exception:
pass
try:
cases_df.loc[i , 'desc'] = f"{k}超过阈值;"
except Exception:
pass
cases_df = cases_df[pd.notnull(cases_df['fullName'])]
cases_df = cases_df[cases_df['flag'] == True]
new_df = pd.DataFrame(cases_df, columns=base_title)
return new_df.to_dict("records")
测试
代码语言:javascript复制cases_data = d.add_desc(
device_cpu_rate=, user=, system=, idle=, pid_cpu=,
free_ram=, pid_pss=, total_ram=,
tempreture=, voltage=,
threadList=,
device_transport=, device_receive=, device_total=,
pid_tx=, pid_total=, pid_rx=,
)
代码语言:javascript复制[
...
{'datetime': Timestamp('2022-08-01 14:05:17'), 'flag': True, 'fullName': 'src.cases_android.wy.test_health.TestMedicationReminder#test_edit_medication_reminder', 'status': 'passed', 'labels': '微医APP_健康_用药提醒-修改用药提醒时间(@钟鑫)', 'start': 1659333917269.0, 'stop': 1659333926307.0, 'duration': 9038.0, 'parameters': nan, 'statusDetails': "{'message': '', 'trace': ''}", 'kano_url': nan, 'steps': '1-点击一条用药提醒记录;1.1-点击「用药提醒_其中一条用药提醒」;2-选择必要时用药;2.1-获取元素的坐标;2.2-点击坐标「558.0」「640.5」所在的位置;2.3-点击坐标「558.0」「640.5」所在的位置;2.4-点击坐标「558.0」「640.5」所在的位置;2.5-从元素「用药提醒_用药时间-每隔几天用药」滑动到元素「用药提醒_用药时间-每隔几小时用药」的位置;2.6-点击「通用_完成」;3-点击保存;3.1-点击「通用_保存」;4-查看提醒时间为必要时;4.1-转换参数化元素;4.2-查看「用药提醒_用药时间」是否存在', 'desc': ';system超过阈值;device_cpu_rate超过阈值;voltage超过阈值;voltage超过阈值;voltage超过阈值;'}
...
]