diff --git a/webmeter/core/engine.py b/webmeter/core/engine.py index 0f240d8..040ebf5 100644 --- a/webmeter/core/engine.py +++ b/webmeter/core/engine.py @@ -2,9 +2,9 @@ import datetime import json from loguru import logger -from core.utils import Common -from core.sqlhandle import crud -from core.task import TaskBase +from webmeter.core.utils import Common +from webmeter.core.sqlhandle import crud +from webmeter.core.task import TaskBase class EngineServie(TaskBase): diff --git a/webmeter/core/plan.py b/webmeter/core/plan.py index 13fa829..fa7b035 100644 --- a/webmeter/core/plan.py +++ b/webmeter/core/plan.py @@ -1,7 +1,7 @@ import os import shutil from loguru import logger -from core.utils import Common, JMX +from webmeter.core.utils import Common, JMX from fastapi import UploadFile class Base(object): diff --git a/webmeter/core/result.py b/webmeter/core/result.py deleted file mode 100644 index 8015236..0000000 --- a/webmeter/core/result.py +++ /dev/null @@ -1,9 +0,0 @@ - -class Manage(object): - """test result management """ - pass - - -class Analyse(object): - """analyse test result""" - pass \ No newline at end of file diff --git a/webmeter/core/sqlhandle/crud.py b/webmeter/core/sqlhandle/crud.py index 03b4273..3fbd8a2 100644 --- a/webmeter/core/sqlhandle/crud.py +++ b/webmeter/core/sqlhandle/crud.py @@ -2,7 +2,7 @@ from typing import Optional from loguru import logger import os, shutil, datetime -from core.plan import TestPlan +from webmeter.core.plan import TestPlan def create_task(tasks: dict): with database.dbConnect() as session: @@ -32,21 +32,31 @@ def update_task(tasks: dict): else: raise Exception('{} is not existed'.format(result.task)) -def query_task_one(tasks: schemas.taskQuery): +def query_task_one(plan: str, task: str) -> dict: with database.dbConnect() as session: - results = session.query(models.Task).filter(models.Task.plan == tasks.plan).all() - result_dict = [result.task for result in results] + results = session.query(models.Task).filter(models.Task.plan == plan, + models.Task.task == task).first() + result_dict = dict() + result_dict['plan'] = results.plan + result_dict['task'] = results.task + result_dict['model'] = results.model + result_dict['status'] = results.status + result_dict['threads'] = results.threads + result_dict['success_num'] = results.success_num + result_dict['fail_num'] = results.fail_num + result_dict['stime'] = results.stime + result_dict['etime'] = results.etime return result_dict -def query_task_all(): +def query_task_all() -> list: with database.dbConnect() as session: results = session.query(models.Task).order_by(models.Task.stime.desc()).all() - result_dict = [{'plan': result.plan, 'task':result.task, + result_list = [{'plan': result.plan, 'task':result.task, 'model': result.model, 'status': result.status, 'threads': result.threads, 'success_num': result.success_num, 'fail_num': result.fail_num, 'stime': result.stime, 'etime': result.etime} for result in results] - return result_dict + return result_list def remove_task_one(plan: str, task: str): logger.warning('remove task data : {}'.format(task)) diff --git a/webmeter/core/sqlhandle/database.py b/webmeter/core/sqlhandle/database.py index a93731f..395439a 100644 --- a/webmeter/core/sqlhandle/database.py +++ b/webmeter/core/sqlhandle/database.py @@ -4,7 +4,7 @@ from contextlib import contextmanager from loguru import logger import os -from core.utils import Common +from webmeter.core.utils import Common SQL_DIR = Common.make_dir(os.path.join(os.getcwd(), 'webmeter')) SQLALCHEMY_DATABASE_URL = "sqlite:///{}/webmeter_app.db".format(SQL_DIR) diff --git a/webmeter/core/sqlhandle/models.py b/webmeter/core/sqlhandle/models.py index 9e0978d..039bc7a 100644 --- a/webmeter/core/sqlhandle/models.py +++ b/webmeter/core/sqlhandle/models.py @@ -1,5 +1,5 @@ from sqlalchemy import Boolean, Column, Integer, String, DateTime, Float -from core.sqlhandle.database import Base +from webmeter.core.sqlhandle.database import Base import datetime diff --git a/webmeter/core/task.py b/webmeter/core/task.py index 22de421..456c08a 100644 --- a/webmeter/core/task.py +++ b/webmeter/core/task.py @@ -2,6 +2,8 @@ import json, os from core.utils import Common from typing import Optional +from webmeter.core.sqlhandle import crud + class TaskBase(object): @@ -18,17 +20,17 @@ def read_statistics_file(cls, plan: str, task: str) -> dict: raise Exception('No content found in the statistics.json') @classmethod - def read_result_file(cls, plan: str, task: str): + def read_result_file(cls, plan: str, task: str) -> list: """read result.jtl content""" result_file_path = os.path.join(cls.ROOT_DIR, plan, 'report', task,'result.jtl') - content = Common.read_file_content(result_file_path) - if content: + content = Common.read_file_lines(result_file_path) + if content.__len__() > 0: return content else: raise Exception('No content found in the result.jtl') @classmethod - def read_log(cls, plan: str, task: str) -> Optional[str]: + def read_log_file(cls, plan: str, task: str) -> Optional[str]: """read result.log content""" log_file_path = os.path.join(cls.ROOT_DIR, plan, 'log', task,'result.log') content = Common.read_file_content(log_file_path) @@ -36,3 +38,30 @@ def read_log(cls, plan: str, task: str) -> Optional[str]: return content else: raise Exception('No content found in the result.log') + +class TaskDetail(TaskBase): + + @classmethod + def getTestAndReportInfo(cls, plan: str, task: str) -> dict: + result = dict() + summary_dict = crud.query_task_one(plan, task) + result['source_file'] = os.path.join(TaskBase.ROOT_DIR, plan, 'report', task, 'result.jtl') + result['stime'] = summary_dict.get('stime') + result['etime'] = summary_dict.get('etime') + return result + + @classmethod + def getRequestSummary(cls, plan: str, task: str) -> list: + jtlContent = TaskBase.read_result_file(plan, task) + jtlContent.pop(0) + jtlList = [{'lable':item.split(',')[2], + 'responseCode':item.split(',')[3], + 'responseMessage':item.split(',')[4], + 'threadName': item.split(',')[5], + 'failureMessage':item.split(',')[8], + 'bytes': item.split(',')[9], + 'sentBytes': item.split(',')[10], + 'allThreads':item.split(',')[12], + 'URL': item.split(',')[13]} for item in jtlContent] + return jtlList + diff --git a/webmeter/core/utils.py b/webmeter/core/utils.py index 5baf324..4c9186a 100644 --- a/webmeter/core/utils.py +++ b/webmeter/core/utils.py @@ -71,7 +71,13 @@ def make_dir_file(cls, dir : str, filename: str, content: str) -> str: def read_file_content(cls, file_path) -> str: with open(file=file_path, mode='r', encoding='utf-8') as f: content = f.read() - return content + return content + + @classmethod + def read_file_lines(cls, file_path) -> list: + with open(file=file_path, mode='r', encoding='utf-8') as f: + content = f.readlines() + return content @classmethod def pc_platform(cls) -> Optional[str]: diff --git a/webmeter/static/js/common.js b/webmeter/static/js/common.js new file mode 100644 index 0000000..dfbce13 --- /dev/null +++ b/webmeter/static/js/common.js @@ -0,0 +1,44 @@ + +const getLocationParams = () => { + let href = window.location.href; + let query = href.substring(href.indexOf("?") + 1); + let vars = query.split("&"); + let obj = {}; + for (let i = 0; i < vars.length; i++) { + let pair = vars[i].split("="); + obj[pair[0]] = pair[1]; + } + return obj; + }; + + +const elMessage = (type, message) => { + switch(type){ + case 'success': + ElementPlus.ElMessage({ + showClose: true, + message: message, + type: 'success', + }) + break; + case 'warning': + ElementPlus.ElMessage({ + showClose: true, + message: message, + type: 'warning', + }) + break; + case 'error': + ElementPlus.ElMessage({ + showClose: true, + message: message, + type: 'error', + }) + break; + default: + ElementPlus.ElMessage({ + showClose: true, + message: message, + }) + }; +} diff --git a/webmeter/static/js/lan.js b/webmeter/static/js/lan.js index 1501ffa..22e6746 100644 --- a/webmeter/static/js/lan.js +++ b/webmeter/static/js/lan.js @@ -1,6 +1,6 @@ const ENGLISH = { 'plan_page_title':'TEST PLAN', - 'result_page_title':'TEST RESULT', + 'task_page_title':'TASK RESULT', 'monitor': 'Monitor', 'star_me': 'Star Me', 'documentation': 'Documentation', @@ -61,7 +61,7 @@ const ENGLISH = { const CHINESE = { 'plan_page_title':'测试计划', - 'result_page_title':'测试结果', + 'task_page_title':'任务管理', 'monitor': '资源监控', 'star_me': 'Github点赞', 'documentation': '文档', diff --git a/webmeter/templates/analysis.html b/webmeter/templates/analysis.html new file mode 100644 index 0000000..e45f541 --- /dev/null +++ b/webmeter/templates/analysis.html @@ -0,0 +1,487 @@ + + + + + + + + + + + WebMeter - TASK ANALYSIS + + + + + +
+ +
+ +
+
+ + +
+
+

Test and Report information

+
+
+
+
+ + + + + + + + + + + + + + + +
Data Source
Start Time
End Time
+
+
+
+
+ +
+
+

Requests Summary

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
lablethread NameURLall Threadsresponse Coderesponse Messagefailure Messagebytessent Bytes
+
+
+
+
+
+
+

Statistics

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
lablesamplerserrorserror(%)avg ResTimemedian ResTimemin ResTimemax ResTime90th pct95th pct99th pctthroughput(s)sent(KB/s)recieve(KB/s)
+
+ +
+
+
+ Charts +
+
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/webmeter/templates/plan.html b/webmeter/templates/plan.html index 3171dee..6f326d5 100644 --- a/webmeter/templates/plan.html +++ b/webmeter/templates/plan.html @@ -1,4 +1,5 @@ + @@ -45,7 +45,7 @@

- + @@ -118,13 +118,15 @@

- Running + + Running + Done
- + @@ -188,7 +190,7 @@

  • - . V1.0.0 + webmeter preview
  • @@ -303,6 +305,10 @@

    Are you sure?

    initTaskData() }); + const analysisClick = (plan, task) =>{ + window.location.href = '/analysis/'+ plan + '/'+ task + } + const setLanguage = (language) => { loading.value = true axios.post('/api/language/set', { @@ -469,7 +475,8 @@

    Are you sure?

    setLanguage, removeClick, removeOneTask, - removeAllTask + removeAllTask, + analysisClick }; } }); diff --git a/webmeter/view/api.py b/webmeter/view/api.py index 706f33a..cf4e60e 100644 --- a/webmeter/view/api.py +++ b/webmeter/view/api.py @@ -1,10 +1,11 @@ from loguru import logger from typing import Union from fastapi import APIRouter, UploadFile, File, Form -from core.plan import TestPlan -from core.engine import EngineServie -from core.sqlhandle import crud, models, schemas -from core.sqlhandle.database import engine +from webmeter.core.plan import TestPlan +from webmeter.core.engine import EngineServie +from webmeter.core.sqlhandle import crud, models, schemas +from webmeter.core.sqlhandle.database import engine +from webmeter.core.task import TaskDetail router = APIRouter() test_plan = TestPlan() models.Base.metadata.create_all(bind=engine) @@ -141,16 +142,6 @@ async def run_plan(content: dict): result = {'status':0, 'msg': str(e)} return result -@router.post("/api/task/query/one") -async def query_task_one(tasks: schemas.taskQuery): - try: - data = crud.query_task_one(tasks=tasks) - result = {'status':1, 'msg': 'success', 'data': data} - except Exception as e: - logger.exception(e) - result = {'status':0, 'msg': str(e)} - return result - @router.post("/api/task/query/all") async def query_task_all(): try: @@ -181,4 +172,40 @@ async def remove_task_all(): except Exception as e: logger.exception(e) result = {'status':0, 'msg': str(e)} + return result + +@router.post("/api/task/analysis/base_info") +async def analysis(content: dict): + plan = content.get('plan') + task = content.get('task') + try: + data = TaskDetail.getTestAndReportInfo(plan, task) + result = {'status':1, 'msg': 'success', 'data': data} + except Exception as e: + logger.exception(e) + result = {'status':0, 'msg': str(e)} + return result + +@router.post("/api/task/analysis/request_summary") +async def requests_summary(content: dict): + plan = content.get('plan') + task = content.get('task') + try: + data = TaskDetail.getRequestSummary(plan, task) + result = {'status':1, 'msg': 'success', 'data': data} + except Exception as e: + logger.exception(e) + result = {'status':0, 'msg': str(e)} + return result + +@router.post("/api/task/analysis/statistics") +async def statistics(content: dict): + plan = content.get('plan') + task = content.get('task') + try: + data = TaskDetail.read_statistics_file(plan, task) + result = {'status':1, 'msg': 'success', 'data': data} + except Exception as e: + logger.exception(e) + result = {'status':0, 'msg': str(e)} return result \ No newline at end of file diff --git a/webmeter/view/page.py b/webmeter/view/page.py index 172c8f9..389e4a0 100644 --- a/webmeter/view/page.py +++ b/webmeter/view/page.py @@ -21,4 +21,8 @@ async def plan(request: Request): @router.get("/result", response_class=HTMLResponse) async def result(request: Request): - return templates.TemplateResponse("result.html", {"request": request}) \ No newline at end of file + return templates.TemplateResponse("result.html", {"request": request}) + +@router.get("/analysis/{plan}/{task}", response_class=HTMLResponse) +async def result(request: Request, plan: str, task: str): + return templates.TemplateResponse("analysis.html", {"request": request, "plan": plan, "task": task}) \ No newline at end of file diff --git a/webmeter/web.py b/webmeter/web.py index 21ccfa2..fe28786 100644 --- a/webmeter/web.py +++ b/webmeter/web.py @@ -1,7 +1,7 @@ import uvicorn from fastapi import FastAPI from webmeter.view import page,api -from webmeter.core.utils import Utils +from webmeter.core.utils import Common import requests import webbrowser import multiprocessing @@ -25,7 +25,7 @@ def open_url(host: str, port: int) -> None: flag = status(host, port) webbrowser.open('http://{}:{}/plan'.format(host, port), new=2) -def main(host=Utils.ip(), port=6006) -> None: +def main(host=Common.ip(), port=6006) -> None: pool = multiprocessing.Pool(processes=2) pool.apply_async(start, (host, port)) pool.apply_async(open_url, (host, port))