enh: change path

This commit is contained in:
jiajingbin 2025-01-23 11:24:38 +08:00
parent e092ae9c0d
commit b6f5152e9d
7 changed files with 816 additions and 0 deletions

View File

@ -0,0 +1,128 @@
from datetime import date
from datetime import timedelta
import os
import re
import requests
from dotenv import load_dotenv
# load .env
load_dotenv()
# define version
version = "3.3.*"
ip = os.getenv("EXCLUDE_IP")
server_ip = os.getenv("SERVER_IP")
owner = os.getenv("OWNER")
# feishu-msg url
feishu_msg_url = os.getenv("FEISHU_MSG_URL")
today = date.today()
#today = date(2023,8,7)
path="/data/telemetry/crash-report/"
# get files for the past 7 days
def get_files():
files = ""
for i in range(1,8):
#print ((today - timedelta(days=i)).strftime("%Y%m%d"))
files = files + path + (today - timedelta(days=i)).strftime("%Y%m%d") + ".txt "
return files
# for none-taosAssertDebug
filter1_cmd = '''grep '"version":"%s"' %s \
| grep "taosd(" \
| awk -F "stackInfo" '{print $2}' \
| grep -v "taosAssertDebug" \
| grep -v %s \
| awk -F "taosd" '{print $3}' \
| cut -d")" -f 1 \
| cut -d"(" -f 2 \
| sort | uniq -c ''' % (version, get_files(), ip)
# for taosAssertDebug
filter2_cmd = '''grep '"version":"%s"' %s \
| grep "taosd(" \
| awk -F "stackInfo" '{print $2}' \
| grep "taosAssertDebug" \
| grep -v %s \
| awk -F "taosd" '{print $3}' \
| cut -d")" -f 1 \
| cut -d"(" -f 2 \
| sort | uniq -c ''' % (version, get_files(), ip)
# get msg info
def get_msg(text):
return {
"msg_type": "post",
"content": {
"post": {
"zh_cn": {
"title": "Telemetry Statistics",
"content": [
[{
"tag": "text",
"text": text
}
]]
}
}
}
}
# post msg
def send_msg(json):
headers = {
'Content-Type': 'application/json'
}
req = requests.post(url=group_url, headers=headers, json=json)
inf = req.json()
if "StatusCode" in inf and inf["StatusCode"] == 0:
pass
else:
print(inf)
# exec cmd and return res
def get_output(cmd):
text = os.popen(cmd)
lines = text.read()
text.close()
return lines
# get sum
def get_count(output):
res = re.findall(" \d+ ", output)
sum1 = 0
for r in res:
sum1 = sum1 + int(r.strip())
return sum1
# print total crash count
def print_result():
#print(f"Files for statistics: {get_files()}\n")
sum1 = get_count(get_output(filter1_cmd))
sum2 = get_count(get_output(filter2_cmd))
total = sum1 + sum2
#print(f"total crashes: {total}")
return total
# send report to feishu
def send_report():
content = f'''
test scope: Telemetry Statistics
owner: {owner}
ip: {server_ip}
from: {get_files().split(" ")[6].split("/")[4].split(".")[0]}
to: {get_files().split(" ")[0].split("/")[4].split(".")[0]}
filter1 result: {get_output(filter1_cmd)}
filter2 result: {get_output(filter2_cmd)}
total crashes: {print_result()}
'''
#send_msg(get_msg(content))
print(content)
print_result()
send_report()

View File

