From 9abbf8d96a5af7387084714ea680bc87bb140584 Mon Sep 17 00:00:00 2001 From: Lxy Date: Sun, 15 Mar 2026 12:41:18 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0=E8=A3=85=E9=A5=B0?= =?UTF-8?q?=E5=99=A8=EF=BC=8C=E7=BB=9F=E8=AE=A1=E4=BD=BF=E7=94=A8=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal_data_service.cpython-311.pyc | Bin 31026 -> 34419 bytes app/adapters/internal_data_service.py | 68 ++++++++++++++++ app/core/__pycache__/metrics.cpython-311.pyc | Bin 8244 -> 10772 bytes app/core/metrics.py | 75 +++++++++++++++++- 4 files changed, 142 insertions(+), 1 deletion(-) diff --git a/app/adapters/__pycache__/internal_data_service.cpython-311.pyc b/app/adapters/__pycache__/internal_data_service.cpython-311.pyc index e39274a3c4df2f4e8309c90062ee7be21a071637..0c26ca1ed77c1a67274c66269162a886e46d285f 100644 GIT binary patch delta 11204 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 delta 7730 zcmb_h3v65E6~5Q89lx&a#Eu;&apI-SQ&JqJUUtr*u)qQAwXyvNJA65LPMK2O_L@qUCX4g4egx&y0M)m zlxWh&fdmsZ3cSmXtMOsoX1VxC5s3=rWq(U}~3lXDSp_pVN{!I8Yst(!Rnk5=d$5Ki(3WL=xK0l7sD%32~jJ=smk~LwS&4p*Go9Bh8xB%gdz-pAmKs51E`TOpMTLYKES zHz~NVJ_WiUqAznH3pUW-#ar;*et`?2^Az2q&nTO?3z?O&c5*lDt0b4o6@0I30Ctk^ zSqpu(19n}yAYc!9q66KEJl3p8W#a7E=R}VzYp1a|Pr^Qz?8Ls3CCWJ$61f#Ol9h_F z$W=JJ>WbmBjjqPg)mMx*DjpQexdsLPEfxP&aqN_=sx)|M54cNvaYbW{K=*G&?$^uwec# z{^v}-ZsIj)hDA#>QB^4phtNOaY;dEPgp0v>W-_nOmczNmAgpZsnRzu;YvI0iHBjAj ze}L4lnYf2A3Z|MVo*7POnG?=7ZSjP#g}bO`PG;h!+HMG#pqYN>zn+R@&8$;JO zaaW}<#Qmnkc_P?0)&#Ewo20#1RFB~*j|iWd^KXYfGl={1Z_NFb!Mz)MG;=JTO&n6f zm25XW)f%eT%xX549ac44oGG!aB1dCcb~}z_YvJS8l?}9dRza|frkm8EWFpIMp#}$Z zwaw3ksM*3Y$z*RVKFIFGYS^KB{eCR7;vP7GU5y<=AzTHTF`khXM(bkz2%1G1NvPRG zx*u0Hn(9S|<~t^>6?aU^rx~=-vALkN%@rr|pSSHbn0u(@I#hfujI;;ME3m40VY1!3 zxEPVnROoBt%i`>`Ib_#KDfEoBq9T28sogD|#j2_x@X%YeKoNBz?Oez=I(+N65UF3u z;+AA3nn?F$io)1ZoXV~z7$K%IQ50ACJFBmvSufFmg9s!FdUY`;yQTah9FdT1;iVmqbo6gBM!&UMi)H{Y!f~-QJ%6;tg_7rW1YAkKYZ9#FMa@% zT?^bi9B!fRt<-%ZTCNw?F72w_Ms3>($mn(;^;fB~10GtM>$;hW+#LAZCl_M35;zHN zBj9$klS;b9z^d!(I#X1?gMe(KL^YE;f2UfM_%AJML=To!b*9p%LDMhQKc{Hu4 zKdu`(8L$k&5P_8-L_pS{JCvjw?Ss@%)CoBhV+fiBqn#3k_SLnW6V#ZcMk{J1s$irU z)ipwItkjJPCO;0@)e8)N5%N#0eoquz^2xQP!e{K zpDF#q>l3J-zk+FBc)*Ua5cjR7^)?C5!-7iMMT2+dyVpM?ipSBum!qtI-Vg|fh^AZ? z`v$hD(^yZy>J9Bub14~QH{^`p#If)gej1|g4p_eNVRH*MjLqkc`}>XFmE7>`WOz!p zFFF+OD_Ul_5F==YzLPS?{We^;safhGi3uFRL&N?}8By$k4{ltxi4#{7@c;%hJuD=# zROByeZ-%gNl9t&*TBdkPTS)6%M6j4(2?3pNxB)WK7mE;3 z#AFoVN)|9i)h+^Z4mM8ENkDIloj}k`+2Nt2!oE!nv^v&JP%B^<(SznPsuNHoSAQeI z#Fn}6%9bX>qyT@|vJmEMZNih28Ba=Xh;9w?ax<1sZskMX+PVl$-P$mzbuoKEDd zy2hL?qq%@ z*OD{rkZX~1_SjOsrvUD$;abX<9C97X56TwIp&HnmMa;OzMo0>M` zr--dW!_?z1|4tUa{XzKM!5_mrdn8!1%Vs`~!^W;VzPA?ob_G|Q!kXsgU)_VllxX31 zaX6kGW+j&uGk)fugjaVhkhak`@DOs)nE^iCr8+vWvZHVn0`K<*;Oo2pP%~{o^KcAn zg-{|0Pu{WqrVv%ht-{ul+lk`(_2JmB8DcVff=16lQ0ZVcb$-&nd{4uYJKLnYXjSyR z#8p-2zgWC3wGxLHr0O!Cg>wh% z{R2~kDoVddhe9s>WF$(`|9RJv&QsW?*+_aK9nYjnHT)cQsaG(+0C(@5BTdk5F7v^| zdq>5ysMw#tkD@ks=WaKWe=i<{Pw%c1Pl0=%e@+Ow^n=>WtmnZc*VpyNlCgALiK+vN zlKpP!lHqIniX!UFr;%=~o$TdLQJ-!vdU_R82cmt+%;BN}*~|i8B!(tPM|{w;-zuFi z+fRJIF6>>)pT9r;$72t%)9`$3nJ$MP+J;!BFM)@`ks+q2>Q#lk3{AaZ>76neyLxpQ z?{i&!W%(&wkXA)6qClR`WE1hLzmK1y=H|OUMsAiz3P#yz+yoc=UbsvCt$Vt~;zokd zr_}G`^7v*jfy7J9s5+d&(rC$MmJ%N&rk5`?r;}0t9E2-w248_sl(2MxHv9ngqtUtZ zHGPbXj_l%eKiS3W{a3e(SII7F3oDeBY%Gy9Vj@=9D);3F4!pt5&Xpju3njX5IE5lL zH>on2!4z;{7%zL${WEQB7UOsgJ+{7~baDWNs+2X6|Jba}$A^}|j-)&9KXB9KP2D$G zw}DIMFR>RdY6hc)EcCw+{U&V~{xK-I7fllhNA{7-ZQ|=ZboQj`^sw0p`!kL3aH@7C zmtLd$vaF0?R*}YwlI$?0cOhK6?tnApM{mJzQytOHV7k` zz%nj{Yd4W5DTkFr|3ImLnH}zJ(rKV{%1#nD6+`}=(f#2~SbmVieX=JA@M+Hft0?I` z+5xOq9nO186=i|XGd@VEd%3oLl0TZQC6hQgyvEGeUJFYO)i#%K&%EScK;Y0~>CqCM zw7{-IqdD^DuwloJaO3PLoQd{G7YTY+Mlri2dwY_qnBx>uxTmS~EWrx|=LskpvX=>d zO7IH7s|2sDCw!COErPcR-X(aC;1>kl1eoU)61~T%M6i+Ee_ka|^isJEJazL;cEJ3R zB$j=c>8*fI#;EMU^3wmK+zFrFvlDr$Fzs7X#*_V8FXwMY{$jZu@8*l^nRyN6W8Tjt z@e02I*Et3HmLJdWIdVeW?50h72*{v$o~z$l8R?#xtAm}Q9y%Y;jWdYTEvv0jGD-1SVB=3rSOUpZO$}vO zb`xV6#{XxDzG?pU`C7`?_nBoC5)UVpHS8mtj;2v3sNccIWQ8b-lLn(`oD>Lbg6M_) z)>yR*9(cE8zmQT`G3mD-KqOnDft!7kK-n(%!w5{U)NG887hmOG3ZBIA|v{ YJ=p1S;o_h2trT9VTyLs)!zd#D2Np|_FaQ7m diff --git a/app/adapters/internal_data_service.py b/app/adapters/internal_data_service.py index 5ff0dbf..f408eca 100644 --- a/app/adapters/internal_data_service.py +++ b/app/adapters/internal_data_service.py @@ -3,12 +3,21 @@ 将 AmazingData SDK 的调用封装为内部接口 对外接口不直接调用 SDK,而是通过内部接口调用 """ +import time +import functools import pandas as pd from datetime import datetime, timedelta from typing import List, Optional, Dict, Any from app.core.logger import info, error +# 导入指标统计 +try: + from app.core.metrics import record_internal_call + METRICS_AVAILABLE = True +except ImportError: + METRICS_AVAILABLE = False + # 数据库相关导入(可选,如果数据库未配置则回退到SDK) try: @@ -20,12 +29,43 @@ except ImportError: DB_AVAILABLE = False +def track_internal_call(category: str): + """对内接口调用统计装饰器 + + Args: + category: 接口类别 (market/base/info) + """ + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not METRICS_AVAILABLE: + return func(*args, **kwargs) + + method_name = func.__name__ + start_time = time.time() + status = "success" + + try: + result = func(*args, **kwargs) + return result + except Exception as e: + status = "error" + raise e + finally: + duration = time.time() - start_time + record_internal_call(category, method_name, status, duration) + + return wrapper + return decorator + + class _MarketDataInternal: """市场数据内部接口 - 封装 _market_data""" def __init__(self, market_data): self._market_data = market_data + @track_internal_call("market") def login(self, username: str, password: str, ip: str, port: str) -> bool: """登录""" try: @@ -39,6 +79,7 @@ class _MarketDataInternal: error(f"[_MarketDataInternal] Login failed: {e}") raise + @track_internal_call("market") def is_login(self) -> bool: """检查登录状态""" try: @@ -46,6 +87,7 @@ class _MarketDataInternal: except Exception: return False + @track_internal_call("market") def query_kline( self, code_list: List[str], @@ -65,6 +107,7 @@ class _MarketDataInternal: error(f"[_MarketDataInternal] Query kline failed: {e}") return {} + @track_internal_call("market") def query_snapshot( self, code_list: List[str], @@ -89,6 +132,7 @@ class _BaseDataInternal: def __init__(self, base_data): self._base_data = base_data + @track_internal_call("base") def get_code_list(self, security_type: str) -> List[str]: """获取代码列表 - 优先从数据库获取,无数据则从SDK获取并缓存""" # 1. 先尝试从数据库获取 @@ -171,6 +215,7 @@ class _BaseDataInternal: finally: db.close() + @track_internal_call("base") def get_future_code_list(self, security_type: str) -> List[str]: """获取期货代码列表""" try: @@ -179,6 +224,7 @@ class _BaseDataInternal: error(f"[_BaseDataInternal] Get future code list failed: {e}") return [] + @track_internal_call("base") def get_code_info(self, security_type: str) -> pd.DataFrame: """获取代码信息""" try: @@ -187,6 +233,7 @@ class _BaseDataInternal: error(f"[_BaseDataInternal] Get code info failed: {e}") return pd.DataFrame() + @track_internal_call("base") def get_calendar(self, market: str) -> List[int]: """获取交易日历 - 优先从数据库获取,无数据则从SDK获取并缓存""" # 1. 先尝试从数据库获取 @@ -270,6 +317,7 @@ class _BaseDataInternal: finally: db.close() + @track_internal_call("base") def get_adj_factor( self, code_list: List[str], @@ -287,6 +335,7 @@ class _BaseDataInternal: error(f"[_BaseDataInternal] Get adj factor failed: {e}") return pd.DataFrame() + @track_internal_call("base") def get_backward_factor( self, code_list: List[str], @@ -304,6 +353,7 @@ class _BaseDataInternal: error(f"[_BaseDataInternal] Get backward factor failed: {e}") return pd.DataFrame() + @track_internal_call("base") def get_etf_pcf(self, code_list: List[str]) -> tuple: """获取ETF申赎数据""" try: @@ -312,6 +362,7 @@ class _BaseDataInternal: error(f"[_BaseDataInternal] Get ETF PCF failed: {e}") return ({}, {}) + @track_internal_call("base") def get_hist_code_list(self, security_type: str) -> pd.DataFrame: """获取历史代码列表""" try: @@ -327,6 +378,7 @@ class _InfoDataInternal: def __init__(self, info_data): self._info_data = info_data + @track_internal_call("info") def get_equity_structure( self, code_list: List[str], @@ -344,6 +396,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get equity structure failed: {e}") return {} + @track_internal_call("info") def get_share_holder( self, code_list: List[str], @@ -368,6 +421,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get share holder failed: {e}") return {} + @track_internal_call("info") def get_holder_num( self, code_list: List[str], @@ -389,6 +443,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get holder num failed: {e}") return {} + @track_internal_call("info") def get_income( self, code_list: List[str], @@ -410,6 +465,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get income failed: {e}") return {} + @track_internal_call("info") def get_balance_sheet( self, code_list: List[str], @@ -431,6 +487,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get balance sheet failed: {e}") return {} + @track_internal_call("info") def get_cash_flow( self, code_list: List[str], @@ -452,6 +509,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get cash flow failed: {e}") return {} + @track_internal_call("info") def get_profit_express( self, code_list: List[str], @@ -473,6 +531,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get profit express failed: {e}") return {} + @track_internal_call("info") def get_profit_notice( self, code_list: List[str], @@ -494,6 +553,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get profit notice failed: {e}") return {} + @track_internal_call("info") def get_margin_summary( self, local_path: str, @@ -513,6 +573,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get margin summary failed: {e}") return pd.DataFrame() + @track_internal_call("info") def get_margin_detail( self, code_list: List[str], @@ -534,6 +595,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get margin detail failed: {e}") return {} + @track_internal_call("info") def get_long_hu_bang( self, code_list: List[str], @@ -555,6 +617,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get long hu bang failed: {e}") return pd.DataFrame() + @track_internal_call("info") def get_block_trading( self, code_list: List[str], @@ -576,6 +639,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get block trading failed: {e}") return pd.DataFrame() + @track_internal_call("info") def get_index_constituent( self, code_list: List[str], @@ -593,6 +657,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get index constituent failed: {e}") return {} + @track_internal_call("info") def get_index_weight( self, code_list: List[str], @@ -614,6 +679,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get index weight failed: {e}") return {} + @track_internal_call("info") def get_fund_share( self, code_list: List[str], @@ -635,6 +701,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get fund share failed: {e}") return {} + @track_internal_call("info") def get_kzz_issuance( self, code_list: List[str], @@ -652,6 +719,7 @@ class _InfoDataInternal: error(f"[_InfoDataInternal] Get kzz issuance failed: {e}") return {} + @track_internal_call("info") def get_history_stock_status( self, code_list: List[str], diff --git a/app/core/__pycache__/metrics.cpython-311.pyc b/app/core/__pycache__/metrics.cpython-311.pyc index 01eaa7911d06404fecdebf3d4e6cd28b88c65309..b7c6c33beabb72e475bdb83196be2f2722960491 100644 GIT binary patch delta 4388 zcmbtWdu&tJ89(>>`jwX*+a%5d+j++cb^LnVR=6s1$KA=0_+$NjJ`KM|U246{} zsg5v9J>hPNP@}IzCi@6AO%ZCoA`|j1jO@>6WmcaFh8acyj6zy4MSMlH&{zBni6|$? zN!>H#ghVA;6fPaN`E28MpB-#Uv^eY-U*lUN5t%R(Euoe%LalE}S9F>z*%PJIM(xxw zCIffPThi6CD+>69k@H&)s|sj2byE8ntN|H}oXjaKR?tdXMXSf;719cjn(12VqSe$b z?0Z(*PxvZ@gBn^(>uCKdBUM7$KpR)2qguFbqRrI1YFlf?nikqh*R7Jd#Jcsg4Z>}Q zBqSb;$PHpM?Vz0ykzMS0!|FZzJfKxbH_}ZjHdOio&(^G{fDLqj*}=_WSiO-dL&SSzq8cI{ zjQAupg~&s~_3hk1Sh9CO?W zw<44R2xlIThpz!5Z3qWOSXiC333rKXENxjPE>Uyd?B!<;awjkz<_*Jqs5f0G5}dRw zj|><#u*%C3oKUR)rvu7XH~@xY$-yJ+CmtPv%Hj`30F1IP?cfpi6C`bnj;M@3&nE55 z)0AW41IVT75s^H?5lJhd$Y^xZy4*=9VjqoGKHy@&TcF){)+e|KQONLoaTkIIp%$SY zK~$L7AyyVIUId(|rvQ@qe@2$fr5Tb3MKU{)%mI)ON)s#SuR!LVi0T!)TGb2i_~ni& zmEdH{16gGe5yJK?jdA_kv?ClCAEALKxEIJ87%HCfBzZHKgc647WI>#rjJLR z58A;ErVVO55T8)fuEiC?nx9VaKs*$U_*E8+MyNVj-W+d6S++b#D>xMblp< z3eLD!^3rSwSfi^Vpd|0ZZ$(QJi1V1BspgYJE3aX-N4_&VaaHFUHW3E{{C zGhpQP__u(u^m@?t7rFHTV7;e>iw+pX$$nP5QFPkb2TYEAU|QlMxCR|Sasq3?0k(0ucyg^!7X5-QQ-x{TO_}zMVwdF@5Ff+fn0m-`sN`pL2Vsi4J%FsG2mNgz z^WK^*F8y&}{8~F`*#zE_mdmaExpjx~>pU=n@<9O)1LU8PHfXKTM}YH*7O`$Ii}(hC z)c#@kYvKL0i?@#iRMw8Y*PCB~pDIxb`ak(-e&&t2 zb3ZKbB+wH4vPZJZvvBtMeDbuXS)B+5nX0xk&ZB(E!s4Op@?>{-J9%OL!u2n1oLPAL zwJ&bGDm?MS5E3Lp)G!`+JVyg?UzXk{k;M8-c$FnZj#>F%2g#AE@COHbxH!0@%g8ysOQlM=%inRdFUP#wYMa#o8aNJ5(~tS@twfVCJR@H1CUnY zp>ZZUP1<-Ypa$Y`p4Q`2V?2pU*jB{bGHvcX8Duf+q+T-@-#p(%*$bo#92dY69``R9 zGjXQIq7nEMzy>DzURq+R83<7r2yo!uFY`)wXZ~E~miO`oRBS|O0!SPEei{w>{k#QP z3PLNwSp+MBsCay*@ySKVXoD3;3xz=NPHERFek!$UhblV&PgOS6pnlph1__0-h3{S! zI%saE=RxF_v`;E~%Fu#W;?HJp&*5zo|GrjT)hxZJ^;T`(>KHt7xW9LJfA7&Df6r4r zeTRB>9qLUN_KnA)Jl>1dEnd}W<7fa<6ANXdXw_b=a_?FP=CW@9(f7q;=|-ODp`yQu zrXzZXXau6nMRA`%y&>&yRgDSpXs{=X&Y4z5qR}wNe#fzeajagB?S*5H5LL}j;!Gq4 zV_lI&_%axe(upv8f?otF_C)pfkoSyElB5)=ofTJpq9v`fJxCHJJt zYR(-`9!^y@&MM{EE7J*X?%%(k*+itqnZHz1X}F9y-RDlEl**LSoKos%#dXK1IAu80 zPI;C%G9{$8^-={0Q%XxpZ#f5>u1pmjm=#yXtgn^t5P*9Kv&5CL&g^oHNu4&EgXE) delta 2006 zcmb7DOKe+36n*dc*>OI$6aShxiS5Lnr$`k=2zAjYr3j%Ei4{v=g(?;ZJqf7@u_!x)*dP|%JI)7EHdvnX?wtFb znR{=af0S^pIUEfN{%-Yro?mQTb+&4EwAJ2$)k|GVD%c9nc~{ak-<)h#6;PqB&@%5% zx>ZF}m>uj;pHraWo_gP+C?Dgh{gNKk)oApVT59d(V+1HHQfb|9~T0s6zfU7Aj&F)ItUZRJ*WBo+c= zX#y4CU}13L>StZBmj$zS*teo5yTON?pM|qJ4B$ROGPL0$8yoCLE()Ev58^t&V#pmt zE)GXwkacA>7+SF=d$xDOxa)-o#?xQ~?F@*svtby;U^1;K{gK+^tV_-?`-U7;qiL1*k=sjYA4PgYd8S^WgR1!)30r)YW`7C8P4wX!V|0 z_K)D{X4^hId6ryXAvlLHYiOoDT`s5c#cavc3Hb%&cPlNC=x+}qQ>&T`uMk z#9MXIA+s)(f>I`x;!R{l!@?Z|uOqDEvyn`_&y!oQCVuI<&?ps>yDW}(-&Gyrr|tuY z?nn=28IJ^P3+V8@B9f6QKN%Y;pR9e(&}V>`#AlISH6zT(u&XYc#`??oS``j)A{rWV zqM4j|Ip$nmmK;zySF-12*UDa!4RDb>_KCGcR~9iCn;v|kWFG&Y{|`!RqdyL+>fHFb PC#R|n;=1v+Ri^qI#M-MH diff --git a/app/core/metrics.py b/app/core/metrics.py index 1f3b5b3..31c4113 100644 --- a/app/core/metrics.py +++ b/app/core/metrics.py @@ -3,7 +3,36 @@ from contextvars import ContextVar from typing import Callable, Optional import time -from prometheus_client import Counter, Histogram, Gauge, Info, generate_latest, CONTENT_TYPE_LATEST +# 尝试导入 prometheus_client,如果不存在则提供空实现 +try: + from prometheus_client import Counter, Histogram, Gauge, Info, generate_latest, CONTENT_TYPE_LATEST + PROMETHEUS_AVAILABLE = True +except ImportError: + PROMETHEUS_AVAILABLE = False + # 提供空的指标类 + class _MockMetric: + def __init__(self, *args, **kwargs): + pass + def labels(self, *args, **kwargs): + return self + def inc(self, *args, **kwargs): + pass + def dec(self, *args, **kwargs): + pass + def set(self, *args, **kwargs): + pass + def observe(self, *args, **kwargs): + pass + def info(self, *args, **kwargs): + pass + + Counter = _MockMetric + Histogram = _MockMetric + Gauge = _MockMetric + Info = _MockMetric + generate_latest = lambda: b"" + CONTENT_TYPE_LATEST = "text/plain" + from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware @@ -75,6 +104,25 @@ cache_hit_ratio = Gauge( ['cache_type'] ) +# ============================================ +# 对内接口调用统计(Internal SDK Interface) +# ============================================ + +# 对内接口调用计数器 +internal_calls_total = Counter( + 'internal_calls_total', + 'Total internal SDK interface calls', + ['category', 'method', 'status'] # category: market/base/info, method: 方法名, status: success/error +) + +# 对内接口调用持续时间 +internal_call_duration_seconds = Histogram( + 'internal_call_duration_seconds', + 'Internal SDK interface call duration', + ['category', 'method'], + buckets=[.001, .005, .01, .025, .05, .1, .25, .5, 1.0, 2.5, 5.0, 10.0] +) + # 应用信息 app_info = Info( 'market_data_service', @@ -151,6 +199,31 @@ def set_cache_hit_ratio(cache_type: str, ratio: float): cache_hit_ratio.labels(cache_type=cache_type).set(ratio) +# ============================================ +# 对内接口调用统计函数 +# ============================================ + +def record_internal_call(category: str, method: str, status: str, duration: float): + """记录对内接口调用指标 + + Args: + category: 接口类别 (market/base/info) + method: 方法名 + status: 状态 (success/error) + duration: 调用耗时(秒) + """ + internal_calls_total.labels( + category=category, + method=method, + status=status + ).inc() + + internal_call_duration_seconds.labels( + category=category, + method=method + ).observe(duration) + + def set_app_info(version: str, build_time: str, git_commit: str = ""): """设置应用信息""" app_info.info({