locust+influxdb+grafana 實現自定義測試報告
locust 本身提供的測試報告勉強可以用,但是也有很多不方便的地方,於是自己搭建一個測試報告,下面是測試報告的效果圖
下面是 locust 輸出的測試結果,可以看到和圖形報告展示的資料是一致的
下面是搭建過程,(省去了 locust 和 grafana 安裝過程,安裝比較簡單,可自行百度)
一 locust 測試指令碼,注意事項
a.配置寫入 csv 檔案時間間隔,在測試類中增加
locust.stats.CSV_STATS_INTERVAL_SEC = 5 # 配置寫入csv檔案頻率為5秒,預設是2秒
b.啟動命令
locust -f test_answer.py -u 50 --csv=fileName --csv-full-history --headless -t10m
--csv=fileName中的fileName是生成檔名的字首
二 influxDb 配置
a.啟動 influxdb
後臺啟動:nohup influxd -config /etc/influxdb/influxdb.conf &
b. 進入 influxdb 資料庫
進入終端:influx -host 127.0.0.1 -port 8096 可以知道埠和 ip,不不指定預設是本機 8086 埠
如果需要修改埠,在/etc/influxdb/influxdb.conf 修改埠
c.建立資料庫
create database databaseName
show databases
d.進入資料庫
use databaseName
e.檢視錶
show measurements
三 grafana 配置
a.配置資料來源
按圖配置就可以了,最下面還有個 Min time interval 填 5 不要忘了
b. 配置儀表模板
官方模板地址:https://grafana.com/grafana/dashboards
我們選 5496 模板,點選 lode 下載
然後選擇資料來源等配置點選 import 按鈕
四 將 locust 測試資料寫入 influxDB
需要注意的是 influxdb 的包和 locust 的包不相容,建議 influxdb 在虛擬環境中執行,可以避免這個問題
指令碼啟動命令:python3 csv_to_influxdb.py -h "127.0.0.1" -p 8086 -db "database" -f "fileName _failures.csv" -s "fileName _stats_history.csv" -a "application" -m "measurement"
import subprocess, copy, time, threading, csv, click
from datetime import datetime
from influxdb import InfluxDBClient
@click.command()
@click.option('--host', '-h', default='127.0.0.1', help="influxdb地址")
@click.option('--port', '-p', default=8086, type=int, help="influxdb埠")
@click.option('--database', '-db', required=True, help="influxdb資料庫名")
@click.option('--m', '-m', required=True, help="influxdb表名 measurement")
@click.option('--fp', '-f', required=True, help="locust的failures_csv_file_path失敗結果檔案路徑")
@click.option('--sp', '-s', required=True, help="locust的status_csv_file_path測試結果檔案路徑")
@click.option('--application', '-a', required=True, help="測試標籤,在資料庫中區分不同的測試計劃")
def run(host, port, database, m, fp, sp, application):
click.echo('host=%s,port=%s, database=%s, m=%s, fp=%s, sp=%s, application=%s' % (host, port, database, m, fp, sp, application))
client = InfluxDBClient(host=host, port=port, database=database)
light = threading.Thread(target=stats_history_to_influxdb, args=(client, m, sp, application))
light.start()
car1 = threading.Thread(target=failures_to_influxdb, args=(m, fp, application))
car1.start()
lock = threading.Lock()
body = [] # 測試資料容器
stats_history_count_dict = {} # 請求總數的容器
stats_history_failures_count_dict = {} # 請求明細中的失敗總數容器
failures_count_dict = {}
'''
failures_count_dict使用來存放失敗總數的容器,由於locust寫入的*_failures.csv檔案是覆蓋寫入的,所以這個檔案使用csv庫讀取,每隔5秒讀取一次
locust同一個請求的同一個失敗的失敗數是累計的,所以這裡將每個錯誤資訊分別儲存,後面計算本5秒鐘每一個api的每一個失敗的失敗數
failures_count_dict = {
'request_name': { # 介面層
'response_msg': { # responsemsg層
'url': 1.0
}
}
}
'''
def body_append(value):
'''
往body裡面寫值
:param value:
:return:
'''
lock.acquire() # 加鎖
global body
body.append(value)
lock.release() # 釋放鎖
def body_clear():
'''
清空body
:return:
'''
lock.acquire() # 加鎖
global body
body.clear()
lock.release() # 釋放鎖
def stats_history_to_influxdb(client, measurement, status_csv_file_path, application):
'''
讀取locust測試結果並寫入influxdb
1.使用tail讀取stats_history.csv檔案,正因為使用了tail命令,所以該指令碼只能在linux環境下執行
2.判斷是否是首行,是則跳過
3.從csv檔案取出請求總數,減去容器中存放的上次請求總數,然後將新值賦值給stats_history_count_dict容器,如果count是負數則說明容器裡面有上一次的測試資料,那麼直接存csv檔案裡面的count,並將容器清空
4.從csv檔案取出失敗數,減去容器中存放的上次失敗數,然後將新值賦值給stats_history_failures_count_dict容器,如果count是負數則說明容器裡面有上一次的測試資料,那麼直接存csv檔案裡面的count,並將容器清空
5.將處理好的資料存入body
a.如果transaction是Aggregated,則額外增加一條transaction等於internal的資料
b.如果failures_count > 0,則額外增加一條statut等於ko的資料
6.最後將body存入influxdb
:return:
'''
p = subprocess.Popen('tail -Fn 0 %s' % status_csv_file_path, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in iter(p.stdout.readline, b''):
click.echo('m=%s, fp=%s, application=%s' % (measurement, status_csv_file_path, application))
line = line.rstrip().decode('utf8')
data_list = line.split(',')
# 判斷是否是首行和無效的Aggregated
if data_list[0][1:-1].isnumeric() and data_list[7].isnumeric():
name = data_list[3][1:-1].replace('/', '_')
# 判斷容器裡面有沒有count,有就用本次的count減去上次的count
this_time_count = float(data_list[18])
if stats_history_count_dict.get(name):
count = this_time_count - stats_history_count_dict.get(name)
# count小於0說明是重新開始測試,需要清空count_dict
if count < 0:
count = this_time_count
stats_history_count_dict.clear()
else:
count = this_time_count
# 給count容器賦值
stats_history_count_dict.update({name: this_time_count})
# 判斷容器裡面有沒有failures_count,有就用本次的count減去上次的count
this_time_failures_count = float(data_list[19])
failures_count = 0.0
if this_time_failures_count > 0: # 如果有error資料就走下面的處理邏輯
if stats_history_failures_count_dict.get(name):
failures_count = this_time_failures_count - stats_history_failures_count_dict.get(name)
# failures_count小於0說明重新開始測試,需要清空stats_history_failures_count_dict
if failures_count < 0:
failures_count = this_time_failures_count
stats_history_failures_count_dict.clear()
else:
failures_count = this_time_failures_count
# 給stats_history_failures_count_dict容器賦值
stats_history_failures_count_dict.update({name: this_time_failures_count})
test_data = {
"measurement": measurement,
"time": int(data_list[0][1:-1]) * 1000000000,
"tags": {
"application": application,
"responseCode": None,
"responseMessage": None,
"statut": 'ok' if name != 'Aggregated' else 'all',
"transaction": name if name != 'Aggregated' else 'all'
},
"fields": {
'avg': float(data_list[21]),
'count': count,
'countError': failures_count,
'endedT': float(data_list[1][1:-1]),
'hit': float(data_list[18]),
'max': float(data_list[23]),
'maxAT': float(data_list[1][1:-1]),
'meanAT': None,
'min': float(data_list[22]),
'minAT': None,
'pct90.0': float(data_list[10]),
'pct95.0': float(data_list[11]),
'pct99.0': float(data_list[13]),
'rb': None,
'sb': float(data_list[24]),
'startedT': float(data_list[1][1:-1])
}
}
# 封裝測試結果
body_append(test_data)
test_data_detailed = copy.deepcopy(body)[len(body) - 1]
# 判斷是否是Aggregated合計資料,如果是就插入一條internal資料,然後寫入influxdb
if name == 'Aggregated':
# 封裝internal,一組資料的最後一個,直接寫入influxdb,並清空body
test_data_detailed['tags']['transaction'] = 'internal'
test_data_detailed['tags']['statut'] = None
test_data_detailed['fields']['avg'] = None
test_data_detailed['fields']['count'] = None
test_data_detailed['fields']['hit'] = None
test_data_detailed['fields']['max'] = None
test_data_detailed['fields']['pct90.0'] = None
test_data_detailed['fields']['pct95.0'] = None
test_data_detailed['fields']['pct99.0'] = None
test_data_detailed['fields']['sb'] = None
body_append(test_data_detailed)
client.write_points(body)
body_clear()
else: # 封裝單個請求的5秒彙總資料
test_data_detailed['tags']['statut'] = 'all'
body_append(test_data_detailed)
if name != 'Aggregated' and failures_count > 0:
# 寫入error資料
test_data_error = copy.deepcopy(test_data_detailed)
test_data_error['tags']['statut'] = 'ko'
test_data_error['fields']['count'] = failures_count
body_append(test_data_error)
def failures_to_influxdb(measurement, failures_csv_file_path, application):
'''
讀取locust斷言失敗的結果,並寫入influxdb
1.清除*_failures.csv中的資料,不清楚的話一開始測試就會直接把上次測試的結果寫入資料庫
2.開始讀取*_failures.csv,第一行是表頭,直接if index跳過
3.從csv檔案取出失敗數,減去容器中存放的上次失敗數,然後將新值賦值給容器,如果count是負數則說明容器裡面有上一次的測試資料,那麼直接存csv檔案裡面的count,並將容器清空
4.將處理好的資料存入body,等待stats_history_to_influxdb方法中寫入influxdb
:return:
'''
try:
open('answer_failures.csv', "r+").truncate() # 清空檔案
except FileNotFoundError:
print("沒有檔案!")
while True:
click.echo('m=%s, fp=%s, application=%s' % (measurement, failures_csv_file_path, application))
try:
answer_failures = csv.reader(open(failures_csv_file_path, 'r', encoding='UTF-8-sig'))
except FileNotFoundError:
time.sleep(5)
continue
for index, line in enumerate(answer_failures):
# 判斷是否是首行和無效的Aggregated
if index:
# 取出transaction和count
name = line[1].replace('/', '_') # name裡面不能有/,如果有這裡會替換成_
this_time_count = float(line[3])
# 格式化Error, 將url和responseMsg,拆分開
error_list = line[2].split(' response ')
# 算出每一次的count數
if failures_count_dict.get(name, {}).get(error_list[1], {}).get(error_list[0], None):
count = this_time_count - failures_count_dict.get(name, {}).get(error_list[1], {}).get(error_list[0], None)
else:
count = this_time_count
# 判斷是否有新增的error
if count > 0:
# 給count容器賦值
# failures_count_dict.update({name: {error_list[1]: {error_list[0]: this_time_count, 'count': count}}})
if failures_count_dict.get(name):
if failures_count_dict.get(name, {}).get(error_list[1], {}):
if failures_count_dict.get(name, {}).get(error_list[1], {}).get(error_list[0], None):
failures_count_dict[name][error_list[1]][error_list[0]] = this_time_count
else:
failures_count_dict[name][error_list[1]].update({error_list[0]: this_time_count})
else:
failures_count_dict[name].update({error_list[1]: {error_list[0]: this_time_count}})
else:
failures_count_dict.update({name: {error_list[1]: {error_list[0]: this_time_count}}})
test_data = {
"measurement": measurement,
"time": datetime.utcnow().isoformat("T"),
"tags": {
"statut": error_list[0], # 將url存放在status欄位
"application": application,
"responseCode": "Assertion failed",
"responseMessage": error_list[1],
"transaction": name
},
"fields": {
'count': count,
}
}
# 封裝測試結果
body_append(test_data)
elif count < 0:
failures_count_dict.clear()
break
time.sleep(5)
if __name__ == '__main__':
run()
相關文章
- jinja2快速實現自定義的robotframework的測試報告Framework測試報告
- 異常-自定義異常的實現和測試
- 測試 iris 時自定義 context 包Context
- 自定義 Composer 包 dome 小測試
- EventSource的自定義實現
- Net 實現自定義Aop
- 自定義實現Complex類
- 自定義SpringMVC部分實現SpringMVC
- Android自定義拍照實現Android
- 介面測試--自定義斷言設定
- Locust+InfluxDB+Grafana 效能測試資料視覺化展示UXGrafana視覺化
- CefSharp自定義快取實現快取
- Flutter自定義Banner的實現Flutter
- Flutter自定義View的實現FlutterView
- Allure測試報告測試報告
- Jumper 測試報告測試報告
- 雲測試報告測試報告
- 測試計劃和測試報告測試報告
- allure報告自定義logo圖片和文字Go
- 自定義事件實現子傳父事件
- 實現MyBatisPlus自定義sql注入器MyBatisSQL
- avalonia實現自定義小彈窗
- Qt實現自定義控制元件QT控制元件
- Flutter實現自定義篩選框Flutter
- @ConfigurationProperties實現自定義配置繫結
- 20190118-自定義實現replace方法
- 重新定義 Locust 的測試報告_效能監控平臺測試報告
- 自動化測試 RobotFramework自定義靜態測試類庫總結Framework
- 滲透測試報告測試報告
- 測試總結報告
- MySQL 效能壓測工具,從入門到自定義測試項MySql
- python實現自定義執行緒池Python執行緒
- 淺談如何實現自定義的 iterator
- video自定義實現視訊播放功能IDE
- Highcharts 實現自定義匯出圖片
- .Net Core中自定義認證實現
- JavaScript實現自定義的生命週期JavaScript
- vue-自定義指令-實現提示功能Vue