mirror of
https://github.com/taosdata/TDengine
synced 2026-05-24 10:09:01 +00:00
189 lines
5.7 KiB
Python
189 lines
5.7 KiB
Python
###################################################################
|
|
# Copyright (c) 2023 by TAOS Technologies, Inc.
|
|
# All rights reserved.
|
|
#
|
|
# This file is proprietary and confidential to TAOS Technologies.
|
|
# No part of this file may be reproduced, stored, transmitted,
|
|
# disclosed or used in any form or by any means other than as
|
|
# expressly provided by the written permission from Jianhui Tao
|
|
#
|
|
###################################################################
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
# about tools funciton extension
|
|
#
|
|
|
|
import sys
|
|
import os
|
|
import time
|
|
import datetime
|
|
import psutil
|
|
import glob
|
|
from .srvCtl import *
|
|
from .eos import *
|
|
from .log import tdLog
|
|
from .sql import tdSql
|
|
|
|
# cpu frequent as random
|
|
def cpuRand(max):
|
|
decimal = int(str(psutil.cpu_freq().current).split(".")[1])
|
|
return decimal % max
|
|
|
|
# remove single and doulbe quotation
|
|
def removeQuota(origin):
|
|
value = ""
|
|
for c in origin:
|
|
if c != '\'' and c != '"':
|
|
value += c
|
|
|
|
return value
|
|
|
|
# find string in taosd log
|
|
def findTaosdLog(key, dnodeIdx=1, retry=60):
|
|
logPath = sc.dnodeLogPath(dnodeIdx)
|
|
logFile = logPath + "/taosdlog.0"
|
|
return findLog(logFile, key, retry)
|
|
|
|
|
|
# find string in log
|
|
def findLog(logFile, key, retry = 60):
|
|
for i in range(retry):
|
|
cmd = f"grep '{key}' {logFile} | wc -l"
|
|
output, error = run(cmd)
|
|
try:
|
|
cnt = int(output)
|
|
if cnt > 0:
|
|
return cnt
|
|
except ValueError:
|
|
print(f"cmd ={cmd} out={output} err={error}")
|
|
print(f"not found string: {key} in {logFile} , retry again {i} ...")
|
|
time.sleep(1)
|
|
return 0
|
|
|
|
# find thread in taosd, return thread count
|
|
def findTaosdThread(threadName):
|
|
"""
|
|
Find the first process named 'taosd' and count the number of threads
|
|
that contain the specified thread name.
|
|
|
|
Args:
|
|
threadName (str): Thread name to search for (supports partial matching)
|
|
|
|
Returns:
|
|
int: Number of matching threads in the first taosd process found,
|
|
returns 0 if no taosd process is found or no threads match.
|
|
"""
|
|
# Step 1: Find all processes named "taosd"
|
|
taosd_pids = []
|
|
|
|
# Iterate through all process directories in /proc
|
|
for pid_dir in glob.glob('/proc/[0-9]*'):
|
|
try:
|
|
# Extract PID from directory path
|
|
pid = os.path.basename(pid_dir)
|
|
|
|
# Read process command name
|
|
with open(os.path.join(pid_dir, 'comm'), 'r') as f:
|
|
comm = f.read().strip()
|
|
|
|
# Check if process is taosd
|
|
if comm == 'taosd':
|
|
taosd_pids.append(pid)
|
|
except:
|
|
# Skip inaccessible process directories
|
|
continue
|
|
|
|
# Return 0 if no taosd process is found
|
|
if not taosd_pids:
|
|
return 0
|
|
|
|
# Step 2: Get thread information for the first taosd process
|
|
taosd_pid = taosd_pids[0]
|
|
task_dir = os.path.join('/proc', taosd_pid, 'task')
|
|
|
|
# Count threads matching the specified name
|
|
thread_count = 0
|
|
|
|
# Iterate through all threads of the process
|
|
for tid in os.listdir(task_dir):
|
|
try:
|
|
# Read thread command name
|
|
comm_file = os.path.join(task_dir, tid, 'comm')
|
|
if os.path.exists(comm_file):
|
|
with open(comm_file, 'r') as f:
|
|
thread_comm = f.read().strip()
|
|
|
|
# Check if thread name contains the search string
|
|
if threadName in thread_comm:
|
|
thread_count += 1
|
|
except:
|
|
# Skip inaccessible thread directories
|
|
continue
|
|
|
|
return thread_count
|
|
|
|
|
|
def check_db_cachemodel(dbname, cachemodel, retry=10):
|
|
"""
|
|
Check whether the database's cachemodel matches the expected value.
|
|
|
|
Args:
|
|
dbname: Database name.
|
|
cachemodel: Expected cachemodel value.
|
|
retry: Max retry count, default 10.
|
|
|
|
Returns:
|
|
bool: True if matched, exit otherwise.
|
|
"""
|
|
sql = (
|
|
f"select `cachemodel` from information_schema.ins_databases "
|
|
f"where name = '{dbname}'"
|
|
)
|
|
actual = None
|
|
for _ in range(retry):
|
|
tdSql.query(sql)
|
|
if tdSql.queryRows > 0:
|
|
actual = tdSql.queryResult[0][0]
|
|
if str(actual) == str(cachemodel):
|
|
tdLog.info(f"Database {dbname} cachemodel is {cachemodel}")
|
|
return True
|
|
time.sleep(1)
|
|
tdLog.exit(f"Database {dbname} cachemodel is {actual}, expected {cachemodel}")
|
|
|
|
|
|
def check_explain_last_row_scan(sql, retry=10):
|
|
"""
|
|
Check whether the explain plan contains "Last row scan" and
|
|
does not contain "table scan".
|
|
|
|
Args:
|
|
sql: SELECT statement to explain (e.g. "select LAST_ROW(c1) from db.tb").
|
|
retry: Max retry count, default 10.
|
|
|
|
Returns:
|
|
bool: True if plan contains "Last row scan" and does not contain
|
|
"table scan"; exit after retry attempts otherwise.
|
|
"""
|
|
explain_sql = (
|
|
f"explain {sql.rstrip(';')}"
|
|
if not sql.strip().lower().startswith("explain")
|
|
else sql
|
|
)
|
|
for _ in range(retry):
|
|
tdSql.query(explain_sql)
|
|
plan_parts = []
|
|
for row in (tdSql.queryResult or []):
|
|
for cell in row:
|
|
if cell is not None:
|
|
plan_parts.append(str(cell))
|
|
plan_text = " ".join(plan_parts)
|
|
plan_lower = plan_text.lower()
|
|
has_last_row_scan = "last row scan" in plan_lower
|
|
has_table_scan = "table scan" in plan_lower
|
|
if has_last_row_scan and not has_table_scan:
|
|
tdLog.info("Explain plan contains 'Last row scan' and does not contain 'table scan'")
|
|
return True
|
|
time.sleep(1)
|
|
tdLog.exit("Explain plan does not contain 'Last row scan' or contains 'table scan'")
|