final save code
@@ -0,0 +1,162 @@
|
||||
import requests
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
import argparse
|
||||
from threading import Lock
|
||||
|
||||
# 全局计数器
|
||||
class RequestCounter:
|
||||
def __init__(self):
|
||||
self.sent = 0
|
||||
self.in_progress = 0
|
||||
self.completed = 0
|
||||
self.success = 0
|
||||
self.failed = 0
|
||||
self.lock = Lock()
|
||||
|
||||
def increment_sent(self):
|
||||
with self.lock:
|
||||
self.sent += 1
|
||||
|
||||
def increment_in_progress(self):
|
||||
with self.lock:
|
||||
self.in_progress += 1
|
||||
|
||||
def decrement_in_progress(self):
|
||||
with self.lock:
|
||||
self.in_progress -= 1
|
||||
|
||||
def increment_completed(self):
|
||||
with self.lock:
|
||||
self.completed += 1
|
||||
|
||||
def increment_success(self):
|
||||
with self.lock:
|
||||
self.success += 1
|
||||
|
||||
def increment_failed(self):
|
||||
with self.lock:
|
||||
self.failed += 1
|
||||
|
||||
def get_stats(self):
|
||||
with self.lock:
|
||||
return {
|
||||
'sent': self.sent,
|
||||
'in_progress': self.in_progress,
|
||||
'completed': self.completed,
|
||||
'success': self.success,
|
||||
'failed': self.failed
|
||||
}
|
||||
|
||||
counter = RequestCounter()
|
||||
|
||||
|
||||
def send_request(url, headers, data):
|
||||
"""发送单个请求"""
|
||||
try:
|
||||
# 更新计数器
|
||||
counter.increment_sent()
|
||||
counter.increment_in_progress()
|
||||
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
|
||||
# 根据响应状态更新计数器
|
||||
if response.status_code == 200:
|
||||
counter.increment_success()
|
||||
else:
|
||||
counter.increment_failed()
|
||||
|
||||
return response.status_code, response.text
|
||||
except Exception as e:
|
||||
counter.increment_failed()
|
||||
return None, str(e)
|
||||
finally:
|
||||
counter.decrement_in_progress()
|
||||
counter.increment_completed()
|
||||
|
||||
def print_stats():
|
||||
"""打印实时统计信息"""
|
||||
while True:
|
||||
stats = counter.get_stats()
|
||||
print(f"\rStats - Sent: {stats['sent']}, In Progress: {stats['in_progress']}, "
|
||||
f"Completed: {stats['completed']} (Success: {stats['success']}, Failed: {stats['failed']})",
|
||||
end="", flush=True)
|
||||
time.sleep(10)
|
||||
|
||||
def worker(url, headers, data, qps, duration):
|
||||
"""工作线程函数,控制QPS"""
|
||||
requests_count = 0
|
||||
start_time = time.time()
|
||||
end_time = start_time + duration
|
||||
|
||||
while time.time() < end_time:
|
||||
request_start = time.time()
|
||||
status_code, response_text = send_request(url, headers, data)
|
||||
requests_count += 1
|
||||
|
||||
# 控制QPS
|
||||
elapsed = time.time() - request_start
|
||||
sleep_time = max(0, (1.0 / qps) - elapsed)
|
||||
if sleep_time > 0:
|
||||
time.sleep(sleep_time)
|
||||
|
||||
return requests_count
|
||||
|
||||
def run_test(url, headers, data, qps, duration, concurrency):
|
||||
"""运行测试"""
|
||||
total_requests = 0
|
||||
start_time = time.time()
|
||||
|
||||
# 启动统计信息打印线程
|
||||
import threading
|
||||
stats_thread = threading.Thread(target=print_stats, daemon=True)
|
||||
stats_thread.start()
|
||||
|
||||
with ThreadPoolExecutor(max_workers=concurrency) as executor:
|
||||
futures = [executor.submit(worker, url, headers, data, qps, duration)
|
||||
for _ in range(concurrency)]
|
||||
|
||||
for future in as_completed(futures):
|
||||
total_requests += future.result()
|
||||
|
||||
end_time = time.time()
|
||||
actual_duration = end_time - start_time
|
||||
actual_qps = total_requests / actual_duration
|
||||
|
||||
# 获取最终统计
|
||||
stats = counter.get_stats()
|
||||
|
||||
print("\n\nTest Summary:")
|
||||
print(f"Total requests: {total_requests}")
|
||||
print(f" Success: {stats['success']}")
|
||||
print(f" Failed: {stats['failed']}")
|
||||
print(f"Success rate: {(stats['success']/total_requests)*100:.2f}%")
|
||||
print(f"Test duration: {actual_duration:.2f} seconds")
|
||||
print(f"Actual QPS: {actual_qps:.2f}")
|
||||
print(f"Target QPS: {qps}")
|
||||
print(f"Concurrency: {concurrency}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='API Load Test Tool')
|
||||
parser.add_argument('--qps', type=float, default=1, help='Requests per second (per thread)')
|
||||
parser.add_argument('--duration', type=float, default=10, help='Test duration in seconds')
|
||||
parser.add_argument('--concurrency', type=int, default=2, help='Number of concurrent threads')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 配置请求参数
|
||||
url = 'http://mq.aidigifi.meidaojia.com/hairColor/v2'
|
||||
# url = 'https://692139771842565-http-8801.northwest1.gpugeek.com:8443/hairColor/v2'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
data = {
|
||||
"img": "data:image/jpeg;base64,/9j/4AAQSkZJ....Euy+sn/oZrKn8CPlK2qR/9k=",
|
||||
"userId": "18701620166",
|
||||
"rgb":[77,99,0],
|
||||
"ratio": 0.9,
|
||||
"output_format": "base64"
|
||||
}
|
||||
|
||||
print(f"Starting test with QPS={args.qps}, duration={args.duration}s, concurrency={args.concurrency}")
|
||||
run_test(url, headers, data, args.qps, args.duration, args.concurrency)
|
||||
@@ -0,0 +1,203 @@
|
||||
import requests
|
||||
import time
|
||||
import base64
|
||||
import random
|
||||
import string
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
import argparse
|
||||
from threading import Lock
|
||||
|
||||
# 全局计数器
|
||||
class RequestCounter:
|
||||
def __init__(self):
|
||||
self.sent = 0
|
||||
self.in_progress = 0
|
||||
self.completed = 0
|
||||
self.success = 0
|
||||
self.failed = 0
|
||||
self.lock = Lock()
|
||||
|
||||
def increment_sent(self):
|
||||
with self.lock:
|
||||
self.sent += 1
|
||||
|
||||
def increment_in_progress(self):
|
||||
with self.lock:
|
||||
self.in_progress += 1
|
||||
|
||||
def decrement_in_progress(self):
|
||||
with self.lock:
|
||||
self.in_progress -= 1
|
||||
|
||||
def increment_completed(self):
|
||||
with self.lock:
|
||||
self.completed += 1
|
||||
|
||||
def increment_success(self):
|
||||
with self.lock:
|
||||
self.success += 1
|
||||
|
||||
def increment_failed(self):
|
||||
with self.lock:
|
||||
self.failed += 1
|
||||
|
||||
def get_stats(self):
|
||||
with self.lock:
|
||||
return {
|
||||
'sent': self.sent,
|
||||
'in_progress': self.in_progress,
|
||||
'completed': self.completed,
|
||||
'success': self.success,
|
||||
'failed': self.failed
|
||||
}
|
||||
|
||||
counter = RequestCounter()
|
||||
|
||||
def generate_random_task_id(length=19):
|
||||
"""生成随机task_id"""
|
||||
digits = string.digits
|
||||
return ''.join(random.choice(digits) for _ in range(length))
|
||||
|
||||
def send_request(url, headers, data):
|
||||
"""发送单个请求"""
|
||||
try:
|
||||
# 更新计数器
|
||||
counter.increment_sent()
|
||||
counter.increment_in_progress()
|
||||
|
||||
# 每次请求生成新的随机task_id
|
||||
data["task_id"] = generate_random_task_id()
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
|
||||
# 根据响应状态更新计数器
|
||||
if response.status_code == 200:
|
||||
counter.increment_success()
|
||||
else:
|
||||
counter.increment_failed()
|
||||
|
||||
return response.status_code, response.text
|
||||
except Exception as e:
|
||||
counter.increment_failed()
|
||||
return None, str(e)
|
||||
finally:
|
||||
counter.decrement_in_progress()
|
||||
counter.increment_completed()
|
||||
|
||||
def print_stats():
|
||||
"""打印实时统计信息"""
|
||||
while True:
|
||||
stats = counter.get_stats()
|
||||
print(f"\rStats - Sent: {stats['sent']}, In Progress: {stats['in_progress']}, "
|
||||
f"Completed: {stats['completed']} (Success: {stats['success']}, Failed: {stats['failed']})",
|
||||
end="", flush=True)
|
||||
time.sleep(10)
|
||||
|
||||
def worker(url, headers, data, qps, duration):
|
||||
"""工作线程函数,控制QPS"""
|
||||
requests_count = 0
|
||||
start_time = time.time()
|
||||
end_time = start_time + duration
|
||||
|
||||
while time.time() < end_time:
|
||||
request_start = time.time()
|
||||
status_code, response_text = send_request(url, headers, data)
|
||||
requests_count += 1
|
||||
|
||||
# 控制QPS
|
||||
elapsed = time.time() - request_start
|
||||
sleep_time = max(0, (1.0 / qps) - elapsed)
|
||||
if sleep_time > 0:
|
||||
time.sleep(sleep_time)
|
||||
|
||||
return requests_count
|
||||
|
||||
def run_test(url, headers, data, qps, duration, concurrency):
|
||||
"""运行测试"""
|
||||
total_requests = 0
|
||||
start_time = time.time()
|
||||
|
||||
# 启动统计信息打印线程
|
||||
import threading
|
||||
stats_thread = threading.Thread(target=print_stats, daemon=True)
|
||||
stats_thread.start()
|
||||
|
||||
with ThreadPoolExecutor(max_workers=concurrency) as executor:
|
||||
futures = [executor.submit(worker, url, headers, data, qps, duration)
|
||||
for _ in range(concurrency)]
|
||||
|
||||
for future in as_completed(futures):
|
||||
total_requests += future.result()
|
||||
|
||||
end_time = time.time()
|
||||
actual_duration = end_time - start_time
|
||||
actual_qps = total_requests / actual_duration
|
||||
|
||||
# 获取最终统计
|
||||
stats = counter.get_stats()
|
||||
|
||||
print("\n\nTest Summary:")
|
||||
print(f"Total requests: {total_requests}")
|
||||
print(f" Success: {stats['success']}")
|
||||
print(f" Failed: {stats['failed']}")
|
||||
print(f"Success rate: {(stats['success']/total_requests)*100:.2f}%")
|
||||
print(f"Test duration: {actual_duration:.2f} seconds")
|
||||
print(f"Actual QPS: {actual_qps:.2f}")
|
||||
print(f"Target QPS: {qps}")
|
||||
print(f"Concurrency: {concurrency}")
|
||||
|
||||
|
||||
def image_to_base64(file_path, mime_type=None):
|
||||
"""
|
||||
将图片文件转换为带Base64前缀的Data URI字符串
|
||||
|
||||
参数:
|
||||
file_path (str): 图片文件路径
|
||||
mime_type (str): 可选,指定MIME类型。如果为None,则根据文件扩展名自动判断
|
||||
|
||||
返回:
|
||||
str: 带Base64前缀的Data URI字符串
|
||||
"""
|
||||
# 如果没有指定MIME类型,根据文件扩展名推断
|
||||
if mime_type is None:
|
||||
extension = file_path.split('.')[-1].lower()
|
||||
mime_types = {
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'webp': 'image/webp',
|
||||
'bmp': 'image/bmp'
|
||||
}
|
||||
mime_type = mime_types.get(extension, 'application/octet-stream')
|
||||
|
||||
# 读取文件内容并编码为Base64
|
||||
with open(file_path, 'rb') as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
|
||||
|
||||
# 组合成Data URI格式
|
||||
return f"data:{mime_type};base64,{encoded_string}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='API Load Test Tool')
|
||||
parser.add_argument('--qps', type=float, default=1, help='Requests per second (per thread)')
|
||||
parser.add_argument('--duration', type=float, default=10, help='Test duration in seconds')
|
||||
parser.add_argument('--concurrency', type=int, default=2, help='Number of concurrent threads')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 配置请求参数
|
||||
url = 'http://mq.aidigifi.meidaojia.com/api/swapHair/v1'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
data = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "1907651680352395265", # 会被每次请求覆盖
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/user2_1_%E5%89%AF%E6%9C%AC.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
|
||||
img64_str = image_to_base64("aaa.jpg")
|
||||
data['img'] = img64_str
|
||||
|
||||
print(f"Starting test with QPS={args.qps}, duration={args.duration}s, concurrency={args.concurrency}")
|
||||
run_test(url, headers, data, args.qps, args.duration, args.concurrency)
|
||||
@@ -0,0 +1,228 @@
|
||||
import time
|
||||
import logging
|
||||
import redis
|
||||
import uuid
|
||||
import json
|
||||
from flask import Flask, request, jsonify
|
||||
from datetime import datetime, timedelta
|
||||
from config import QUEUE_NAME, DEFAULT_TIMEOUT, KEY_QUEUE_LOCK_NAME, acquire_lock
|
||||
|
||||
# 创建 logger
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO) # 设置 logger 的级别
|
||||
|
||||
|
||||
# 获取当前日期和时间
|
||||
now = datetime.now()
|
||||
|
||||
# 格式化为字符串(例如:2023-10-25 14:30:45)
|
||||
date_time_str = now.strftime("%Y-%m-%d_%H:%M:%S")
|
||||
|
||||
# 创建文件 handler
|
||||
file_handler = logging.FileHandler(f'/var/log/meidaojia/app_{date_time_str}.log')
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(file_formatter)
|
||||
|
||||
# 创建控制台 handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
console_handler.setFormatter(console_formatter)
|
||||
|
||||
# 添加 handlers 到 logger
|
||||
logger.addHandler(file_handler)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# 配置
|
||||
from config import REDIS_HOST
|
||||
from config import REDIS_PORT
|
||||
from config import REDIS_DB
|
||||
|
||||
|
||||
# 创建 Redis 连接池
|
||||
redis_pool = redis.ConnectionPool(
|
||||
host=REDIS_HOST,
|
||||
port=REDIS_PORT,
|
||||
db=REDIS_DB,
|
||||
max_connections=2000 # 根据实际情况调整
|
||||
)
|
||||
|
||||
def get_redis_conn():
|
||||
"""获取 Redis 连接"""
|
||||
return redis.Redis(connection_pool=redis_pool)
|
||||
|
||||
get_redis_conn().set(QUEUE_NAME, "[]")
|
||||
|
||||
def pushStr2Queue(key, data_str):
|
||||
redis_conn = get_redis_conn()
|
||||
lock = acquire_lock(redis_conn, KEY_QUEUE_LOCK_NAME)
|
||||
if lock:
|
||||
try:
|
||||
redis_conn.set(key, data_str, ex=60)
|
||||
key_queue_str = redis_conn.get(QUEUE_NAME).decode("utf-8")
|
||||
key_queue = json.loads(key_queue_str)
|
||||
key_queue.append(key)
|
||||
logger.info(f"pushStr2Queue queue len:{key_queue}")
|
||||
key_queue_str = json.dumps(key_queue)
|
||||
redis_conn.set(QUEUE_NAME, key_queue_str)
|
||||
finally:
|
||||
lock.release()
|
||||
else:
|
||||
logger.error(f"pushStr2Queue get lock error {data_str}")
|
||||
|
||||
|
||||
|
||||
|
||||
@app.route('/hairColor/v2', methods=['POST'])
|
||||
def api_hairColor_v2():
|
||||
# 获取请求参数
|
||||
data = request.get_json()
|
||||
if not data or 'img' not in data:
|
||||
return jsonify({'msg': 'Missing img parameter', "state":-1, "data":"" }), 400
|
||||
|
||||
if not data or 'rgb' not in data:
|
||||
return jsonify({'msg': 'Missing "rgb": parameter', "state":-1, "data":""}), 400
|
||||
|
||||
if not data or 'ratio' not in data:
|
||||
return jsonify({'msg': 'Missing "ratio": parameter', "state":-1, "data":""}), 400
|
||||
|
||||
if not data or 'output_format' not in data:
|
||||
return jsonify({'msg': 'Missing "output_format": parameter', "state":-1, "data":""}), 400
|
||||
|
||||
|
||||
key = str(uuid.uuid4())
|
||||
redis_conn = get_redis_conn()
|
||||
|
||||
# if len(data['img']) > 256:
|
||||
# redis_conn.set(f"img_{key}", data['img'], ex=60)
|
||||
# data['img'] = "base64"
|
||||
|
||||
task_data = {}
|
||||
task_data['key'] = key
|
||||
task_data['api'] = "/hairColor/v2"
|
||||
task_data['request'] = data
|
||||
logger.info(f"request hairColor/v2 img:{data['img'][:64]}, rgb:{data['rgb']}, ratio:{data['ratio']} output_format:{data['output_format']}")
|
||||
task_data_str = json.dumps(task_data)
|
||||
pushStr2Queue(key, task_data_str)
|
||||
timeout = DEFAULT_TIMEOUT
|
||||
start_time = datetime.now()
|
||||
result_key = f"result_{key}"
|
||||
result_status_key = f"result_status_code_{key}"
|
||||
while (datetime.now() - start_time).seconds < timeout:
|
||||
if redis_conn.exists(result_key):
|
||||
result_str = redis_conn.get(result_key)
|
||||
return result_str, int(redis_conn.get(result_status_key)), {'Content-Type': 'application/json'}
|
||||
time.sleep(0.1)
|
||||
|
||||
logger.error(f"request hairColor time out ")
|
||||
return jsonify({
|
||||
'msg': f'Timeout after {timeout} seconds, http time out'
|
||||
, "state":-1, "data":""
|
||||
}), 408
|
||||
|
||||
|
||||
@app.route('/api/swapHair/v1', methods=['POST'])
|
||||
def api_swapHair_v1():
|
||||
if not request.is_json:
|
||||
return jsonify({"error": "Request must be JSON"}), 400
|
||||
# 获取请求参数
|
||||
data = request.get_json()
|
||||
if not data or 'hair_id' not in data:
|
||||
return jsonify({'msg': 'Missing hair_id parameter', "state":-1, "data":"" }), 400
|
||||
if not data or 'task_id' not in data:
|
||||
return jsonify({'msg': 'Missing task_id parameter', "state":-1, "data":""}), 400
|
||||
if not data or 'user_img_path' not in data:
|
||||
return jsonify({'msg': 'Missing user_img_path parameter', "state":-1, "data":""}), 400
|
||||
if not data or 'is_hr' not in data:
|
||||
return jsonify({'msg': 'Missing is_hr parameter', "state":-1, "data":""}), 400
|
||||
|
||||
if not data or 'output_format' not in data:
|
||||
return jsonify({'msg': 'Missing output_format parameter', "state":-1, "data":""}), 400
|
||||
|
||||
redis_conn = get_redis_conn()
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
# if len(data['user_img_path']) > 256:
|
||||
# redis_conn.set(f"img_{key}", data['user_img_path'], ex=60)
|
||||
# data['user_img_path'] = "base64"
|
||||
|
||||
task_data = {}
|
||||
task_data['key'] = key
|
||||
task_data['api'] = "/api/swapHair/v1"
|
||||
task_data['request'] = data
|
||||
|
||||
logger.info(f"request /api/swapHair/v1 user_img_path:{data['user_img_path'][:64]} hair_id:{data['hair_id']}")
|
||||
|
||||
task_data_str = json.dumps(task_data)
|
||||
pushStr2Queue(key, task_data_str)
|
||||
timeout = DEFAULT_TIMEOUT
|
||||
start_time = datetime.now()
|
||||
result_key = f"result_{key}"
|
||||
result_status_key = f"result_status_code_{key}"
|
||||
while (datetime.now() - start_time).seconds < timeout:
|
||||
if redis_conn.exists(result_key):
|
||||
result_str = redis_conn.get(result_key)
|
||||
return result_str, int(redis_conn.get(result_status_key)), {'Content-Type': 'application/json'}
|
||||
time.sleep(0.1)
|
||||
|
||||
logger.error(f"request /api/swapHair/v1 time out")
|
||||
return jsonify({
|
||||
'msg': f'Timeout after {timeout} seconds, http time out'
|
||||
, "state":-1, "data":""
|
||||
}), 408
|
||||
|
||||
|
||||
|
||||
# @app.route('/api/uploadHair/v1', methods=['POST'])
|
||||
# def api_uploadHair_v1():
|
||||
# # 获取请求参数
|
||||
# data = request.get_json()
|
||||
# if not data or 'img_lists' not in data:
|
||||
# return jsonify({'msg': 'Missing img_lists parameter', "state":-1, "data":"" }), 400
|
||||
# if not data or '"hair_id": ' not in data:
|
||||
# return jsonify({'msg': 'Missing "hair_id": parameter', "state":-1, "data":""}), 400
|
||||
|
||||
# if not data or 'output_format' not in data:
|
||||
# logger.warning(f"api_swapHair_v1 no output_format task_id:{data['task_id']}")
|
||||
|
||||
|
||||
# key = str(uuid.uuid4())
|
||||
# redis_conn = get_redis_conn()
|
||||
# task_data = {}
|
||||
# task_data['key'] = key
|
||||
# task_data['api'] = "/api/uploadHair/v1"
|
||||
# task_data['request'] = data
|
||||
# logger.info(f"request api_uploadHair_v1 task_data: {json.dumps(task_data)}")
|
||||
# task_data_str = json.dumps(task_data)
|
||||
# pushStr2Queue(task_data_str)
|
||||
# timeout = DEFAULT_TIMEOUT
|
||||
# start_time = datetime.now()
|
||||
# result_key = f"result_{key}"
|
||||
# while (datetime.now() - start_time).seconds < timeout:
|
||||
# if redis_conn.exists(result_key):
|
||||
# result_str = redis_conn.get(result_key)
|
||||
# result = json.loads(result_str)
|
||||
# if result['state'] == 0:
|
||||
# return jsonify(result), 200
|
||||
# else:
|
||||
# return jsonify(result), 500
|
||||
# time.sleep(0.1)
|
||||
|
||||
# logger.error(f"request api_uploadHair_v1 time out")
|
||||
# return jsonify({
|
||||
# 'msg': f'Timeout after {timeout} seconds, http time out'
|
||||
# , "state":-1, "data":""
|
||||
# }), 408
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 启动Flask应用,启用多线程处理
|
||||
app.run(
|
||||
host='0.0.0.0',
|
||||
port=80,
|
||||
threaded=True, # 启用多线程处理并发请求
|
||||
debug=False # 生产环境应设置为False
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 定义连接参数
|
||||
HOST="northwest1.gpugeek.com"
|
||||
PORT="55139"
|
||||
USER="root"
|
||||
PASSWORD="uaUMSzmAHyka2ZcBvQHZ5My9PUNRP78T"
|
||||
TARGET_DIR="/root/project/hair_service_sd"
|
||||
COMMAND="if ! pgrep -f 'run_copy_cost_colorb64.py'; then bash start_services.sh; fi"
|
||||
|
||||
# 使用expect工具自动化交互过程
|
||||
/usr/bin/expect <<EOF
|
||||
set timeout 30
|
||||
|
||||
# 启动ssh连接
|
||||
spawn ssh -p $PORT $USER@$HOST
|
||||
|
||||
expect {
|
||||
# 处理首次连接的确认提示
|
||||
"you sure you want to continue connecting" {
|
||||
send "yes\r"
|
||||
exp_continue
|
||||
}
|
||||
# 匹配各种可能的密码提示形式
|
||||
"*password:" {
|
||||
send "$PASSWORD\r"
|
||||
}
|
||||
"Password:" {
|
||||
send "$PASSWORD\r"
|
||||
}
|
||||
"*assword:" {
|
||||
send "$PASSWORD\r"
|
||||
}
|
||||
}
|
||||
|
||||
# 等待登录成功提示
|
||||
expect {
|
||||
"*\$ " {}
|
||||
"*# " {}
|
||||
}
|
||||
|
||||
# 进入目标目录
|
||||
send "cd $TARGET_DIR\r"
|
||||
expect {
|
||||
"*\$ " {}
|
||||
"*# " {}
|
||||
}
|
||||
|
||||
# 执行检查进程并启动服务的命令
|
||||
send "$COMMAND\r"
|
||||
|
||||
# 等待命令执行完成
|
||||
expect {
|
||||
"*\$ " {
|
||||
send "exit\r"
|
||||
}
|
||||
"*# " {
|
||||
send "exit\r"
|
||||
}
|
||||
timeout {
|
||||
send "exit\r"
|
||||
}
|
||||
}
|
||||
|
||||
# 等待ssh会话结束
|
||||
expect eof
|
||||
EOF
|
||||
|
||||
echo "自动化任务已完成"
|
||||
@@ -0,0 +1,68 @@
|
||||
import time
|
||||
from redis.lock import Lock
|
||||
import logging
|
||||
|
||||
QUEUE_NAME = 'task_queue'
|
||||
# 配置
|
||||
REDIS_HOST = 'r-2zevknj33pkqrrbzjp.redis.rds.aliyuncs.com'
|
||||
REDIS_PORT = 6379
|
||||
REDIS_DB = 0
|
||||
DEFAULT_TIMEOUT = (60+10)
|
||||
GPU_SERVER_TIME_OUT = 60
|
||||
GPU_SERVER_LIST = 'gpu_server_list_key'
|
||||
SERVER_LOG_ = "server_log"
|
||||
SERVER_LOG_EVENT_LEN = 20
|
||||
KEY_QUEUE_LOCK_NAME = 'KEY_QUEUE_LOCK_NAME'
|
||||
SERVER_LIST_LOCK_NAME = 'SERVER_LIST_LOCK_NAME'
|
||||
SERVER_LIST_LOG_LOCK_NAME = 'SERVER_LIST_LOG_LOCK_NAME'
|
||||
|
||||
# 创建 logger
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO) # 设置 logger 的级别
|
||||
|
||||
# 创建文件 handler
|
||||
file_handler = logging.FileHandler('/var/log/meidaojia/config.log')
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(file_formatter)
|
||||
|
||||
# 创建控制台 handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
console_handler.setFormatter(console_formatter)
|
||||
|
||||
# 添加 handlers 到 logger
|
||||
logger.addHandler(file_handler)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
def acquire_lock(redis_client, lock_name):
|
||||
lock = Lock(
|
||||
redis_client,
|
||||
name=lock_name,
|
||||
timeout=30,
|
||||
blocking_timeout=10
|
||||
)
|
||||
acquired = lock.acquire()
|
||||
if acquired:
|
||||
return lock
|
||||
return None
|
||||
|
||||
|
||||
|
||||
# def acquire_lock(r, acquire_timeout=10):
|
||||
# """获取分布式锁"""
|
||||
# end = time.time() + acquire_timeout
|
||||
# while time.time() < end:
|
||||
# # 使用 SETNX 尝试获取锁
|
||||
# if r.setnx(TASK_REDIS_LOCK_NAME, 1):
|
||||
# # 设置过期时间,防止死锁
|
||||
# r.expire(TASK_REDIS_LOCK_NAME, 10)
|
||||
# return True
|
||||
# time.sleep(0.1)
|
||||
# return False
|
||||
# # return True
|
||||
|
||||
# def release_lock(r):
|
||||
# """释放分布式锁"""
|
||||
# r.delete(TASK_REDIS_LOCK_NAME)
|
||||
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 646 KiB |
|
After Width: | Height: | Size: 706 KiB |
|
After Width: | Height: | Size: 654 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 151 KiB |
@@ -0,0 +1,84 @@
|
||||
import time
|
||||
import logging
|
||||
import redis
|
||||
import uuid
|
||||
import json
|
||||
from flask import Flask, request, jsonify, send_from_directory
|
||||
from datetime import datetime, timedelta
|
||||
from config import QUEUE_NAME, DEFAULT_TIMEOUT
|
||||
from flask_cors import CORS
|
||||
GPU_SERVER_LIST = 'gpu_server_list_key'
|
||||
|
||||
# 创建 logger
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO) # 设置 logger 的级别
|
||||
|
||||
# 创建文件 handler
|
||||
file_handler = logging.FileHandler('/var/log/meidaojia/app.log')
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(file_formatter)
|
||||
|
||||
# 创建控制台 handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
console_handler.setFormatter(console_formatter)
|
||||
|
||||
# 添加 handlers 到 logger
|
||||
logger.addHandler(file_handler)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
monitor = Flask(__name__)
|
||||
CORS(monitor)
|
||||
|
||||
# 配置
|
||||
from config import REDIS_HOST
|
||||
from config import REDIS_PORT
|
||||
from config import REDIS_DB
|
||||
from config import SERVER_LOG_
|
||||
|
||||
# 创建 Redis 连接池
|
||||
redis_pool = redis.ConnectionPool(
|
||||
host=REDIS_HOST,
|
||||
port=REDIS_PORT,
|
||||
db=REDIS_DB,
|
||||
max_connections=2000 # 根据实际情况调整
|
||||
)
|
||||
|
||||
def get_redis_conn():
|
||||
"""获取 Redis 连接"""
|
||||
return redis.Redis(connection_pool=redis_pool)
|
||||
|
||||
@monitor.route('/get_server_state', methods=['GET', 'POST'])
|
||||
def get_server_state():
|
||||
monitor_data = []
|
||||
redis_conn = get_redis_conn()
|
||||
server_list_str = redis_conn.get(GPU_SERVER_LIST).decode("utf-8")
|
||||
server_list = json.loads(server_list_str)
|
||||
for name in server_list:
|
||||
server_str = redis_conn.get(name).decode("utf-8")
|
||||
server = json.loads(server_str)
|
||||
|
||||
server_log_name = f"{SERVER_LOG_}{name}"
|
||||
server_log_str = redis_conn.get(server_log_name).decode("utf-8")
|
||||
server_log = json.loads(server_log_str)
|
||||
server_log['busy'] = not server['can_use']
|
||||
monitor_data.append(server_log)
|
||||
json_str = json.dumps(monitor_data)
|
||||
print(len(json_str))
|
||||
return json_str, 200
|
||||
|
||||
|
||||
@monitor.route('/')
|
||||
def serve_index():
|
||||
return send_from_directory('static', 'index.html')
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 启动Flask应用,启用多线程处理
|
||||
monitor.run(
|
||||
host='0.0.0.0',
|
||||
port=5001,
|
||||
threaded=True, # 启用多线程处理并发请求
|
||||
debug=False # 生产环境应设置为False
|
||||
)
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 定义目标目录和检查的进程名
|
||||
TARGET_DIR="/root/project/hair_service_sd"
|
||||
PROCESS_NAME="run_copy_cost_colorb64.py"
|
||||
START_COMMAND="bash start_services.sh"
|
||||
|
||||
# 读取servers.csv文件并逐行处理
|
||||
while IFS=, read -r name ssh_cmd password _; do
|
||||
# 去除可能存在的空格和引号
|
||||
name=$(echo "$name" | xargs)
|
||||
ssh_cmd=$(echo "$ssh_cmd" | xargs)
|
||||
password=$(echo "$password" | xargs)
|
||||
|
||||
echo "..................................正在处理服务器: $name ........................................"
|
||||
|
||||
# 从ssh命令中提取端口和主机
|
||||
port=$(echo "$ssh_cmd" | grep -oP '\-p\s*\K\d+')
|
||||
host=$(echo "$ssh_cmd" | grep -oP 'root@\K[^\s]+')
|
||||
|
||||
# 使用expect工具自动化交互过程
|
||||
/usr/bin/expect <<EOF
|
||||
set timeout 30
|
||||
|
||||
# 启动ssh连接
|
||||
spawn $ssh_cmd
|
||||
|
||||
expect {
|
||||
# 处理首次连接的确认提示
|
||||
"you sure you want to continue connecting" {
|
||||
send "yes\r"
|
||||
exp_continue
|
||||
}
|
||||
# 匹配各种可能的密码提示形式
|
||||
"*password:" {
|
||||
send "$password\r"
|
||||
}
|
||||
"Password:" {
|
||||
send "$password\r"
|
||||
}
|
||||
"*assword:" {
|
||||
send "$password\r"
|
||||
}
|
||||
}
|
||||
|
||||
# 等待登录成功提示
|
||||
expect {
|
||||
"*\$ " {}
|
||||
"*# " {}
|
||||
}
|
||||
|
||||
# 进入目标目录
|
||||
send "cd $TARGET_DIR\r"
|
||||
expect {
|
||||
"*\$ " {}
|
||||
"*# " {}
|
||||
}
|
||||
|
||||
# 检查进程并决定是否启动服务
|
||||
send "if ! pgrep -f '$PROCESS_NAME'; then echo '启动服务...'; $START_COMMAND; else echo '进程已在运行,跳过启动'; fi\r"
|
||||
|
||||
# 等待命令执行完成
|
||||
expect {
|
||||
"*\$ " {
|
||||
send "exit\r"
|
||||
}
|
||||
"*# " {
|
||||
send "exit\r"
|
||||
}
|
||||
timeout {
|
||||
send "exit\r"
|
||||
}
|
||||
}
|
||||
|
||||
# 等待ssh会话结束
|
||||
expect eof
|
||||
EOF
|
||||
|
||||
echo "已完成服务器: $name"
|
||||
# 添加延时
|
||||
echo "--------------------------------------------- 等待 ----------------------------------------------"
|
||||
sleep 5
|
||||
done < servers_test.csv
|
||||
|
||||
echo "所有服务器处理完成"
|
||||
@@ -0,0 +1,126 @@
|
||||
import threading
|
||||
import requests
|
||||
import json
|
||||
import base64
|
||||
|
||||
url = 'http://mq.aidigifi.meidaojia.com/api/swapHair/v1'
|
||||
# url = 'https://692139771842565-http-8801.northwest1.gpugeek.com:8443/api/swapHair/v1'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
data = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "1907651680352395265",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/user2_1_%E5%89%AF%E6%9C%AC.JPG",
|
||||
# "user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_8.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
|
||||
more_data = []
|
||||
more_data.append(data)
|
||||
|
||||
data1 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "2345623451234",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_8.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data1)
|
||||
|
||||
data2 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "3145645324",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_7.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data2)
|
||||
|
||||
data3 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "456412345",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_6.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data3)
|
||||
|
||||
data4 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "45623445",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_5.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data4)
|
||||
|
||||
data5 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "2345867435",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_4.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data5)
|
||||
|
||||
|
||||
data6 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "86747535",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_3.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data6)
|
||||
|
||||
def image_to_base64(image_path):
|
||||
"""读取图片文件并转换为Base64编码"""
|
||||
with open(image_path, "rb") as image_file:
|
||||
# 读取图片二进制数据
|
||||
image_data = image_file.read()
|
||||
# 转换为Base64编码的bytes
|
||||
base64_bytes = base64.b64encode(image_data)
|
||||
# 转换为字符串(如果需要)
|
||||
base64_string = base64_bytes.decode('utf-8')
|
||||
return base64_string
|
||||
|
||||
# img64_str = image_to_base64("test_image.jfif")
|
||||
# data['user_img_path'] = img64_str
|
||||
|
||||
# 存储所有请求的响应
|
||||
responses = []
|
||||
|
||||
# 线程锁,防止多线程写入冲突
|
||||
lock = threading.Lock()
|
||||
|
||||
|
||||
send_index = 0
|
||||
|
||||
def send_request():
|
||||
try:
|
||||
global send_index
|
||||
send_data = more_data[send_index]
|
||||
send_index = send_index + 1
|
||||
response = requests.post(url, headers=headers, json=send_data)
|
||||
with lock: # 加锁,防止多线程同时修改 responses
|
||||
responses.append(response.json())
|
||||
except Exception as e:
|
||||
with lock:
|
||||
responses.append({"error": str(e)})
|
||||
|
||||
# 创建并启动多个线程
|
||||
threads = []
|
||||
num_requests = 7 # 并发请求数量
|
||||
|
||||
for _ in range(num_requests):
|
||||
t = threading.Thread(target=send_request)
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
# 等待所有线程完成
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
# 打印所有响应
|
||||
for i, resp in enumerate(responses, 1):
|
||||
print(f"请求 {i} 结果:", resp)
|
||||
@@ -0,0 +1,137 @@
|
||||
import requests
|
||||
import random
|
||||
import time
|
||||
import concurrent.futures
|
||||
import base64
|
||||
from collections import defaultdict
|
||||
|
||||
url = 'http://172.17.110.92/api/swapHair/v1'
|
||||
# url = 'https://699520645652485-http-8801.northwest1.gpugeek.com:8443/api/swapHair/v1'
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
data = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "1907651680352395265",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/user2_1_%E5%89%AF%E6%9C%AC.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
|
||||
def image_to_base64(file_path, mime_type=None):
|
||||
"""
|
||||
将图片文件转换为带Base64前缀的Data URI字符串
|
||||
|
||||
参数:
|
||||
file_path (str): 图片文件路径
|
||||
mime_type (str): 可选,指定MIME类型。如果为None,则根据文件扩展名自动判断
|
||||
|
||||
返回:
|
||||
str: 带Base64前缀的Data URI字符串
|
||||
"""
|
||||
# 如果没有指定MIME类型,根据文件扩展名推断
|
||||
if mime_type is None:
|
||||
extension = file_path.split('.')[-1].lower()
|
||||
mime_types = {
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'webp': 'image/webp',
|
||||
'bmp': 'image/bmp'
|
||||
}
|
||||
mime_type = mime_types.get(extension, 'application/octet-stream')
|
||||
|
||||
# 读取文件内容并编码为Base64
|
||||
with open(file_path, 'rb') as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
|
||||
|
||||
# 组合成Data URI格式
|
||||
return f"data:{mime_type};base64,{encoded_string}"
|
||||
|
||||
img64_str = image_to_base64("aaa.jpg")
|
||||
data['user_img_path'] = img64_str
|
||||
|
||||
class RequestStats:
|
||||
def __init__(self):
|
||||
self.total_requests = 0
|
||||
self.successful_requests = 0
|
||||
self.failed_requests = 0
|
||||
self.response_times = []
|
||||
self.start_time = None
|
||||
self.end_time = None
|
||||
self.request_timestamps = defaultdict(int)
|
||||
|
||||
def record_success(self, response_time):
|
||||
self.successful_requests += 1
|
||||
self.response_times.append(response_time)
|
||||
self.request_timestamps[int(time.time())] += 1
|
||||
|
||||
def record_failure(self):
|
||||
self.failed_requests += 1
|
||||
|
||||
def calculate_qps(self):
|
||||
if not self.request_timestamps:
|
||||
return 0
|
||||
timestamps = sorted(self.request_timestamps.keys())
|
||||
if len(timestamps) < 2:
|
||||
return self.successful_requests
|
||||
total_time = timestamps[-1] - timestamps[0] + 1
|
||||
return self.successful_requests / total_time
|
||||
|
||||
def send_request(stats):
|
||||
try:
|
||||
data['task_id'] = str(int(random.uniform(0, 10000000)))
|
||||
start_time = time.time()
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
end_time = time.time()
|
||||
response_time = end_time - start_time
|
||||
|
||||
if response.status_code == 200:
|
||||
stats.record_success(response_time)
|
||||
print(f"请求完成,响应时间: {response_time:.3f} 秒")
|
||||
return response_time
|
||||
else:
|
||||
print(f"请求失败,状态码: {response.status_code}")
|
||||
stats.record_failure()
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"请求发生错误: {e}")
|
||||
stats.record_failure()
|
||||
return None
|
||||
|
||||
def main():
|
||||
total_requests = 100 #请求总数
|
||||
concurrency = 25 # 并发数
|
||||
|
||||
stats = RequestStats()
|
||||
stats.start_time = time.time()
|
||||
stats.total_requests = total_requests
|
||||
|
||||
print(f"开始测试,总请求数: {total_requests},并发数: {concurrency}")
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
|
||||
futures = [executor.submit(send_request, stats) for _ in range(total_requests)]
|
||||
concurrent.futures.wait(futures)
|
||||
|
||||
stats.end_time = time.time()
|
||||
|
||||
print("\n统计结果:")
|
||||
print(f"总请求数: {stats.total_requests}")
|
||||
print(f"成功请求数: {stats.successful_requests}")
|
||||
print(f"失败请求数: {stats.failed_requests}")
|
||||
|
||||
if stats.successful_requests > 0:
|
||||
total_time = stats.end_time - stats.start_time
|
||||
avg_response_time = sum(stats.response_times) / len(stats.response_times)
|
||||
qps = stats.calculate_qps()
|
||||
|
||||
print(f"测试总时间: {total_time:.3f} 秒")
|
||||
print(f"平均响应时间: {avg_response_time:.3f} 秒")
|
||||
print(f"最大响应时间: {max(stats.response_times):.3f} 秒")
|
||||
print(f"最小响应时间: {min(stats.response_times):.3f} 秒")
|
||||
print(f"QPS (每秒查询率): {qps:.2f}")
|
||||
else:
|
||||
print("所有请求都失败了,无法计算统计信息")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,93 @@
|
||||
import requests
|
||||
import random
|
||||
import time
|
||||
import base64
|
||||
|
||||
# url = 'https://693565319933957-http-8801.northwest1.gpugeek.com:8443/api/swapHair/v1'
|
||||
url = 'http://172.17.110.92/api/swapHair/v1'
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
data = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "1907651680352395265",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/user2_1_%E5%89%AF%E6%9C%AC.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
|
||||
def image_to_base64(file_path, mime_type=None):
|
||||
"""
|
||||
将图片文件转换为带Base64前缀的Data URI字符串
|
||||
|
||||
参数:
|
||||
file_path (str): 图片文件路径
|
||||
mime_type (str): 可选,指定MIME类型。如果为None,则根据文件扩展名自动判断
|
||||
|
||||
返回:
|
||||
str: 带Base64前缀的Data URI字符串
|
||||
"""
|
||||
# 如果没有指定MIME类型,根据文件扩展名推断
|
||||
if mime_type is None:
|
||||
extension = file_path.split('.')[-1].lower()
|
||||
mime_types = {
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'webp': 'image/webp',
|
||||
'bmp': 'image/bmp'
|
||||
}
|
||||
mime_type = mime_types.get(extension, 'application/octet-stream')
|
||||
|
||||
# 读取文件内容并编码为Base64
|
||||
with open(file_path, 'rb') as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
|
||||
|
||||
# 组合成Data URI格式
|
||||
return f"data:{mime_type};base64,{encoded_string}"
|
||||
|
||||
# img64_str = image_to_base64("aaa.jpg")
|
||||
# data['user_img_path'] = img64_str
|
||||
|
||||
def send_request():
|
||||
try:
|
||||
data['task_id'] = str(int(random.uniform(0, 10000000)))
|
||||
start_time = time.time()
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
end_time = time.time()
|
||||
response_time = end_time - start_time
|
||||
return response_time
|
||||
except Exception as e:
|
||||
print(f"请求发生错误: {e}")
|
||||
return None
|
||||
|
||||
def main():
|
||||
total_requests = 30
|
||||
response_times = []
|
||||
|
||||
for i in range(total_requests):
|
||||
print(f"正在发送第 {i+1} 个请求...")
|
||||
rt = send_request()
|
||||
if rt is not None:
|
||||
response_times.append(rt)
|
||||
print(f"第 {i+1} 个请求完成,响应时间: {rt:.3f} 秒")
|
||||
else:
|
||||
print(f"第 {i+1} 个请求失败")
|
||||
|
||||
# 单路串行,不需要延迟,但如果需要可以添加
|
||||
# time.sleep(1)
|
||||
|
||||
if response_times:
|
||||
avg_response_time = sum(response_times) / len(response_times)
|
||||
print("\n统计结果:")
|
||||
print(f"总请求数: {total_requests}")
|
||||
print(f"成功请求数: {len(response_times)}")
|
||||
print(f"失败请求数: {total_requests - len(response_times)}")
|
||||
print(f"平均响应时间: {avg_response_time:.3f} 秒")
|
||||
print(f"最大响应时间: {max(response_times):.3f} 秒")
|
||||
print(f"最小响应时间: {min(response_times):.3f} 秒")
|
||||
else:
|
||||
print("所有请求都失败了,无法计算统计信息")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,45 @@
|
||||
693573774479365,ssh -p 51884 root@northwest1.gpugeek.com,VZxzyqDNbucAEdm5nZQFFQ67v6X7vQcK,,,,,,,,,,,,,,,,,
|
||||
693573706682373,ssh -p 46901 root@northwest1.gpugeek.com,ZDdguaaQsNRb8qmMaTu7q95eu2vG5Qc3,,,,,,,,,,,,,,,,,
|
||||
693570752139269,ssh -p 44853 root@northwest1.gpugeek.com,Wavu5Cnewm65p5ABXqXuSMFfF2c77ZuG,,,,,,,,,,,,,,,,,
|
||||
693570664882181,ssh -p 49563 root@northwest1.gpugeek.com,4mz3pKZdVff9X3hFzcfVY4h4VkzSTpcb,,,,,,,,,,,,,,,,,
|
||||
693565557084165,ssh -p 57192 root@northwest1.gpugeek.com,5ztvzCwtEaBG8CYaCbyxZVXmzmUxuD7b,,,,,,,,,,,,,,,,,
|
||||
693565452951557,ssh -p 40188 root@northwest1.gpugeek.com,wWAsrQ5yGvahpF2zHXVFrRterMeYWuN3,,,,,,,,,,,,,,,,,
|
||||
693565319933957,ssh -p 55139 root@northwest1.gpugeek.com,uaUMSzmAHyka2ZcBvQHZ5My9PUNRP78T,,,,,,,,,,,,,,,,,
|
||||
693597199511557,ssh -p 54874 root@northwest1.gpugeek.com,zN2SB3DZxNpnvpQzC7PBHun3CNh2ydGV,,,,,,,,,,,,,,,,,
|
||||
693597082546181,ssh -p 40885 root@northwest1.gpugeek.com,HdYQ3QtzKdRgNQZaXWDKYSqWPkE2w53h,,,,,,,,,,,,,,,,,
|
||||
693596619751429,ssh -p 44684 root@northwest1.gpugeek.com,tFF9WKSe5XRSDR8PWcNweF2cZ6ZdYpaW,,,,,,,,,,,,,,,,,
|
||||
693596213850117,ssh -p 44307 root@northwest1.gpugeek.com,YSMNFffKTMY37Ze83UfmSr5XF65RP7dT,,,,,,,,,,,,,,,,,
|
||||
693595248599045,ssh -p 54178 root@northwest1.gpugeek.com,akZ3qyRtqUum553sMNXEVNY3qzxMA3sV,,,,,,,,,,,,,,,,,
|
||||
693595168616453,ssh -p 59719 root@northwest1.gpugeek.com,XqQzT8qtfUWFQP8E3PPMb7FMPRUYBehS,,,,,,,,,,,,,,,,,
|
||||
693594319818757,ssh -p 56408 root@northwest1.gpugeek.com,46SMhqxfqkyvVu6qKyr9UW7GfTnK5GCp,,,,,,,,,,,,,,,,,
|
||||
693594143621125,ssh -p 47103 root@northwest1.gpugeek.com,ZvxQPDvKQeVdRMnm3VTMD5vfdg8VpqCE,,,,,,,,,,,,,,,,,
|
||||
693579634159621,ssh -p 45208 root@northwest1.gpugeek.com,467nsPAHNgS5fvzNNvzCaebzs3Z4DB8s,,,,,,,,,,,,,,,,,
|
||||
693579093471237,ssh -p 40600 root@northwest1.gpugeek.com,mRabksFkwYV8NWx2NmAufTevNrFUeVBw,,,,,,,,,,,,,,,,,
|
||||
693578025705477,ssh -p 58639 root@northwest1.gpugeek.com,WSXHUssQSXTCWSDuVCdcvG8DQyFPU2er,,,,,,,,,,,,,,,,,
|
||||
693577880313861,ssh -p 59280 root@northwest1.gpugeek.com,CywKAemA3b2z6VFG5TzgYzAKy9bnGvNM,,,,,,,,,,,,,,,,,
|
||||
693577753862149,ssh -p 58493 root@northwest1.gpugeek.com,yDxpPQbyxEuNbPQPapUV72vK3s9CRu3F,,,,,,,,,,,,,,,,,
|
||||
693577626185733,ssh -p 59071 root@northwest1.gpugeek.com,UFNpw9B48ahAU8RrmcGVmSENazGCSSm5,,,,,,,,,,,,,,,,,
|
||||
693577527365637,ssh -p 57796 root@northwest1.gpugeek.com,2sZhwEmS7RdgQeuCpSnQqgpfR4BQnhvd,,,,,,,,,,,,,,,,,
|
||||
693575032029189,ssh -p 44408 root@northwest1.gpugeek.com,yM6wR5g8VGXbb4XfvA9ghYWVk9maTht8,,,,,,,,,,,,,,,,,
|
||||
693574955724805,ssh -p 43820 root@northwest1.gpugeek.com,csQbqbCAkdwTZZSVdmxMq3svKhdpEAtF,,,,,,,,,,,,,,,,,
|
||||
699531043577861,ssh -p 53934 root@northwest1.gpugeek.com,WD3WcCNTuz5VxmbAdmWFSYZVfhEXf8NK,,,,,,,,,,,,,,,,,
|
||||
699531676672005,ssh -p 53976 root@northwest1.gpugeek.com,p37A8HRPcAU8SP4ssha4VBS9phgrykF9,,,,,,,,,,,,,,,,,
|
||||
699552291840005,ssh -p 52102 root@northwest1.gpugeek.com,S5G4zWPQ8WMZ89Tk3afTgQAAfpqXSuM3,,,,,,,,,,,,,,,,,
|
||||
699552392802309,ssh -p 48055 root@northwest1.gpugeek.com,afXHTcaPMqbWnqCfWv6tzSFkBKFFf7vS,,,,,,,,,,,,,,,,,
|
||||
699552766849029,ssh -p 51140 root@northwest1.gpugeek.com,9YFyTAruNZwr8K44ZXp9ET5aG5c4DTa4,,,,,,,,,,,,,,,,,
|
||||
699554474184709,ssh -p 40280 root@northwest1.gpugeek.com,sbSfgFa8V4bhBDpbqtccCvXkHF6gSfAn,,,,,,,,,,,,,,,,,
|
||||
699554413039621,ssh -p 49817 root@northwest1.gpugeek.com,dRB7CRtcdgGwFe4nbxaEh8D7nXMmuYDX,,,,,,,,,,,,,,,,,
|
||||
699554332229637,ssh -p 40625 root@northwest1.gpugeek.com,HQz2nC7tbTTxGZhC7YN4YFCg5WfcMDD2,,,,,,,,,,,,,,,,,
|
||||
699554276352005,ssh -p 43763 root@northwest1.gpugeek.com,bYFCDzzN2MKtWHF4qvyK9PtG9xqurA5z,,,,,,,,,,,,,,,,,
|
||||
699554118586373,ssh -p 49257 root@northwest1.gpugeek.com,GNMmwCpB3NcGryqznhB2acDmtgFPBYXs,,,,,,,,,,,,,,,,,
|
||||
699554021834757,ssh -p 47192 root@northwest1.gpugeek.com,wFxwYDD6KapUSYgAanEcYzDRusQCvPBT,,,,,,,,,,,,,,,,,
|
||||
699553939038213,ssh -p 47118 root@northwest1.gpugeek.com,fXU3wEvUE4ckCfSXwkVW7dW8xG3VqRh2,,,,,,,,,,,,,,,,,
|
||||
699553843216389,ssh -p 40918 root@northwest1.gpugeek.com,3EYpyAx7s5gbHQEXWNZ6Td3QdMCZU8Mu,,,,,,,,,,,,,,,,,
|
||||
699553742393349,ssh -p 57046 root@northwest1.gpugeek.com,a6KeZhBPMWecTtvsP3kbgcV7MZseyerY,,,,,,,,,,,,,,,,,
|
||||
699553618411525,ssh -p 52041 root@northwest1.gpugeek.com,tev3z7knqaaSP2T74QtYqtfb9HdNy9WW,,,,,,,,,,,,,,,,,
|
||||
699553554767877,ssh -p 42691 root@northwest1.gpugeek.com,r3bFQs4uNFyUCDN99f7deH2pXqdmRYEV,,,,,,,,,,,,,,,,,
|
||||
699553450074117,ssh -p 42199 root@northwest1.gpugeek.com,hYgx3rbWpmYSFs6pEq8umZqcgAZyZa9p,,,,,,,,,,,,,,,,,
|
||||
699553329291269,ssh -p 49696 root@northwest1.gpugeek.com,EVnYtmqBZTtdfzXPX39CukVugrX6HzEW,,,,,,,,,,,,,,,,,
|
||||
699553242570757,ssh -p 57669 root@northwest1.gpugeek.com,FU9UmrBvMeCGvvxfDdX5fWCn4E9AtTBg,,,,,,,,,,,,,,,,,
|
||||
699552977174533,ssh -p 41179 root@northwest1.gpugeek.com,NFx6ahHDQKpqFEBg4nrPMerureYX5dMV,,,,,,,,,,,,,,,,,
|
||||
699552838074373,ssh -p 46220 root@northwest1.gpugeek.com,qZTCYqU4KpB9GbZfgxXAwcxwMcKM9eFz,,,,,,,,,,,,,,,,,
|
||||
|
@@ -0,0 +1,7 @@
|
||||
699553450074117,ssh -p 42199 root@northwest1.gpugeek.com,hYgx3rbWpmYSFs6pEq8umZqcgAZyZa9p,,,,,,,,,,,,,,,,,
|
||||
699553742393349,ssh -p 57046 root@northwest1.gpugeek.com,a6KeZhBPMWecTtvsP3kbgcV7MZseyerY,,,,,,,,,,,,,,,,,
|
||||
699553618411525,ssh -p 52041 root@northwest1.gpugeek.com,tev3z7knqaaSP2T74QtYqtfb9HdNy9WW,,,,,,,,,,,,,,,,,
|
||||
699553242570757,ssh -p 57669 root@northwest1.gpugeek.com,FU9UmrBvMeCGvvxfDdX5fWCn4E9AtTBg,,,,,,,,,,,,,,,,,
|
||||
699553554767877,ssh -p 42691 root@northwest1.gpugeek.com,r3bFQs4uNFyUCDN99f7deH2pXqdmRYEV,,,,,,,,,,,,,,,,,
|
||||
699552977174533,ssh -p 41179 root@northwest1.gpugeek.com,NFx6ahHDQKpqFEBg4nrPMerureYX5dMV,,,,,,,,,,,,,,,,,
|
||||
699552838074373,ssh -p 46220 root@northwest1.gpugeek.com,qZTCYqU4KpB9GbZfgxXAwcxwMcKM9eFz,,,,,,,,,,,,,,,,,
|
||||
|
@@ -0,0 +1,382 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>服务器状态监控</title>
|
||||
<style>
|
||||
:root {
|
||||
--color-busy: #f39c12;
|
||||
--color-idle: #2ecc71;
|
||||
--color-normal: #2ecc71;
|
||||
--color-error: #e74c3c;
|
||||
--color-action: #3498db;
|
||||
--color-text: #333;
|
||||
--color-light-text: #7f8c8d;
|
||||
--color-bg: #f5f5f5;
|
||||
--color-card-bg: white;
|
||||
--color-event-bg: #fafafa;
|
||||
--color-event-item: white;
|
||||
}
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.dashboard {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
gap: 10px;
|
||||
}
|
||||
.refresh-btn {
|
||||
padding: 8px 16px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.refresh-btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
.refresh-btn:active {
|
||||
background-color: #1c6da8;
|
||||
}
|
||||
.refresh-info {
|
||||
text-align: center;
|
||||
margin: 0 auto 20px;
|
||||
padding: 10px;
|
||||
background-color: var(--color-card-bg);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
max-width: 500px;
|
||||
}
|
||||
.server-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
.server-card {
|
||||
background-color: var(--color-card-bg);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 500px; /* 固定高度 */
|
||||
}
|
||||
.server-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.server-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.server-status {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.status-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.status-busy { background-color: var(--color-busy); }
|
||||
.status-idle { background-color: var(--color-idle); }
|
||||
.status-normal { background-color: var(--color-normal); }
|
||||
.status-error { background-color: var(--color-error); }
|
||||
.status-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
.last-update {
|
||||
font-size: 12px;
|
||||
color: var(--color-light-text);
|
||||
}
|
||||
.events-section {
|
||||
margin-top: 10px;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0; /* 重要:允许内部滚动 */
|
||||
}
|
||||
.events-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.events-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
.event-count {
|
||||
background-color: var(--color-action);
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.event-list {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
background-color: var(--color-event-bg);
|
||||
}
|
||||
.event-item {
|
||||
margin-bottom: 10px;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-event-item);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.event-time {
|
||||
font-size: 12px;
|
||||
color: var(--color-light-text);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.event-action {
|
||||
font-weight: bold;
|
||||
margin: 5px 0;
|
||||
color: var(--color-action);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.action-tag {
|
||||
background-color: #e1f5fe;
|
||||
color: #0288d1;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.event-data {
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
background-color: #f9f9f9;
|
||||
padding: 8px;
|
||||
border-radius: 3px;
|
||||
margin-top: 5px;
|
||||
font-family: monospace;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.badge-busy {
|
||||
background-color: rgba(243, 156, 18, 0.1);
|
||||
color: var(--color-busy);
|
||||
}
|
||||
.badge-idle {
|
||||
background-color: rgba(46, 204, 113, 0.1);
|
||||
color: var(--color-idle);
|
||||
}
|
||||
.badge-normal {
|
||||
background-color: rgba(46, 204, 113, 0.1);
|
||||
color: var(--color-normal);
|
||||
}
|
||||
.badge-error {
|
||||
background-color: rgba(231, 76, 60, 0.1);
|
||||
color: var(--color-error);
|
||||
}
|
||||
.badge-success {
|
||||
background-color: rgba(46, 204, 113, 0.1);
|
||||
color: var(--color-normal);
|
||||
}
|
||||
.badge-failure {
|
||||
background-color: rgba(231, 76, 60, 0.1);
|
||||
color: var(--color-error);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="dashboard">
|
||||
<h1>服务器状态监控</h1>
|
||||
<div class="controls">
|
||||
<button class="refresh-btn" id="refreshBtn">手动刷新数据</button>
|
||||
</div>
|
||||
<div class="refresh-info">
|
||||
最后更新时间: <strong id="lastUpdateTime">-</strong>
|
||||
</div>
|
||||
<div class="server-grid" id="serverContainer">
|
||||
<!-- 服务器卡片将在这里动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取DOM元素
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
const lastUpdateTime = document.getElementById('lastUpdateTime');
|
||||
const serverContainer = document.getElementById('serverContainer');
|
||||
|
||||
// 获取服务器状态数据
|
||||
async function fetchServerStatus() {
|
||||
try {
|
||||
const response = await fetch('http://182.92.195.47:5001/get_server_state');
|
||||
if (!response.ok) throw new Error('网络响应不正常');
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('获取服务器状态失败:', error);
|
||||
alert('获取数据失败: ' + error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化JSON数据
|
||||
function formatData(data) {
|
||||
if (typeof data === 'string') return data;
|
||||
try {
|
||||
return JSON.stringify(data, null, 2);
|
||||
} catch {
|
||||
return String(data);
|
||||
}
|
||||
}
|
||||
|
||||
// 判断服务器状态
|
||||
function getServerStatus(server) {
|
||||
const now = Date.now() / 1000;
|
||||
const lastEventTime = server.last_event?.time || 0;
|
||||
|
||||
// 忙碌状态
|
||||
const busyStatus = {
|
||||
status: server.busy ? 'busy' : 'idle',
|
||||
text: server.busy ? '忙碌' : '空闲',
|
||||
indicatorClass: server.busy ? 'status-busy' : 'status-idle',
|
||||
badgeClass: server.busy ? 'badge-busy' : 'badge-idle'
|
||||
};
|
||||
|
||||
// 连接状态 (5分钟判断)
|
||||
const isNormal = (now - lastEventTime) < (5*60 + 10);
|
||||
const connectStatus = {
|
||||
status: isNormal ? 'normal' : 'error',
|
||||
text: isNormal ? '活跃' : '不活跃',
|
||||
indicatorClass: isNormal ? 'status-normal' : 'status-error',
|
||||
badgeClass: isNormal ? 'badge-normal' : 'badge-error'
|
||||
};
|
||||
|
||||
// 上次运行状态
|
||||
const hasLastCall = server.hasOwnProperty('last_call');
|
||||
const lastCallStatus = {
|
||||
status: hasLastCall && server.last_call === 'success' ? 'success' : 'failure',
|
||||
text: hasLastCall && server.last_call === 'success' ? '成功' : '失败',
|
||||
indicatorClass: hasLastCall && server.last_call === 'success' ? 'status-normal' : 'status-error',
|
||||
badgeClass: hasLastCall && server.last_call === 'success' ? 'badge-success' : 'badge-failure'
|
||||
};
|
||||
|
||||
return { busyStatus, connectStatus, lastCallStatus };
|
||||
}
|
||||
|
||||
// 创建服务器卡片
|
||||
function createServerCard(server) {
|
||||
const status = getServerStatus(server);
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'server-card';
|
||||
card.innerHTML = `
|
||||
<div class="server-header">
|
||||
<div class="server-name">${server.name}</div>
|
||||
</div>
|
||||
<div class="server-status">
|
||||
<div class="status-item">
|
||||
<span class="status-indicator ${status.busyStatus.indicatorClass}"></span>
|
||||
<span class="status-text">运行状态: <span class="status-badge ${status.busyStatus.badgeClass}">${status.busyStatus.text}</span></span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-indicator ${status.connectStatus.indicatorClass}"></span>
|
||||
<span class="status-text">活跃状态: <span class="status-badge ${status.connectStatus.badgeClass}">${status.connectStatus.text}</span></span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-indicator ${status.lastCallStatus.indicatorClass}"></span>
|
||||
<span class="status-text">上次运行: <span class="status-badge ${status.lastCallStatus.badgeClass}">${status.lastCallStatus.text}</span></span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="last-update">最后活动: ${server.last_event?.time_str || '无记录'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="events-section">
|
||||
<div class="events-header">
|
||||
<div class="events-title">事件记录</div>
|
||||
<div class="event-count">${server.events.length}条</div>
|
||||
</div>
|
||||
<div class="event-list">
|
||||
${server.events.map(event => `
|
||||
<div class="event-item">
|
||||
<div class="event-time">${event.time_str}</div>
|
||||
<div class="event-action">
|
||||
<span>${event.action}</span>
|
||||
<span class="action-tag">${event.action}</span>
|
||||
</div>
|
||||
<div class="event-data">${formatData(event.data)}</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
// 更新整个UI
|
||||
async function updateUI() {
|
||||
refreshBtn.disabled = true;
|
||||
refreshBtn.textContent = '加载中...';
|
||||
|
||||
const servers = await fetchServerStatus();
|
||||
|
||||
if (servers && servers.length > 0) {
|
||||
serverContainer.innerHTML = '';
|
||||
servers.forEach(server => {
|
||||
const card = createServerCard(server);
|
||||
serverContainer.appendChild(card);
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
lastUpdateTime.textContent = now.toLocaleString();
|
||||
}
|
||||
|
||||
refreshBtn.disabled = false;
|
||||
refreshBtn.textContent = '手动刷新数据';
|
||||
}
|
||||
|
||||
// 添加事件监听
|
||||
refreshBtn.addEventListener('click', updateUI);
|
||||
|
||||
// 初始加载
|
||||
updateUI();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,124 @@
|
||||
import threading
|
||||
import requests
|
||||
import json
|
||||
import base64
|
||||
import time
|
||||
import random
|
||||
|
||||
|
||||
# url = 'http://172.17.110.92/hairColor/v2'
|
||||
url = 'https://693565557084165-http-8801.northwest1.gpugeek.com:8443/hairColor/v2'
|
||||
|
||||
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
data = {
|
||||
"img": "data:image/jpeg;base64,/9j/4AAQSkZJ....Euy+sn/oZrKn8CPlK2qR/9k=",
|
||||
"userId": "18701620166",
|
||||
"rgb":[77,99,0],
|
||||
"ratio": 0.9,
|
||||
"output_format": "base64"
|
||||
}
|
||||
|
||||
def image_to_base64(file_path, mime_type=None):
|
||||
"""
|
||||
将图片文件转换为带Base64前缀的Data URI字符串
|
||||
|
||||
参数:
|
||||
file_path (str): 图片文件路径
|
||||
mime_type (str): 可选,指定MIME类型。如果为None,则根据文件扩展名自动判断
|
||||
|
||||
返回:
|
||||
str: 带Base64前缀的Data URI字符串
|
||||
"""
|
||||
# 如果没有指定MIME类型,根据文件扩展名推断
|
||||
if mime_type is None:
|
||||
extension = file_path.split('.')[-1].lower()
|
||||
mime_types = {
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'webp': 'image/webp',
|
||||
'bmp': 'image/bmp'
|
||||
}
|
||||
mime_type = mime_types.get(extension, 'application/octet-stream')
|
||||
|
||||
# 读取文件内容并编码为Base64
|
||||
with open(file_path, 'rb') as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
|
||||
|
||||
# 组合成Data URI格式
|
||||
return f"data:{mime_type};base64,{encoded_string}"
|
||||
|
||||
img64_str = image_to_base64("aaa.jpg")
|
||||
data['img'] = img64_str
|
||||
|
||||
# 存储所有请求的响应
|
||||
responses = []
|
||||
responses_time = []
|
||||
|
||||
# 线程锁,防止多线程写入冲突
|
||||
lock = threading.Lock()
|
||||
|
||||
|
||||
def send_request():
|
||||
try:
|
||||
start_time = time.time()
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
end_time = time.time()
|
||||
response_time = end_time - start_time
|
||||
print(f"相应时间:{response_time}")
|
||||
result = response.json()
|
||||
if 'state' in result:
|
||||
if result['state'] != 0:
|
||||
print(f"请求失败: {result}")
|
||||
time.sleep(5)
|
||||
with lock: # 加锁,防止多线程同时修改 responses
|
||||
responses.append(response)
|
||||
responses_time.append(response_time)
|
||||
except Exception as e:
|
||||
with lock:
|
||||
error_msg = {"error": str(e)}
|
||||
responses.append(error_msg)
|
||||
print(f"请求失败: {error_msg}") # 打印错误信息
|
||||
time.sleep(5) # 延时5秒
|
||||
|
||||
# 创建并启动多个线程
|
||||
threads = []
|
||||
num_requests = 1 # 并发请求数量
|
||||
test_rand_num = 1
|
||||
|
||||
time_start = time.time()
|
||||
|
||||
for _ in range(test_rand_num):
|
||||
for _ in range(num_requests):
|
||||
t = threading.Thread(target=send_request)
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
# 等待所有线程完成
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
|
||||
end_time = time.time()
|
||||
|
||||
total_time = (end_time - time_start)
|
||||
qps = (num_requests*test_rand_num)/total_time
|
||||
print(f"num_requests:{num_requests} total_time:{total_time} qps:{qps}")
|
||||
|
||||
totalReqTime = 0
|
||||
for t in responses_time:
|
||||
totalReqTime += t
|
||||
avgTime = totalReqTime/len(responses_time)
|
||||
print(f"平均相应时间:{avgTime}")
|
||||
|
||||
|
||||
|
||||
# 打印所有响应
|
||||
for i, resp in enumerate(responses, 1):
|
||||
print(f"请求 {i} 结果:", {resp.status_code, resp.text[:128]})
|
||||
|
||||
print(f"num_requests:{num_requests} total_time:{total_time} qps:{qps}")
|
||||
print(f"平均相应时间:{avgTime}")
|
||||
@@ -0,0 +1,164 @@
|
||||
import threading
|
||||
import requests
|
||||
import json
|
||||
import base64
|
||||
import time
|
||||
import random
|
||||
|
||||
url = 'http://mq.aidigifi.meidaojia.com/api/swapHair/v1'
|
||||
# url = 'https://692524911165445-http-8801.northwest1.gpugeek.com:8443/api/swapHair/v1'
|
||||
# url = 'https://692502023221253-http-8801.northwest1.gpugeek.com:8443/api/swapHair/v1'
|
||||
# url = 'https://692493464571909-http-8801.northwest1.gpugeek.com:8443/api/swapHair/v1'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
data = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "1907651680352395265",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/user2_1_%E5%89%AF%E6%9C%AC.JPG",
|
||||
# "user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_8.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
|
||||
more_data = []
|
||||
more_data.append(data)
|
||||
|
||||
data1 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "2345623451234",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_8.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data1)
|
||||
|
||||
data2 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "3145645324",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_7.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data2)
|
||||
|
||||
data3 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "456412345",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_6.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data3)
|
||||
|
||||
data4 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "45623445",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_5.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data4)
|
||||
|
||||
data5 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "2345867435",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_4.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data5)
|
||||
|
||||
|
||||
data6 = {
|
||||
"hair_id": "1907651680352395265",
|
||||
"task_id": "86747535",
|
||||
"user_img_path": "https://cdn.meidaojia.com/ZoeFiles/testuser_3.JPG",
|
||||
"is_hr": "false",
|
||||
"output_format": "base64"
|
||||
}
|
||||
more_data.append(data6)
|
||||
|
||||
def image_to_base64(file_path, mime_type=None):
|
||||
"""
|
||||
将图片文件转换为带Base64前缀的Data URI字符串
|
||||
|
||||
参数:
|
||||
file_path (str): 图片文件路径
|
||||
mime_type (str): 可选,指定MIME类型。如果为None,则根据文件扩展名自动判断
|
||||
|
||||
返回:
|
||||
str: 带Base64前缀的Data URI字符串
|
||||
"""
|
||||
# 如果没有指定MIME类型,根据文件扩展名推断
|
||||
if mime_type is None:
|
||||
extension = file_path.split('.')[-1].lower()
|
||||
mime_types = {
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'webp': 'image/webp',
|
||||
'bmp': 'image/bmp'
|
||||
}
|
||||
mime_type = mime_types.get(extension, 'application/octet-stream')
|
||||
|
||||
# 读取文件内容并编码为Base64
|
||||
with open(file_path, 'rb') as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
|
||||
|
||||
# 组合成Data URI格式
|
||||
return f"data:{mime_type};base64,{encoded_string}"
|
||||
|
||||
img64_str = image_to_base64("aaa.jpg")
|
||||
data['user_img_path'] = img64_str
|
||||
|
||||
# 存储所有请求的响应
|
||||
responses = []
|
||||
|
||||
# 线程锁,防止多线程写入冲突
|
||||
lock = threading.Lock()
|
||||
|
||||
|
||||
send_index = 0
|
||||
|
||||
def send_request(start_time):
|
||||
try:
|
||||
sleep_time = start_time #random.uniform(0, 2) # 生成 [0, 2) 之间的随机浮点数
|
||||
time.sleep(sleep_time)
|
||||
global send_index
|
||||
send_data = more_data[send_index]
|
||||
# send_index = send_index + 1
|
||||
send_data['task_id'] = str(int(random.uniform(0, 10000000)))
|
||||
response = requests.post(url, headers=headers, json=send_data)
|
||||
with lock: # 加锁,防止多线程同时修改 responses
|
||||
responses.append(response)
|
||||
except Exception as e:
|
||||
with lock:
|
||||
responses.append({"error": str(e)})
|
||||
|
||||
# 创建并启动多个线程
|
||||
threads = []
|
||||
num_requests = 10 # 并发请求数量
|
||||
|
||||
time_start = time.time()
|
||||
|
||||
start_time = 0
|
||||
for _ in range(num_requests):
|
||||
# start_time = start_time + 1
|
||||
start_time = 0 # random.uniform(0, 1)
|
||||
t = threading.Thread(target=send_request, args=(start_time,))
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
# 等待所有线程完成
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
end_time = time.time()
|
||||
|
||||
total_time = (end_time - time_start)
|
||||
qps = num_requests/total_time
|
||||
print(f"num_requests:{num_requests} total_time:{total_time} qps:{qps}")
|
||||
|
||||
# 打印所有响应
|
||||
for i, resp in enumerate(responses, 1):
|
||||
print(f"请求 {i} 结果: {resp.status_code, resp.text[:128]}")
|
||||
|
After Width: | Height: | Size: 124 KiB |
@@ -0,0 +1,428 @@
|
||||
import time
|
||||
import datetime
|
||||
import uuid
|
||||
import redis
|
||||
import logging
|
||||
import requests
|
||||
import threading
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from config import REDIS_HOST
|
||||
from config import REDIS_PORT
|
||||
from config import REDIS_DB
|
||||
from config import QUEUE_NAME
|
||||
from config import GPU_SERVER_LIST
|
||||
from config import SERVER_LOG_EVENT_LEN
|
||||
from config import SERVER_LOG_
|
||||
from config import GPU_SERVER_TIME_OUT
|
||||
from config import KEY_QUEUE_LOCK_NAME, acquire_lock, SERVER_LIST_LOCK_NAME, SERVER_LIST_LOG_LOCK_NAME
|
||||
import copy
|
||||
|
||||
import logging
|
||||
import csv
|
||||
|
||||
# 创建 logger
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO) # 设置 logger 的级别
|
||||
|
||||
|
||||
# 获取当前日期和时间
|
||||
now = datetime.now()
|
||||
|
||||
# 格式化为字符串(例如:2023-10-25 14:30:45)
|
||||
date_time_str = now.strftime("%Y-%m-%d_%H:%M:%S")
|
||||
|
||||
# 创建文件 handler
|
||||
file_handler = logging.FileHandler(f'/var/log/meidaojia/worker_{date_time_str}.log')
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(file_formatter)
|
||||
|
||||
# 创建控制台 handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
console_handler.setFormatter(console_formatter)
|
||||
|
||||
# 添加 handlers 到 logger
|
||||
logger.addHandler(file_handler)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# 创建 Redis 连接池
|
||||
redis_pool = redis.ConnectionPool(
|
||||
host=REDIS_HOST,
|
||||
port=REDIS_PORT,
|
||||
db=REDIS_DB,
|
||||
max_connections=2000 # 根据实际情况调整
|
||||
)
|
||||
|
||||
def get_redis_conn():
|
||||
"""获取 Redis 连接"""
|
||||
return redis.Redis(connection_pool=redis_pool)
|
||||
|
||||
redis_conn = get_redis_conn()
|
||||
|
||||
def get_time(timestamp):
|
||||
# 转换为UTC时间
|
||||
utc_time = datetime.utcfromtimestamp(timestamp)
|
||||
|
||||
# 转换为北京时间 (UTC+8)
|
||||
beijing_time = utc_time + timedelta(hours=8) # 正确
|
||||
|
||||
# 格式化输出
|
||||
formatted_time = beijing_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
return formatted_time
|
||||
|
||||
def server_log_event(name, action, in_data, success=True):
|
||||
|
||||
log_data = copy.deepcopy(in_data)
|
||||
server_log_name = f"{SERVER_LOG_}{name}"
|
||||
|
||||
lock = acquire_lock(redis_conn, f"server_log_lock_{name}")
|
||||
if lock:
|
||||
try:
|
||||
logger.info(f"server_log_event name:{name} action:{action} success:{success}")
|
||||
server_log_str = redis_conn.get(server_log_name).decode("utf-8")
|
||||
server_log = json.loads(server_log_str)
|
||||
event = {}
|
||||
t = time.time()
|
||||
event['time'] = t
|
||||
event['time_str'] = get_time(t)
|
||||
event['action'] = action
|
||||
|
||||
server_log['last_call'] = "success"
|
||||
if 'state' in log_data:
|
||||
if log_data['state'] != 0:
|
||||
server_log['last_call'] = "failure"
|
||||
|
||||
if not success:
|
||||
server_log['last_call'] = "failure"
|
||||
|
||||
if 'data' in log_data:
|
||||
imgstr = log_data['data']
|
||||
imglen = len(imgstr)
|
||||
if imglen > 256:
|
||||
log_data['data'] = imgstr[:64]
|
||||
|
||||
if 'img' in log_data:
|
||||
imgstr = log_data['img']
|
||||
imglen = len(imgstr)
|
||||
if imglen > 256:
|
||||
log_data['img'] = imgstr[:64]
|
||||
|
||||
if 'result' in log_data:
|
||||
imgstr = log_data['result']
|
||||
imglen = len(imgstr)
|
||||
if imglen > 256:
|
||||
log_data['result'] = imgstr[:64]
|
||||
|
||||
if 'user_img_path' in log_data:
|
||||
imgstr = log_data['user_img_path']
|
||||
imglen = len(imgstr)
|
||||
if imglen > 256:
|
||||
log_data['user_img_path'] = imgstr[:32]
|
||||
|
||||
event['data'] = log_data
|
||||
if(len(server_log['events']) > SERVER_LOG_EVENT_LEN):
|
||||
server_log['events'].pop(0)
|
||||
server_log['events'].append(event)
|
||||
server_log['last_event'] = event
|
||||
server_log_str = json.dumps(server_log)
|
||||
redis_conn.set(f"{SERVER_LOG_}{name}", server_log_str)
|
||||
|
||||
finally:
|
||||
lock.release()
|
||||
else:
|
||||
logger.error(f"get lock none {server_log_name}")
|
||||
|
||||
def registerGpuServer(name, url, can_use):
|
||||
try:
|
||||
server_list_str = redis_conn.get(GPU_SERVER_LIST).decode("utf-8")
|
||||
server_list = json.loads(server_list_str)
|
||||
if name not in server_list:
|
||||
server_list.append(name)
|
||||
server = {}
|
||||
server['name'] = name
|
||||
server['url'] = url
|
||||
server['can_use'] = can_use
|
||||
server_str = json.dumps(server)
|
||||
logger.info(f"registerGpuServer, {server_str}")
|
||||
redis_conn.set(name, server_str)
|
||||
|
||||
server_log = {}
|
||||
server_log['name'] = name
|
||||
event = {}
|
||||
t = time.time()
|
||||
event['time'] = t
|
||||
event['time_str'] = get_time(t)
|
||||
event['action'] = "register"
|
||||
event['data'] = f"url {url}"
|
||||
server_log['last_event'] = event
|
||||
server_log['events'] = []
|
||||
server_log['last_call'] = "success"
|
||||
if(len(server_log['events']) > SERVER_LOG_EVENT_LEN):
|
||||
server_log['events'].pop(0)
|
||||
server_log['events'].append(event)
|
||||
server_log_str = json.dumps(server_log)
|
||||
redis_conn.set(f"{SERVER_LOG_}{name}", server_log_str)
|
||||
|
||||
redis_conn.set(GPU_SERVER_LIST, json.dumps(server_list))
|
||||
except Exception as e:
|
||||
logger.error(f"registerGpuServer{e.message}")
|
||||
|
||||
|
||||
def get_remote_gpu_server():
|
||||
start_time = datetime.now()
|
||||
while (datetime.now() - start_time).seconds < GPU_SERVER_TIME_OUT:
|
||||
lock = acquire_lock(redis_conn, SERVER_LIST_LOCK_NAME)
|
||||
try:
|
||||
server_list_str = redis_conn.get(GPU_SERVER_LIST).decode("utf-8")
|
||||
server_list = json.loads(server_list_str)
|
||||
for name in server_list:
|
||||
server_str = redis_conn.get(name).decode("utf-8")
|
||||
server = json.loads(server_str)
|
||||
if server['can_use']:
|
||||
return server
|
||||
finally:
|
||||
lock.release()
|
||||
time.sleep(0.2)
|
||||
return None
|
||||
|
||||
def call_remote_gpu_server(task_data_str, server=None):
|
||||
task_data = json.loads(task_data_str)
|
||||
key = task_data['key']
|
||||
if server == None:
|
||||
server = get_remote_gpu_server()
|
||||
|
||||
result_str = ''
|
||||
result_status_code = '200'
|
||||
if server:
|
||||
headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if 'hairColor' in task_data['api']:
|
||||
data = {
|
||||
"img": task_data['request']['img'],
|
||||
"rgb": task_data['request']["rgb"],
|
||||
"ratio": task_data['request']['ratio'],
|
||||
"userId": task_data['request']['userId'],
|
||||
"output_format":task_data['request']['output_format']
|
||||
}
|
||||
else:
|
||||
data = {
|
||||
"hair_id": task_data['request']['hair_id'],
|
||||
"task_id": task_data['request']["task_id"],
|
||||
"user_img_path": task_data['request']['user_img_path'],
|
||||
"is_hr": task_data['request']['is_hr'],
|
||||
"output_format":task_data['request']['output_format']
|
||||
}
|
||||
name = server['name']
|
||||
|
||||
# set server busy
|
||||
server['can_use'] = False
|
||||
server_str = json.dumps(server)
|
||||
redis_conn.set(name, server_str)
|
||||
|
||||
server_log_event(server['name'], "Request_GpuServer", data, True)
|
||||
url = server['url'] + task_data['api']
|
||||
try:
|
||||
logger.info(f"call get_remote_gpu_server {url}")
|
||||
start_ms = int(time.time() * 1000) # 转换为毫秒级整数
|
||||
base64 = False
|
||||
if data['output_format'] == 'base64':
|
||||
base64 = True
|
||||
|
||||
# if 'hairColor' in task_data['api']:
|
||||
# if data['img'] == 'base64':
|
||||
# imgstr = redis_conn.get(f"img_{key}").decode("utf-8")
|
||||
# data['img'] = imgstr
|
||||
# else:
|
||||
# if data['user_img_path'] == 'base64':
|
||||
# imgstr = redis_conn.get(f"img_{key}").decode("utf-8")
|
||||
# data['user_img_path'] = imgstr
|
||||
|
||||
response = requests.post(url, headers=headers, json=data, timeout=30)
|
||||
|
||||
logger.info(f"call finshed {response.status_code} {response.headers} {response.text[:128]}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
end_ms = int(time.time() * 1000)
|
||||
if 'hairColor' in task_data['api']:
|
||||
if base64:
|
||||
result['result'] = f"data:image/jpeg;base64,{result['result']}"
|
||||
else:
|
||||
if base64:
|
||||
result['data'] = f"data:image/jpeg;base64,{result['data']}"
|
||||
|
||||
result['process_time_ms'] = (end_ms - start_ms)
|
||||
server_log_event(server['name'], "GpuServer_Response", result, True)
|
||||
result_str = json.dumps(result)
|
||||
result_status_code = '200'
|
||||
if 'state' in result:
|
||||
if result['state'] != 0:
|
||||
result_status_code = '500'
|
||||
else:
|
||||
try:
|
||||
result = response.json()
|
||||
except json.JSONDecodeError:
|
||||
result = {
|
||||
"state":-1,
|
||||
"data":"",
|
||||
'msg': f'status_code error{response.status_code} call_remote_gpu_server{url} {response.text}'
|
||||
}
|
||||
time.sleep(1)
|
||||
result_str = json.dumps(result)
|
||||
result_status_code = str(response.status_code)
|
||||
server_log_event(server['name'], f"error GpuServer_Response ", result, False)
|
||||
except Exception as e:
|
||||
logger.info(f"End call Exception {url} {type(e).__name__}, 错误信息: {e}")
|
||||
time.sleep(1)
|
||||
result = {
|
||||
"state":-1,
|
||||
"data":"",
|
||||
'msg': f'Exception call call_remote_gpu_server{url}'
|
||||
}
|
||||
result_str = json.dumps(result)
|
||||
result_status_code = '500'
|
||||
server_log_event(server['name'], f"error GpuServer_Response ", result, False)
|
||||
|
||||
logger.info(f"End call {url}")
|
||||
# release server
|
||||
server['can_use'] = True
|
||||
server_str = json.dumps(server)
|
||||
redis_conn.set(name, server_str)
|
||||
|
||||
else:
|
||||
result_str = json.dumps({
|
||||
"state":-1,
|
||||
"data":"",
|
||||
'msg': f'Timeout after all gpu server is busy'
|
||||
})
|
||||
result_status_code = '500'
|
||||
|
||||
result_key = f"result_{key}"
|
||||
redis_conn.set(result_key, result_str, ex=60)
|
||||
result_status_key = f"result_status_code_{key}"
|
||||
redis_conn.set(result_status_key, result_status_code, ex=60)
|
||||
|
||||
|
||||
def check_server_work_call(server):
|
||||
task_data = {}
|
||||
request = {}
|
||||
request['hair_id'] = "1907651680352395265"
|
||||
request['task_id'] = "1907651680352395265"
|
||||
request['user_img_path'] = "https://cdn.meidaojia.com/ZoeFiles/user2_1_%E5%89%AF%E6%9C%AC.JPG"
|
||||
request['output_format'] = "url"
|
||||
request['is_hr'] = "false"
|
||||
task_data['request'] = request
|
||||
key = str(uuid.uuid4())
|
||||
task_data['key'] = key
|
||||
task_data['api'] = "/api/swapHair/v1"
|
||||
task_data_str = json.dumps(task_data)
|
||||
threading.Thread(target=call_remote_gpu_server, args=(task_data_str, server)).start()
|
||||
|
||||
def check_server_work():
|
||||
server_list_str = redis_conn.get(GPU_SERVER_LIST).decode("utf-8")
|
||||
server_list = json.loads(server_list_str)
|
||||
for name in server_list:
|
||||
server_str = redis_conn.get(name).decode("utf-8")
|
||||
server = json.loads(server_str)
|
||||
if not server['can_use']:
|
||||
continue
|
||||
|
||||
server_log_name = f"{SERVER_LOG_}{name}"
|
||||
server_log_str = redis_conn.get(server_log_name).decode("utf-8")
|
||||
server_log = json.loads(server_log_str)
|
||||
if time.time() - server_log['last_event']['time'] > (5*60):
|
||||
logger.info("check_server_work and call name")
|
||||
check_server_work_call(server)
|
||||
|
||||
|
||||
def get_task_queue_key():
|
||||
redis_conn = get_redis_conn()
|
||||
lock = acquire_lock(redis_conn, KEY_QUEUE_LOCK_NAME)
|
||||
if not lock:
|
||||
logger.error("get_task_queue_key get lock error")
|
||||
return
|
||||
try:
|
||||
key_queue_str = redis_conn.get(QUEUE_NAME).decode("utf-8")
|
||||
key_queue = json.loads(key_queue_str)
|
||||
queue_len = len(key_queue)
|
||||
if(queue_len > 0):
|
||||
logger.info(f"get_task_queue_key queue len:{queue_len}")
|
||||
key = key_queue.pop(0)
|
||||
key_queue_str = json.dumps(key_queue)
|
||||
logger.info(f"get_task_queue_key queue len:{len(key_queue)}")
|
||||
redis_conn.set(QUEUE_NAME, key_queue_str)
|
||||
return key
|
||||
else:
|
||||
return None
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def main_worker():
|
||||
while True:
|
||||
try:
|
||||
key = get_task_queue_key()
|
||||
if key:
|
||||
task_data_str = redis_conn.get(key)
|
||||
if task_data_str:
|
||||
threading.Thread(target=call_remote_gpu_server, args=(task_data_str,)).start()
|
||||
else:
|
||||
logging.error(f"main_worker get task_data_str error with key{key}")
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.info(f"main_worker {e}")
|
||||
# check_server_work()
|
||||
time.sleep(0.1)
|
||||
|
||||
serverindex = 0
|
||||
def regServer(instance):
|
||||
global serverindex
|
||||
registerGpuServer(f"s{serverindex}_1_{instance}", f"https://{instance}-http-8801.northwest1.gpugeek.com:8443", True)
|
||||
# registerGpuServer(f"s{serverindex}_2_{instance}", f"https://{instance}-http-8801.northwest1.gpugeek.com:8443", True)
|
||||
serverindex += 1
|
||||
|
||||
def get_server_names(csv_file='servers.csv'):
|
||||
"""
|
||||
从servers.csv文件中提取所有主机名称
|
||||
|
||||
参数:
|
||||
csv_file (str): CSV文件路径,默认为'servers.csv'
|
||||
|
||||
返回:
|
||||
list: 包含所有主机名称的列表
|
||||
"""
|
||||
server_names = []
|
||||
|
||||
try:
|
||||
with open(csv_file, mode='r', encoding='utf-8') as file:
|
||||
csv_reader = csv.reader(file)
|
||||
for row in csv_reader:
|
||||
if row: # 确保不是空行
|
||||
# 取第一列并去除前后空格
|
||||
server_name = row[0].strip()
|
||||
if server_name: # 确保名称不为空
|
||||
server_names.append(server_name)
|
||||
except FileNotFoundError:
|
||||
print(f"错误:文件 {csv_file} 未找到")
|
||||
except Exception as e:
|
||||
print(f"读取文件时出错: {e}")
|
||||
|
||||
return server_names
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
print(f"[Worker] Starting with PID: {os.getpid()}")
|
||||
redis_conn.set(GPU_SERVER_LIST, "[]")
|
||||
|
||||
|
||||
# servers = get_server_names()
|
||||
# for s in servers:
|
||||
# regServer(s)
|
||||
regServer("693570664882181")
|
||||
regServer("693565557084165")
|
||||
|
||||
main_worker()
|
||||