aim-proxy/aim_proxy.py

200 lines
6.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from aim.storage.treeutils import decode_tree
from fastapi import FastAPI, HTTPException, Request
import httpx
import traceback
import uvicorn
import json
from typing import Iterator
import struct
from pydantic import BaseModel
from typing import List
from aim.sdk.repo import Repo
import os
import logging
import time
app = FastAPI(
title="aim-proxy API",
description="This is aim-proxy API",
version="1.0.0",
openapi_url="/api/v1/openapi.json",
docs_url="/api/v1/docs",
redoc_url="/api/v1/redoc"
)
# 设置日志格式
logging.basicConfig(level=logging.INFO)
@app.middleware("http")
async def log_request_time(request: Request, call_next):
start_time = time.time() # 记录请求开始时间
response = await call_next(request) # 处理请求
process_time = time.time() - start_time # 计算处理时间
logging.info(f"Request {request.method} {request.url} completed in {process_time:.4f} seconds")
return response # 返回响应
def is_running_in_kubernetes():
return 'KUBERNETES_SERVICE_HOST' in os.environ and 'KUBERNETES_SERVICE_PORT' in os.environ
if is_running_in_kubernetes():
print("Running in Kubernetes environment")
repo_path = 'aim://aim-server-service.aim:53800'
host = 'http://aim-ui-service.aim:43800'
else:
repo_path = 'aim://172.20.32.197:30058'
host = 'http://172.20.32.197:30059'
print("Not running in Kubernetes environment")
api_url = f'{host}/api/runs/search/run/'
delete_url = f'{host}/api/runs/delete-batch'
# 打开 Aim repository
repo = Repo(path=repo_path)
@app.get("/api/runs/search/run")
async def search_runs(query: str = None, offset: str = None, limit: int = 45):
try:
print("query is ", query)
print("offset is ", offset)
print("limit is ", limit)
#query_url = f'{api_url}?q={query}&limit={limit}&offset={offset}'
print("query_url is ", api_url)
data = await fetch_run_data(api_url, query=query, limit=limit, offset=offset)
result = parse_run_data(data)
return result
except Exception as e:
print(f"An error occurred while searching runs: {str(e)}")
traceback.print_exc()
raise HTTPException(status_code=500, detail=str(e))
# 定义请求体模型
class RunDeleteRequest(BaseModel):
run_hashes: List[str]
@app.post("/api/runs/delete-batch/run")
async def delete_runs(request: RunDeleteRequest):
try:
if not request.run_hashes:
raise HTTPException(status_code=400, detail="run_hash is required")
async with httpx.AsyncClient() as client:
run_hashes = request.run_hashes
print("delete url is ", delete_url)
print("run_hashes is ", run_hashes)
response = await client.post(delete_url, json=run_hashes)
response.raise_for_status() # 如果响应状态码不是 200则引发异常
return {"message": "Runs deleted successfully"}
except httpx.HTTPStatusError as e:
print(f"HTTP error occurred: {e.response.status_code}")
print(e.response.text)
except Exception as e:
print(f"An error occurred: {str(e)}")
def decode_encoded_tree_stream(stream: Iterator[bytes], concat_chunks=False) -> bytes:
# TODO: handle case when chunk ends at the middle of key/value
# TODO: if remaining part of chunk cannot be unpacked, prepend to next one and try with new chunk
prev_chunk_tail = b''
if concat_chunks:
data = b''
for chunk in stream:
data += chunk
while data:
(key_size,), data_tail = struct.unpack('I', data[:4]), data[4:]
key, data_tail = data_tail[:key_size], data_tail[key_size:]
(value_size,), data_tail = struct.unpack('I', data_tail[:4]), data_tail[4:]
value, data_tail = data_tail[:value_size], data_tail[value_size:]
data = data_tail
yield key, value
else:
for chunk in stream:
data = prev_chunk_tail + chunk
prev_chunk_tail = b''
while data:
try:
(key_size,), data_tail = struct.unpack('I', data[:4]), data[4:]
key, data_tail = data_tail[:key_size], data_tail[key_size:]
(value_size,), data_tail = struct.unpack('I', data_tail[:4]), data_tail[4:]
value, data_tail = data_tail[:value_size], data_tail[value_size:]
data = data_tail
except Exception:
prev_chunk_tail = data
break
yield key, value
assert prev_chunk_tail == b''
async def fetch_run_data(api_url: str, query: str = '', limit: int = 45, offset: str = None) -> dict:
try:
params = {
'q': query,
'limit': limit,
'offset': offset
}
async with httpx.AsyncClient() as client:
response = await client.get(api_url, params=params)
response.raise_for_status() # 如果响应状态码不是 200则引发异常
decoded_response = decode_tree(decode_encoded_tree_stream(response.iter_bytes(chunk_size=512 * 1024)))
return decoded_response
except httpx.HTTPStatusError as e:
print(f"HTTP error occurred: {e.response.status_code}")
print(e.response.text)
except Exception as e:
print(f"An error occurred: {str(e)}")
def process_metrics(hash, data):
# 过滤掉名字以 __system 开头的 metric
filtered_metrics = [metric for metric in data['traces']['metric'] if not metric['name'].startswith('__system')]
# 给剩余的 metric 添加 last_value 键
for metric in filtered_metrics:
metric['last_value'] = metric['values']['last']
# 更新数据中的 metric 列表
data['traces']['metric'] = filtered_metrics
data['run_hash'] = hash
return data
def parse_run_data(decoded_response):
try:
if not decoded_response:
print("No data to parse")
return
result = []
for i, run in decoded_response.items():
print(f"Run {i}:")
if i.startswith('progress'):
continue
run = process_metrics(i, run)
print("data is ", json.dumps(run, indent=4))
result.append(run)
print("result is ", json.dumps(result, indent=4))
return result
except Exception as e:
print(f"An error occurred while parsing data: {str(e)}")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=7123)