@ -0,0 +1,308 @@
from datetime import date
from datetime import timedelta
import os
import json
import re
import requests
import subprocess
from dotenv import load_dotenv
# load .env
# You should have a .env file in the same directory as this script
# You can exec: cp .env.example .env
load_dotenv()
# define version
version = "3.3.2.*"
version_pattern_str = version.replace('.', r'\.').replace('*', r'\d+')
version_pattern = re.compile(rf'^{version_pattern_str}$')
version_stack_list = list()
# define ip
ip = os.getenv("EXCLUDE_IP")
server_ip = os.getenv("SERVER_IP")
http_serv_ip = os.getenv("HTTP_SERV_IP")
http_serv_port = os.getenv("HTTP_SERV_PORT")
owner = os.getenv("OWNER")
# feishu-msg url
feishu_msg_url = os.getenv("FEISHU_MSG_URL")
# get today
today = date.today()
# Define the file and parameters
path="/data/telemetry/crash-report/"
trace_report_path = path + "trace_report"
os.makedirs(path, exist_ok=True)
os.makedirs(trace_report_path, exist_ok=True)
assert_script_path = path + "filter_assert.sh"
nassert_script_path = path + "filter_nassert.sh"
# get files for the past 7 days
def get_files():
files = ""
for i in range(1,8):
#print ((today - timedelta(days=i)).strftime("%Y%m%d"))
files = files + path + (today - timedelta(days=i)).strftime("%Y%m%d") + ".txt "
return files.strip().split(" ")
# Define the AWK script as a string with proper escaping
def get_res(file_path):
# Execute the script
command = ['bash', file_path, version, ip] + get_files()
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
# Capture the output and errors
output, errors = process.communicate()
# Check for errors
if process.returncode != 0:
return errors
else:
return output.rstrip()
def get_sum(output):
# Split the output into lines
lines = output.strip().split('\n')
# Initialize the sum
total_sum = 0
# Iterate over each line
for line in lines:
# Split each line by space to separate the columns
parts = line.split()
# The first part of the line is the number, convert it to integer
if parts: # Check if there are any elements in the parts list
number = int(parts[0])
total_sum += number
return total_sum
def convert_html(data):
# convert data to json
start_time = get_files()[6].split("/")[-1].split(".")[0]
end_time = get_files()[0].split("/")[-1].split(".")[0]
html_report_file = f'{start_time}_{end_time}.html'
json_data = json.dumps(data)
# Create HTML content
html_content = f'''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stack Trace Report</title>
<style>
body {{
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f0f0f5;
}}
h1 {{
color: #2c3e50;
text-align: center;
}}
table {{
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}}
th, td {{
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}}
th {{
background-color: #3498db;
color: white;
}}
tr:nth-child(even) {{
background-color: #ecf0f1;
}}
tr:hover {{
background-color: #d1e7fd;
}}
pre {{
background-color: #f7f7f7;
padding: 10px;
border: 1px solid #ddd;
overflow-x: auto;
white-space: pre-wrap;
border-radius: 5px;
}}
</style>
</head>
<body>
<h1>Stack Trace Report From {start_time} To {end_time} </h1>
<table>
<thead>
<tr>
<th>Key Stack Info</th>
<th>Versions</th>
<th>Num Of Crashes</th>
<th>Full Stack Info</th>
</tr>
</thead>
<tbody id="report">
</tbody>
</table>
<script>
const data = {json_data};
const reportBody = document.getElementById('report');
data.forEach(entry => {{
const row = document.createElement('tr');
row.innerHTML = `
<td>${{entry.key_stack_info}}</td>
<td>${{entry.version_list.join('<br>')}}</td>
<td>${{entry.count}}</td>
<td><pre>${{entry.full_stack_info}}</pre></td>
`;
reportBody.appendChild(row);
}});
</script>
</body>
</html>
'''
# Write the HTML content to a file
with open(f'{trace_report_path}/{html_report_file}', 'w') as f:
f.write(html_content)
return html_report_file
def get_version_stack_list(res):
for line in res.strip().split('\n'):
version_list = list()
version_stack_dict = dict()
count = line.split()[0]
key_stack_info = line.split()[1]
for file in get_files():
with open(file, 'r') as infile:
for line in infile:
line = line.strip()
data = json.loads(line)
# print(line)
if ip not in line and version_pattern.search(data["version"]) and key_stack_info in line:
if data["version"] not in version_list:
version_list.append(data["version"])
full_stack_info = data["stackInfo"]
version_stack_dict["key_stack_info"] = key_stack_info
version_stack_dict["full_stack_info"] = full_stack_info
version_stack_dict["version_list"] = version_list
version_stack_dict["count"] = count
# print(version_stack_dict)
version_stack_list.append(version_stack_dict)
return version_stack_list
# get msg info
def get_msg(text):
return {
"msg_type": "post",
"content": {
"post": {
"zh_cn": {
"title": "Telemetry Statistics",
"content": [
[{
"tag": "text",
"text": text
}
]]
}
}
}
}
# post msg
def send_msg(json):
headers = {
'Content-Type': 'application/json'
}
req = requests.post(url=feishu_msg_url, headers=headers, json=json)
inf = req.json()
if "StatusCode" in inf and inf["StatusCode"] == 0:
pass
else:
print(inf)
def format_results(results):
# Split the results into lines
lines = results.strip().split('\n')
# Parse lines into a list of tuples (number, rest_of_line)
parsed_lines = []
for line in lines:
parts = line.split(maxsplit=1)
if len(parts) == 2:
number = int(parts[0]) # Convert the number part to an integer
parsed_lines.append((number, parts[1]))
# Sort the parsed lines by the first element (number) in descending order
parsed_lines.sort(reverse=True, key=lambda x: x[0])
# Determine the maximum width of the first column for alignment
# max_width = max(len(str(item[0])) for item in parsed_lines)
if parsed_lines:
max_width = max(len(str(item[0])) for item in parsed_lines)
else:
max_width = 0
# Format each line to align the numbers and function names with indentation
formatted_lines = []
for number, text in parsed_lines:
formatted_line = f" {str(number).rjust(max_width)} {text}"
formatted_lines.append(formatted_line)
# Join the formatted lines into a single string
return '\n'.join(formatted_lines)
# # send report to feishu
def send_report(res, sum, html_report_file):
content = f'''
version: v{version}
from: {get_files()[6].split("/")[-1].split(".")[0]}
to: {get_files()[0].split("/")[-1].split(".")[0]}
ip: {server_ip}
owner: {owner}
result: \n{format_results(res)}\n
total crashes: {sum}\n
details: http://{http_serv_ip}:{http_serv_port}/{html_report_file}
'''
print(get_msg(content))
send_msg(get_msg(content))
# print(content)
# for none-taosAssertDebug
nassert_res = get_res(nassert_script_path)
# print(nassert_res)
# for taosAssertDebug
assert_res = get_res(assert_script_path)
# print(assert_res)
# combine the results
res = nassert_res + assert_res
# get version stack list
version_stack_list = get_version_stack_list(res) if len(res) > 0 else list()
# convert to html
html_report_file = convert_html(version_stack_list)
# get sum
sum = get_sum(res)
# send report
send_report(res, sum, html_report_file)

