376 lines
11 KiB
Python
376 lines
11 KiB
Python
import time
|
|
import server
|
|
import execution
|
|
import inspect
|
|
import os
|
|
import json
|
|
from aiohttp import web
|
|
import csv
|
|
import torch
|
|
|
|
API_PATH = "/exec-timer/stat"
|
|
ENABLE_CSV_GEN = os.environ.get("EXEC_TIMER_ENABLE_CSV_GEN", True)
|
|
TIME_PRECISION = int(os.environ.get("EXEC_TIMER_TIME_PRECISION", "2"))
|
|
OUTPUT_PATH = os.path.join(os.path.dirname(__file__), "outputs")
|
|
|
|
API_ALLOW_ORIGIN = "*"
|
|
|
|
NODE_STATS = {}
|
|
WORKFLOW_INFO = {
|
|
"start_time": None,
|
|
"end_time": None,
|
|
"prompt_id": None,
|
|
"is_running": False,
|
|
"peak_memory_usage": None,
|
|
}
|
|
|
|
GLOBAL_STEP = 1
|
|
|
|
|
|
def init_stats(prompt_id: str):
|
|
global NODE_STATS, WORKFLOW_INFO, GLOBAL_STEP
|
|
NODE_STATS = {}
|
|
WORKFLOW_INFO = {
|
|
"start_time": time.perf_counter(),
|
|
"end_time": None,
|
|
"prompt_id": prompt_id,
|
|
"is_running": True,
|
|
"peak_memory_usage": None,
|
|
}
|
|
GLOBAL_STEP = 1
|
|
torch.cuda.reset_max_memory_allocated()
|
|
print(
|
|
f"\n[Exec_Timer] Workflow begins, prompt_id: {prompt_id}, initialize status..."
|
|
)
|
|
|
|
|
|
def reset_stats():
|
|
global NODE_STATS, WORKFLOW_INFO, GLOBAL_STEP
|
|
NODE_STATS = {}
|
|
WORKFLOW_INFO = {
|
|
"start_time": None,
|
|
"end_time": None,
|
|
"prompt_id": None,
|
|
"is_running": False,
|
|
"peak_memory_usage": None,
|
|
}
|
|
GLOBAL_STEP = 1
|
|
torch.cuda.reset_max_memory_allocated()
|
|
|
|
|
|
def get_accurate_time(raw_time: float, precision: int):
|
|
if precision < 0:
|
|
raise ValueError("The precision can't be smaller than zero.")
|
|
|
|
if precision == 0:
|
|
return int(raw_time)
|
|
else:
|
|
return round(raw_time, TIME_PRECISION)
|
|
|
|
|
|
def generate_csv_file(prompt_id: str):
|
|
global WORKFLOW_INFO, NODE_STATS, OUTPUT_PATH
|
|
try:
|
|
prompt_id = WORKFLOW_INFO["prompt_id"]
|
|
csv_filepath = os.path.join(OUTPUT_PATH, f"{prompt_id}.csv")
|
|
with open(csv_filepath, "w") as file:
|
|
csv_writer = csv.writer(file, delimiter=" ")
|
|
csv_writer.writerow(
|
|
["GLOBAL_STEP", "CLASS TYPE", "Execution Time(ms)", "Unique ID"]
|
|
)
|
|
for global_step, node_stat in NODE_STATS.items():
|
|
csv_writer.writerow(
|
|
[
|
|
global_step,
|
|
node_stat["class_type"],
|
|
node_stat["exec_time_ms"],
|
|
node_stat["unique_id"],
|
|
]
|
|
)
|
|
|
|
workflow_execution_time_diff = (
|
|
WORKFLOW_INFO["end_time"] - WORKFLOW_INFO["start_time"]
|
|
) * 1000
|
|
workflow_execution_time_ms = get_accurate_time(
|
|
workflow_execution_time_diff, TIME_PRECISION
|
|
)
|
|
csv_writer.writerow(
|
|
["Total Execution Time(ms)", workflow_execution_time_ms]
|
|
)
|
|
csv_writer.writerow(
|
|
["Peak Memory Usage(Bytes)", WORKFLOW_INFO["peak_memory_usage"]]
|
|
)
|
|
|
|
print(f"The csv file has been generated: {csv_filepath}")
|
|
except Exception as e:
|
|
print(f"[Exec_Timer] Failed to save csv: {str(e)}")
|
|
|
|
|
|
def generate_json_file(prompt_id: str):
|
|
global NODE_STATS, WORKFLOW_INFO, OUTPUT_PATH
|
|
|
|
node_stats_with_mem = NODE_STATS.copy()
|
|
workflow_execution_time_diff = (
|
|
WORKFLOW_INFO["end_time"] - WORKFLOW_INFO["start_time"]
|
|
) * 1000
|
|
workflow_execution_time_ms = get_accurate_time(
|
|
workflow_execution_time_diff, TIME_PRECISION
|
|
)
|
|
node_stats_with_mem["peak_memory_usage"] = WORKFLOW_INFO["peak_memory_usage"]
|
|
node_stats_with_mem["total_time"] = workflow_execution_time_ms
|
|
|
|
try:
|
|
prompt_id = WORKFLOW_INFO["prompt_id"]
|
|
json_filepath = os.path.join(OUTPUT_PATH, f"{prompt_id}.json")
|
|
with open(json_filepath, "w") as file:
|
|
json.dump(node_stats_with_mem, file)
|
|
|
|
print(f"The json file has been generated: {json_filepath}")
|
|
except Exception as e:
|
|
print(f"[Exec_Timer] Failed to save json: {str(e)}")
|
|
|
|
|
|
async def send_workflow_timer_record(request):
|
|
global NODE_EXEC_LOG, WORKFLOW_INFO, OUTPUT_PATH
|
|
post_data = await request.json()
|
|
prompt_id = post_data.get("prompt_id")
|
|
if prompt_id == WORKFLOW_INFO["prompt_id"] and WORKFLOW_INFO["is_running"]:
|
|
return_code = 200
|
|
return_msg = "Succeed to find the target prompt, but it is still running."
|
|
node_stats = {}
|
|
else:
|
|
try:
|
|
json_filepath = os.path.join(OUTPUT_PATH, f"{prompt_id}.json")
|
|
with open(json_filepath, "r") as file:
|
|
node_stats = json.load(file)
|
|
return_code = 200
|
|
return_msg = "Succeed to get the workflow execution infomation."
|
|
except Exception as e:
|
|
print(f"Failed to open {json_filepath}: {str(e)}")
|
|
return_code = 500
|
|
return_msg = f"Failed to get status info for {prompt_id}"
|
|
node_stats = {}
|
|
|
|
response_data = {
|
|
"code": return_code,
|
|
"msg": return_msg,
|
|
"prompt_id": prompt_id,
|
|
"data": node_stats,
|
|
"timestamp": int(time.time() * 1000),
|
|
}
|
|
headers = {
|
|
"Access-Control-Allow-Origin": API_ALLOW_ORIGIN,
|
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
}
|
|
return web.json_response(response_data, headers=headers)
|
|
|
|
|
|
def register_api_route():
|
|
try:
|
|
server.PromptServer.instance.app.add_routes(
|
|
[
|
|
# web.get(API_PATH, get_workflow_timer_record),
|
|
web.post(API_PATH, send_workflow_timer_record),
|
|
# web.options(API_PATH, get_workflow_timer_record)
|
|
]
|
|
)
|
|
print(
|
|
f"[Exec_Timer] API registered | access address: http://0.0.0.0:8188{API_PATH}"
|
|
)
|
|
except Exception as e:
|
|
print(f"[Exec_Timer] Failed to register API: {str(e)}")
|
|
|
|
|
|
origin_execute = execution.execute
|
|
|
|
# async
|
|
if inspect.iscoroutinefunction(origin_execute):
|
|
|
|
async def hooked_execute(
|
|
server_inst,
|
|
dynprompt,
|
|
caches,
|
|
current_item,
|
|
extra_data,
|
|
executed,
|
|
prompt_id,
|
|
execution_list,
|
|
pending_subgraph_results,
|
|
pending_async_nodes,
|
|
*args,
|
|
**kwargs,
|
|
):
|
|
global NODE_STATS, WORKFLOW_INFO, GLOBAL_STEP
|
|
unique_id = current_item
|
|
|
|
if not WORKFLOW_INFO["is_running"]:
|
|
init_stats(prompt_id)
|
|
|
|
try:
|
|
node_info = dynprompt.get_node(unique_id)
|
|
class_type = node_info.get("class_type", "Unknown Class")
|
|
display_id = dynprompt.get_display_node_id(unique_id)
|
|
except Exception:
|
|
class_type = "Unknown Class"
|
|
display_id = "Unknown Id"
|
|
|
|
node_start_time = time.perf_counter()
|
|
|
|
result = await origin_execute(
|
|
server_inst,
|
|
dynprompt,
|
|
caches,
|
|
current_item,
|
|
extra_data,
|
|
executed,
|
|
prompt_id,
|
|
execution_list,
|
|
pending_subgraph_results,
|
|
pending_async_nodes,
|
|
*args,
|
|
**kwargs,
|
|
)
|
|
|
|
# Caculate the execution time
|
|
node_end_time = time.perf_counter()
|
|
exec_time_ms = (node_end_time - node_start_time) * 1000
|
|
exec_time_ms = get_accurate_time(exec_time_ms, TIME_PRECISION)
|
|
|
|
NODE_STATS[str(GLOBAL_STEP)] = {
|
|
"display_id": display_id,
|
|
"class_type": class_type,
|
|
"start_time": node_start_time,
|
|
"exec_time_ms": exec_time_ms,
|
|
"unique_id": unique_id,
|
|
}
|
|
GLOBAL_STEP += 1
|
|
|
|
print(
|
|
f"[Exec_Timer] Node Exec Finished | Class Type{class_type:25s} | Front ID: {display_id:5s} | Execution Time: {exec_time_ms} ms | Unique_id: {unique_id}"
|
|
)
|
|
|
|
return result
|
|
else:
|
|
|
|
def hooked_execute(
|
|
server_inst,
|
|
dynprompt,
|
|
caches,
|
|
current_item,
|
|
extra_data,
|
|
executed,
|
|
prompt_id,
|
|
execution_list,
|
|
pending_subgraph_results,
|
|
*args,
|
|
**kwargs,
|
|
):
|
|
global NODE_STATS, WORKFLOW_INFO, GLOBAL_STEP
|
|
unique_id = current_item
|
|
|
|
if not WORKFLOW_INFO["is_running"]:
|
|
init_stats(prompt_id)
|
|
|
|
try:
|
|
node_info = dynprompt.get_node(unique_id)
|
|
class_type = node_info.get("class_type", "Unknown Class")
|
|
display_id = dynprompt.get_display_node_id(unique_id)
|
|
except Exception:
|
|
class_type = "Unknown Class"
|
|
display_id = "Unknown ID"
|
|
|
|
node_start_time = time.perf_counter()
|
|
|
|
result = origin_execute(
|
|
server_inst,
|
|
dynprompt,
|
|
caches,
|
|
current_item,
|
|
extra_data,
|
|
executed,
|
|
prompt_id,
|
|
execution_list,
|
|
pending_subgraph_results,
|
|
*args,
|
|
**kwargs,
|
|
)
|
|
|
|
node_end_time = time.perf_counter()
|
|
exec_time_ms = (node_end_time - node_start_time) * 1000
|
|
exec_time_ms = get_accurate_time(exec_time_ms, TIME_PRECISION)
|
|
|
|
# 存储统计数据
|
|
NODE_STATS[str(GLOBAL_STEP)] = {
|
|
"display_id": display_id,
|
|
"class_type": class_type,
|
|
"start_time": node_start_time,
|
|
"exec_time_ms": exec_time_ms,
|
|
"unique_id": unique_id,
|
|
}
|
|
GLOBAL_STEP += 1
|
|
|
|
print(
|
|
f"[Exec_Timer] Node Exec Finished | Class Type: {class_type:25s} | Front ID: {display_id:5s} | Execution Time: {exec_time_ms} ms | Unique_id: {unique_id}"
|
|
)
|
|
|
|
return result
|
|
|
|
|
|
execution.execute = hooked_execute
|
|
|
|
|
|
origin_send_sync = server.PromptServer.send_sync
|
|
|
|
|
|
def hooked_send_sync(self, event, data, sid=None):
|
|
global WORKFLOW_INFO, ENABLE_CSV_GEN
|
|
origin_send_sync(self, event, data, sid)
|
|
|
|
# The signal that indicate the workflow ends: event = 'executing' && data.node = None
|
|
if (
|
|
event == "executing"
|
|
and data
|
|
and data.get("node") is None
|
|
and WORKFLOW_INFO["is_running"]
|
|
):
|
|
WORKFLOW_INFO["end_time"] = time.perf_counter()
|
|
workflow_execution_time_tmsp = (
|
|
WORKFLOW_INFO["end_time"] - WORKFLOW_INFO["start_time"]
|
|
) * 1000
|
|
workflow_execution_time_ms = round(workflow_execution_time_tmsp, TIME_PRECISION)
|
|
print(
|
|
f"The total excution time of the workflow {WORKFLOW_INFO['prompt_id']} is {workflow_execution_time_ms}"
|
|
)
|
|
|
|
WORKFLOW_INFO["peak_memory_usage"] = torch.cuda.max_memory_allocated()
|
|
print(
|
|
f"[Exec-Timer] The max memory usage is : {WORKFLOW_INFO['peak_memory_usage']} MB"
|
|
)
|
|
prompt_id = WORKFLOW_INFO["prompt_id"]
|
|
generate_json_file(prompt_id)
|
|
if ENABLE_CSV_GEN:
|
|
generate_csv_file(prompt_id)
|
|
reset_stats()
|
|
|
|
|
|
server.PromptServer.send_sync = hooked_send_sync
|
|
|
|
register_api_route()
|
|
|
|
print(
|
|
"""
|
|
_____________ _____ _____ __
|
|
/ ___/ __/ __/ / ___/__ _______ / __/ | /| / /
|
|
/ /___\ \/ _/ / /__/ _ \/ __/ -_)\ \ | |/ |/ /
|
|
_____\___/___/___/__\___/\___/_/ _\__/___/_|__/|__/___ __
|
|
/_ __/ _/ |/ / __/ _ \ / _ \/ / / / / / ___/ _/ |/ /
|
|
/ / _/ // /|_/ / _// , _/ / ___/ /__/ /_/ / (_ // // /
|
|
/_/ /___/_/ /_/___/_/|_| /_/ /____/\____/\___/___/_/|_/
|
|
"""
|
|
)
|
|
|
|
print(
|
|
f"[Exec_Timer] The pure backend plugin Exec_Timer load successfully | Generate CSV File:{'True' if ENABLE_CSV_GEN else 'False'} | Time Precision: {TIME_PRECISION}"
|
|
)
|