View File

@ -0,0 +1,210 @@
# Table of Contents
1. [Introduction](#1-introduction)
1. [Prerequisites](#2-prerequisites)
1. [Running](#3-running)
# 1. Introduction
This manual is intended to give developers comprehensive guidance to collect crash information from the past 7 days and report it to the FeiShu notification group.
> [!NOTE]
> - The commands and scripts below are verified on Linux (CentOs 7.9.2009).
> - The commands and steps described below are to run the tests on a single host.
# 2. Prerequisites
- Install Python3
```bash
yum install python3
yum install python3-pip
```
- Install Python dependencies
```bash
pip3 install requests python-dotenv
```
# 3. Running
In `crash-report` directory, there are different types of tests for TDengine. Below is a brief introduction about how to run them and how to add new cases.
### 3.1 Unit Test
Unit tests are the smallest testable units, which are used to test functions, methods or classes in TDengine code.
### 3.1.1 How to run single test case?
```bash
cd debug/build/bin
./osTimeTests
```
### 3.1.2 How to run all unit test cases?
```bash
cd tests/unit-test/
bash test.sh -e 0
```
### 3.1.3 How to add new cases?
<details>
<summary>Detailed steps to add new unit test case</summary>
The Google test framwork is used for unit testing to specific function module, please refer to steps below to add a new test case:
##### a. Create test case file and develop the test scripts
In the test directory corresponding to the target function module, create test files in CPP format and write corresponding test cases.
##### b. Update build configuration
Modify the CMakeLists.txt file in this directory to ensure that the new test files are properly included in the compilation process. See the `source/os/test/CMakeLists.txt` file for configuration examples.
##### c. Compile test code
In the root directory of the project, create a compilation directory (e.g., debug), switch to the directory and run CMake commands (e.g., `cmake .. -DBUILD_TEST=1`) to generate a compilation file,
and then run a compilation command (e.g. make) to complete the compilation of the test code.
##### d. Execute the test program
Find the executable file in the compiled directory(e.g. `TDengine/debug/build/bin/`) and run it.
##### e. Integrate into CI tests
Use the add_test command to add new compiled test cases into CI test collection, ensure that the new added test cases can be run for every build.
</details>
## 3.2 System Test
System tests are end-to-end test cases written in Python from a system point of view. Some of them are designed to test features only in enterprise ediiton, so when running on community edition, they may fail. We'll fix this issue by separating the cases into different gruops in the future.
### 3.2.1 How to run a single test case?
Take test file `system-test/2-query/avg.py` for example:
```bash
cd tests/system-test
python3 ./test.py -f 2-query/avg.py
```
### 3.2.2 How to run all system test cases?
```bash
cd tests
./run_all_ci_cases.sh -t python # all python cases
```
### 3.2.3 How to add new case?
<details>
<summary>Detailed steps to add new system test case</summary>
The Python test framework is developed by TDengine team, and test.py is the test case execution and monitoring of the entry program, Use `python3 ./test.py -h` to view more features.
Please refer to steps below for how to add a new test case:
##### a. Create a test case file and develop the test cases
Create a file in `tests/system-test` containing each functional directory and refer to the use case template `tests/system-test/0-others/test_case_template.py` to add a new test case.
##### b. Execute the test case
Ensure the test case execution is successful.
``` bash
cd tests/system-test && python3 ./test.py -f 0-others/test_case_template.py
```
##### c. Integrate into CI tests
Edit `tests/parallel_test/cases.task` and add the testcase path and executions in the specified format. The third column indicates whether to use Address Sanitizer mode for testing.
```bash
#caseID,rerunTimes,Run with Sanitizer,casePath,caseCommand
,,n,system-test, python3 ./test.py -f 0-others/test_case_template.py
```
</details>
## 3.3 Legacy Test
In the early stage of TDengine development, test cases are run by an internal test framework called TSIM, which is developed in C++.
### 3.3.1 How to run single test case?
To run the legacy test cases, please execute the following commands:
```bash
cd tests/script
./test.sh -f tsim/db/basic1.sim
```
### 3.3.2 How to run all legacy test cases?
```bash
cd tests
./run_all_ci_cases.sh -t legacy # all legacy cases
```
### 3.3.3 How to add new cases?
> [!NOTE]
> TSIM test framwork is deprecated by system test now, it is encouraged to add new test cases in system test, please refer to [System Test](#32-system-test) for details.
## 3.4 Smoke Test
Smoke test is a group of test cases selected from system test, which is also known as sanity test to ensure the critical functionalities of TDengine.
### 3.4.1 How to run test?
```bash
cd /root/TDengine/packaging/smokeTest
./test_smoking_selfhost.sh
```
### 3.4.2 How to add new cases?
New cases can be added by updating the value of `commands` variable in `test_smoking_selfhost.sh`.
## 3.5 Chaos Test
A simple tool to execute various functions of the system in a randomized way, hoping to expose potential problems without a pre-defined test scenario.
### 3.5.1 How to run test?
```bash
cd tests/pytest
python3 auto_crash_gen.py
```
### 3.5.2 How to add new cases?
1. Add a function, such as `TaskCreateNewFunction` in `pytest/crash_gen/crash_gen_main.py`.
2. Integrate `TaskCreateNewFunction` into the `balance_pickTaskType` function in `crash_gen_main.py`.
## 3.6 CI Test
CI testing (Continuous Integration testing), is an important practice in software development that aims to automate frequent integration of code into a shared codebase, build and test it to ensure code quality and stability.
TDengine CI testing will run all the test cases from the following three types of tests: unit test, system test and legacy test.
### 3.6.1 How to run all CI test cases?
If this is the first time to run all the CI test cases, it is recommended to add the test branch, please run it with following commands:
```bash
cd tests
./run_all_ci_cases.sh -b main # on main branch
```
### 3.6.2 How to add new cases?
Please refer to the [Unit Test](#31-unit-test)、[System Test](#32-system-test) and [Legacy Test](#33-legacy-test) sections for detailed steps to add new test cases, when new cases are added in aboved tests, they will be run automatically by CI test.

View File

@ -0,0 +1,15 @@
#!/bin/bash
source .env
filesPath="/data/telemetry/crash-report"
version="3.0.4.1"
taosdataIp=$EXCLUDE_IP
grep "\"version\":\"${version}\"" ${filesPath}/*.txt \
| grep "taosd(" \
| awk -F "stackInfo" '{print $2}' \
| grep -v "taosAssertDebug" \
| grep -v ${taosdataIp} \
| awk -F "taosd" '{print $2}' \
| cut -d")" -f 1 \
| cut -d"(" -f 2 \
| sort | uniq -c

View File

@ -0,0 +1,14 @@
#!/bin/bash
source .env
filesPath="/data/telemetry/crash-report"
version="3.0.4.1"
taosdataIp=$EXCLUDE_IP
grep "\"version\":\"${version}\"" ${filesPath}/*.txt \
| grep "taosd(" \
| awk -F "stackInfo" '{print $2}' \
| grep "taosAssertDebug" \
| grep -v ${taosdataIp} \
| awk -F "taosd" '{print $3}' \
| cut -d")" -f 1 \
| cut -d"(" -f 2 \
| sort | uniq -c

View File

@ -0,0 +1,67 @@
#!/bin/bash
# Extract version and IP from the first two arguments
version="$1"
ip="$2"
shift 2 # Remove the first two arguments, leaving only file paths
# All remaining arguments are considered as file paths
file_paths="$@"
# Execute the awk script and capture the output
readarray -t output < <(awk -v version="$version" -v ip="$ip" '
BEGIN {
RS = "\\n"; # Set the record separator to newline
FS = ","; # Set the field separator to comma
total = 0; # Initialize total count
version_regex = version; # Use the passed version pattern
ip_regex = ip; # Use the passed IP pattern
}
{
start_collecting = 0;
version_matched = 0;
ip_excluded = 0;
# Check each field within a record
for (i = 1; i <= NF; i++) {
if ($i ~ /"ip":"[^"]*"/ && $i ~ ip_regex) {
ip_excluded = 1;
}
if ($i ~ /"version":"[^"]*"/ && $i ~ version_regex) {
version_matched = 1;
}
}
if (!ip_excluded && version_matched) {
for (i = 1; i <= NF; i++) {
if ($i ~ /taosAssertDebug/ && start_collecting == 0) {
start_collecting = 1;
continue;
}
if (start_collecting == 1 && $i ~ /taosd\(([^)]+)\)/) {
match($i, /taosd\(([^)]+)\)/, arr);
if (arr[1] != "") {
count[arr[1]]++;
total++;
break;
}
}
}
}
}
END {
for (c in count) {
printf "%d %s\n", count[c], c;
}
print "Total count:", total;
}' $file_paths)
# Capture the function details and total count into separate variables
function_details=$(printf "%s\n" "${output[@]::${#output[@]}-1}")
total_count="${output[-1]}"
# Output or use the variables as needed
#echo "Function Details:"
echo "$function_details"
#echo "Total Count:"
#echo "$total_count"

View File

@ -0,0 +1,74 @@
#!/bin/bash
# Pass version, ip, and file paths as arguments
version="$1"
ip="$2"
shift 2 # Shift the first two arguments to get file paths
file_paths="$@"
# Execute awk and capture the output
readarray -t output < <(awk -v version="$version" -v ip="$ip" '
BEGIN {
RS = "\\n"; # Set the record separator to newline
total = 0; # Initialize total count
version_regex = "\"version\":\"" version; # Construct the regex for version
ip_regex = "\"ip\":\"" ip "\""; # Construct the regex for IP
}
{
found = 0; # Initialize the found flag to false
start_collecting = 1; # Start collecting by default, unless taosAssertDebug is encountered
split($0, parts, "\\n"); # Split each record by newline
# Check for version and IP in each part
version_matched = 0;
ip_excluded = 0;
for (i in parts) {
if (parts[i] ~ version_regex) {
version_matched = 1; # Set flag if version is matched
}
if (parts[i] ~ ip_regex) {
ip_excluded = 1; # Set flag if IP is matched
break; # No need to continue if IP is excluded
}
}
# Process only if version is matched and IP is not excluded
if (version_matched && !ip_excluded) {
for (i in parts) {
if (parts[i] ~ /taosAssertDebug/) {
start_collecting = 0; # Skip this record if taosAssertDebug is encountered
break; # Exit the loop
}
}
if (start_collecting == 1) { # Continue processing if taosAssertDebug is not found
for (i in parts) {
if (found == 0 && parts[i] ~ /frame:.*taosd\([^)]+\)/) {
# Match the first frame that meets the condition
match(parts[i], /taosd\(([^)]+)\)/, a); # Extract the function name
if (a[1] != "") {
count[a[1]]++; # Increment the count for this function name
total++; # Increment the total count
found = 1; # Set found flag to true
break; # Exit the loop once the function is found
}
}
}
}
}
}
END {
for (c in count) {
printf "%d %s\n", count[c], c; # Print the count and function name formatted
}
print total; # Print the total count alone
}' $file_paths) # Note the removal of quotes around "$file_paths" to handle multiple paths
# Capture the function details and total count into separate variables
function_details=$(printf "%s\n" "${output[@]::${#output[@]}-1}") # Join array elements with newlines
total_count="${output[-1]}" # The last element
# Output or use the variables as needed
#echo "Function Details:"
echo "$function_details"
#echo "Total Count:"
#echo "$total_count"