
    #j>R                        U d Z ddlZddlZddlZej                            d          r ej        d          ndZddlZddl	Z	ddl
mZ ddlmZmZ ddlmZ ddlmZ ddlmZ dd	lmZmZ dd
lmZ erddlZdefdZdZdZdZde defdZ! ej"        e#          Z$da%d!dedefdZ&d Z' G d d          Z( G d de(          Z) G d de(          Z* e)            a+e(e,d<   de(fdZ-de*fd Z.dS )"a  
OpenTelemetry metrics instrumentation for Hindsight API.

This module provides metrics for:
- Operation latency (retain, recall, reflect) with percentiles
- Token usage (input/output) per operation
- Per-bank granularity via labels
- LLM call latency and token usage with scope dimension
- HTTP request metrics (latency, count by endpoint/method/status)
- Process metrics (CPU, memory, file descriptors, threads)
- Database connection pool metrics
    Nresource)contextmanager)TYPE_CHECKINGCallable)metrics)PrometheusMetricReader)MeterProvider)"ExplicitBucketHistogramAggregationView)Resourcereturnc                  "    ddl m}   |             S )z>Get current tenant (schema) from context for metrics labeling.r   get_current_schema)"hindsight_api.engine.memory_enginer   r   s    ]/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/hindsight_api/metrics.py_get_tenantr   "   s&     FEEEEE    )皙?      ?      ?g      ?      ?       @      @      @g      @      $@      .@g      4@      >@      N@      ^@)r   r   r   r   r   r   r   r   r   r   r   r    )g{Gzt?g{Gz?g?g?r   r   r   r   g      @r   r   r   token_countc                 f    | dk     rdS | dk     rdS | dk     rdS | dk     rdS | d	k     rd
S | dk     rdS dS )a4  
    Convert a token count to a bucket label for use as a dimension.

    This allows analyzing token usage patterns without high-cardinality issues.

    Buckets:
    - "0-100": Very small requests/responses
    - "100-500": Small requests/responses
    - "500-1k": Medium requests/responses
    - "1k-5k": Large requests/responses
    - "5k-10k": Very large requests/responses
    - "10k-50k": Huge requests/responses
    - "50k+": Extremely large requests/responses

    Args:
        token_count: Number of tokens

    Returns:
        Bucket label string
    d   z0-100i  z100-500i  z500-1ki  z1k-5ki'  z5k-10kiP  z10k-50kz50k+ )r!   s    r   get_token_bucketr%   5   si    * Sw	s		y	t		x	t		w	u		x	u		yvr   hindsight-api1.0.0service_nameservice_versionc                    t          j        | |d          }t                      }t          dt	          t
                              }t          dt	          t                              }t          dt	          t                              }t          ||g|||g          }t          j
        |           t          j        t                    a|S )aT  
    Initialize OpenTelemetry metrics with Prometheus exporter.

    This should be called once during application startup.

    Args:
        service_name: Name of the service for resource attributes
        service_version: Version of the service

    Returns:
        PrometheusMetricReader instance (for accessing metrics endpoint)
    )zservice.namezservice.versionhindsight.operation.duration)
boundaries)instrument_nameaggregationhindsight.llm.durationhindsight.http.duration)r   metric_readersviews)r   creater   r   r
   DURATION_BUCKETSLLM_DURATION_BUCKETSHTTP_DURATION_BUCKETSr	   r   set_meter_provider	get_meter__name___meter)r(   r)   r   prometheus_readerduration_viewllm_duration_viewhttp_duration_viewproviders           r   initialize_metricsr@   `   s     (.	
 	
 H /00 66BRSSS  M 06BVWWW   16BWXXX   )*/1CD  H x((( x((Fr   c                  <    t           t          d          t           S )zGet the global meter instance.Nz9Metrics not initialized. Call initialize_metrics() first.)r:   RuntimeErrorr$   r   r   r8   r8      s    ~VWWWMr   c                       e Zd ZdZe	 	 	 ddededededz  dedz  f
d	            Z	 	 	 ddedededededede	fdZ
edededeg ef         fd            ZddZdS )MetricsCollectorBasez"Base class for metrics collectors.apiN	operationbank_idsourcebudget
max_tokensc                     t           )z8Context manager to record operation duration and status.NotImplementedErrorselfrF   rG   rH   rI   rJ   s         r   record_operationz%MetricsCollectorBase.record_operation   s
     "!r   r   Tr?   modelscopedurationinput_tokensoutput_tokenssuccessc                     t           )  
        Record metrics for an LLM call.

        Args:
            provider: LLM provider name (openai, anthropic, gemini, groq, ollama, lmstudio)
            model: Model name
            scope: Scope identifier (e.g., "memory", "reflect", "consolidation")
            duration: Call duration in seconds
            input_tokens: Number of input/prompt tokens
            output_tokens: Number of output/completion tokens
            success: Whether the call was successful
        rL   rO   r?   rQ   rR   rS   rT   rU   rV   s           r   record_llm_callz$MetricsCollectorBase.record_llm_call   s
    , "!r   methodendpointstatus_code_getterc                     t           )z/Context manager to record HTTP request metrics.rL   rO   r[   r\   r]   s       r   record_http_requestz(MetricsCollectorBase.record_http_request   s
     "!r   poolasyncpg.Poolc                     dS )z-Set the database pool for metrics collection.Nr$   rO   ra   s     r   set_db_poolz MetricsCollectorBase.set_db_pool   s    r   rE   NNr   r   Tra   rb   )r9   
__module____qualname____doc__r   strintrP   floatboolrZ   r   r`   re   r$   r   r   rD   rD      s>       ,,
 !!%	" 	"	" 	" 		"
 d
	" $J	" 	" 	" ^	"" " "" " 	"
 " " " " " " "0 "# " "RZ[]_b[bRc " " " ^"     r   rD   c                       e Zd ZdZe	 	 	 ddededededz  dedz  f
d	            Z	 	 	 ddedededededede	fdZ
edededeg ef         fd            ZdS )NoOpMetricsCollectorzJNo-op metrics collector that does nothing. Used when metrics are disabled.rE   NrF   rG   rH   rI   rJ   c              #      K   dV  dS )zNo-op context manager.Nr$   rN   s         r   rP   z%NoOpMetricsCollector.record_operation   s       	r   r   Tr?   rQ   rR   rS   rT   rU   rV   c                     dS )zNo-op LLM call recording.Nr$   rY   s           r   rZ   z$NoOpMetricsCollector.record_llm_call   s	     	r   r[   r\   r]   c              #      K   dV  dS )zNo-op HTTP request recording.Nr$   r_   s       r   r`   z(NoOpMetricsCollector.record_http_request   s       	r   rf   rg   )r9   ri   rj   rk   r   rl   rm   rP   rn   ro   rZ   r   r`   r$   r   r   rq   rq      s*       TT
 !!%	 		 	 		
 d
	 $J	 	 	 ^	"    	
        #  RZ[]_b[bRc    ^  r   rq   c                       e Zd ZdZd Ze	 	 	 ddededededz  d	edz  f
d
            Z	 	 	 ddededede	dedede
fdZedededeg ef         fd            Zd Zd dZd ZdS )!MetricsCollectorzv
    Collector for Hindsight API metrics.

    Provides methods to record latency and token usage for operations.
    c                    t                      | _        ddlm}  |            j        | _        | j                            ddd          | _        | j                            ddd	          | _	        | j                            d
dd          | _
        | j                            ddd          | _        | j                            ddd          | _        | j                            ddd          | _        | j                            ddd          | _        | j                            ddd          | _        | j                            ddd          | _        |                                  d | _        d S )N   )
get_configr+   z+Duration of Hindsight operations in secondss)namedescriptionunitzhindsight.operation.totalz#Total number of operations executed
operationsr/   z$Duration of LLM API calls in secondszhindsight.llm.tokens.inputz$Number of input tokens for LLM callstokenszhindsight.llm.tokens.outputz&Number of output tokens from LLM callszhindsight.llm.calls.totalzTotal number of LLM API callscallsr0   z$Duration of HTTP requests in secondszhindsight.http.requests.totalzTotal number of HTTP requestsrequestsz#hindsight.http.requests.in_progressz#Number of HTTP requests in progress)r8   meterconfigry   metrics_include_bank_id_include_bank_idcreate_histogramoperation_durationcreate_counteroperation_totalllm_durationllm_tokens_inputllm_tokens_outputllm_calls_totalhttp_request_durationhttp_requests_totalcreate_up_down_counterhttp_requests_in_progress_setup_process_metrics_db_pool)rO   ry   s     r   __init__zMetricsCollector.__init__   s   [[
&&&&&& *
 D #'*"="=/=jqt #> #
 #

  $z88,:_fr  9  
  
 !J77)7]dg 8 
 

 !%
 9 9-;ahp !: !
 !
 "&!:!:.<dks "; "
 "

  $z88,:Y`g  9  
  

 &*Z%@%@*8^eh &A &
 &
" $(:#<#<0>]dn $= $
 $
  *.)J)J6= *K *
 *
& 	##%%% 04r   rE   NrF   rG   rH   rI   rJ   c              #     K   t          j                     }||t                      d}| j        r||d<   |r||d<   |rt          |          |d<   d}	 dV  n# t          $ r d} w xY w	 t          j                     |z
  }	t          |                                          |d<   | j                            |	|           | j        	                    d	|           dS # t          j                     |z
  }	t          |                                          |d<   | j                            |	|           | j        	                    d	|           w xY w)
aL  
        Context manager to record operation duration and status.

        Usage:
            with metrics.record_operation("recall", bank_id="user123", source="api", budget="mid", max_tokens=4096):
                # ... perform operation
                pass

        Args:
            operation: Operation name (retain, recall, reflect, consolidation)
            bank_id: Memory bank ID
            source: Source of the operation (api, reflect, internal)
            budget: Optional budget level (low, mid, high)
            max_tokens: Optional max tokens for the operation
        )rF   rH   tenantrG   rI   rJ   TNFrV   rx   )
timer   r   rl   	Exceptionlowerr   recordr   add)
rO   rF   rG   rH   rI   rJ   
start_time
attributesrV   rS   s
             r   rP   z!MetricsCollector.record_operation7  s     0 Y[[
"!mm
 


   	,$+Jy! 	*#)Jx  	7'*:J|$	4EEEE 	 	 	G	 
 y{{Z/H$'LL$6$6$8$8Jy! #**8Z@@@  $$Q
33333 y{{Z/H$'LL$6$6$8$8Jy! #**8Z@@@  $$Q
3333s   A C A%%C A2Er   Tr?   rQ   rR   rS   rT   rU   rV   c                    |||t          |                                          t                      d}| j                            ||           | j                            d|           |dk    r0i |dt          |          i}	| j                            ||	           |dk    r2i |dt          |          i}
| j	                            ||
           dS dS )rX   )r?   rQ   rR   rV   r   rx   r   token_bucketN)
rl   r   r   r   r   r   r   r%   r   r   )rO   r?   rQ   rR   rS   rT   rU   rV   base_attributesinput_attributesoutput_attributess              r   rZ   z MetricsCollector.record_llm_calll  s   0 !7||))++!mm
 
 	  ?;;; 	  O444 ! !  0 > >    !%%l4DEEE1!!! 0 ? ?! ! "&&}6GHHHHH r   r[   r\   r]   c              #     K   t          j                     }||d}| j                            d|           	 dV  t          j                     |z
  } |            }|dz   d}t                      }	i |t	          |          ||	d}
| j                            ||
           | j                            d|
           | j                            d|           dS # t          j                     |z
  } |            }|dz   d}t                      }	i |t	          |          ||	d}
| j                            ||
           | j                            d|
           | j                            d|           w xY w)a  
        Context manager to record HTTP request metrics.

        Usage:
            status_code = [200]  # Use list for mutability
            with metrics.record_http_request("GET", "/api/banks", lambda: status_code[0]):
                # ... handle request
                status_code[0] = response.status_code

        Args:
            method: HTTP method (GET, POST, etc.)
            endpoint: Request endpoint path
            status_code_getter: Callable that returns the status code after request completes
        )r[   r\   rx   Nr#   xx)status_codestatus_classr   )r   r   r   r   rl   r   r   r   )rO   r[   r\   r]   r   r   rS   r   r   r   r   s              r   r`   z$MetricsCollector.record_http_request  s       Y[[
%+BB 	&**1o>>>	DEEEy{{Z/H,,..K)S0444L !]]F!";// , 	  J &--h
CCC$((J777 *..r?CCCCC' y{{Z/H,,..K)S0444L !]]F!";// , 	  J &--h
CCC$((J777 *..r?CCCCs   C B E;c                 (   t           dS d }d }d }d }| j                            d|gdd	           | j                            d
|gdd	           | j                            d|gdd	           | j                            d|gdd	           dS )z-Set up observable gauges for process metrics.Nc              3      K   	 t                               t           j                  }t          j        |j        ddi          V  t          j        |j        ddi          V  dS # t          $ r Y dS w xY w)zGet process CPU times.typeusersystemN)_resource_mod	getrusageRUSAGE_SELFr   Observationru_utimeru_stimer   )_optionsrusages     r   get_cpu_timesz>MetricsCollector._setup_process_metrics.<locals>.get_cpu_times  s      &001JKK)&/FF;KLLLLL)&/FH;MNNNNNNN   s   A A& &
A43A4c              3      K   	 t                               t           j                  }|j        }t	          j                    j        dk    r|dz  }t          j        |ddi          V  dS # t          $ r Y dS w xY w)z"Get process memory usage in bytes.Linuxi   r   rss_maxN)
r   r   r   	ru_maxrssosunamesysnamer   r   r   )r   r   max_rsss      r   get_memory_usagezAMetricsCollector._setup_process_metrics.<locals>.get_memory_usage  s      &001JKK *8::%00tOG)'FI3FGGGGGGG   s   A%A+ +
A98A9c              3   ^  K   	 t           j                            d          r9t          t          j        d                    }t          j        |          V  dS t                              t          j	                  \  }}t          j        |ddi          V  dS # t          $ r Y dS w xY w)z$Get number of open file descriptors.z/proc/self/fdlimitsoftN)r   pathexistslenlistdirr   r   r   	getrlimitRLIMIT_NOFILEr   )r   countr   hards       r   get_open_file_descriptorszJMetricsCollector._setup_process_metrics.<locals>.get_open_file_descriptors  s      
7>>/22 G
? ; ;<<E!-e4444444 "/!8!89T!U!UJD$!-dWf4EFFFFFFF   s   AB A B 
B,+B,c              3   |   K   	 t          j        t          j                              V  dS # t          $ r Y dS w xY w)zGet number of active threads.N)r   r   	threadingactive_countr   )r   s    r   get_thread_countzAMetricsCollector._setup_process_metrics.<locals>.get_thread_count  sS      ))*@*B*BCCCCCCC   s   '- 
;;zhindsight.process.cpu.secondszProcess CPU time in secondsrz   r{   	callbacksr|   r}   zhindsight.process.memory.byteszProcess memory usage in bytesByzhindsight.process.open_fdszNumber of open file descriptorsz{fds}zhindsight.process.threadszNumber of active threadsz	{threads})r   r   create_observable_gauge)rO   r   r   r   r   s        r   r   z'MetricsCollector._setup_process_metrics  s    F	 	 	
	 
	 
		 	 		 	 	 	
**0$o5	 	+ 	
 	
 	
 	
**1'(7	 	+ 	
 	
 	
 	
**-019	 	+ 	
 	
 	
 	
**,'(2	 	+ 	
 	
 	
 	
 	
r   ra   rb   c                 <    || _         |                                  dS )z
        Set the database pool for metrics collection.

        Args:
            pool: asyncpg connection pool instance
        N)r   _setup_db_pool_metricsrd   s     r   re   zMetricsCollector.set_db_pool  s#     ##%%%%%r   c                 (     fd} fd} fd} fd} j                             d|gdd            j                             d	|gd
d            j                             d|gdd            j                             d|gdd           dS )z3Set up observable gauges for database pool metrics.c              3      K   j         A	 t          j        j                                                   V  dS # t          $ r Y dS w xY wdS )zGet current pool size.N)r   r   r   get_sizer   r   rO   s    r   get_pool_sizez>MetricsCollector._setup_db_pool_metrics.<locals>.get_pool_size'  si      }(!-dm.D.D.F.FGGGGGGG    DD )(   -; 
A	A	c              3      K   j         A	 t          j        j                                                   V  dS # t          $ r Y dS w xY wdS )z'Get number of free connections in pool.N)r   r   r   get_idle_sizer   r   s    r   get_pool_free_sizezCMetricsCollector._setup_db_pool_metrics.<locals>.get_pool_free_size/  si      }(!-dm.I.I.K.KLLLLLLL    DD )(r   c              3      K   j         A	 t          j        j                                                   V  dS # t          $ r Y dS w xY wdS )zGet pool minimum size.N)r   r   r   get_min_sizer   r   s    r   get_pool_min_sizezBMetricsCollector._setup_db_pool_metrics.<locals>.get_pool_min_size7  i      }(!-dm.H.H.J.JKKKKKKK    DD )(r   c              3      K   j         A	 t          j        j                                                   V  dS # t          $ r Y dS w xY wdS )zGet pool maximum size.N)r   r   r   get_max_sizer   r   s    r   get_pool_max_sizezBMetricsCollector._setup_db_pool_metrics.<locals>.get_pool_max_size?  r   r   zhindsight.db.pool.sizez)Current number of connections in the poolz{connections}r   zhindsight.db.pool.idlez&Number of idle connections in the poolzhindsight.db.pool.minzMinimum pool sizezhindsight.db.pool.maxzMaximum pool sizeN)r   r   )rO   r   r   r   r   s   `    r   r   z'MetricsCollector._setup_db_pool_metrics$  s0   	 	 	 	 		 	 	 	 		 	 	 	 		 	 	 	 	 	
**)$oC 	 	+ 	
 	
 	
 	
**))*@ 	 	+ 	
 	
 	
 	
**(()+ 	 	+ 	
 	
 	
 	
**(()+ 	 	+ 	
 	
 	
 	
 	
r   rf   rg   rh   )r9   ri   rj   rk   r   r   rl   rm   rP   rn   ro   rZ   r   r`   r   re   r   r$   r   r   rv   rv      s        84 84 84t 
 !!%24 2424 24 	24
 d
24 $J24 24 24 ^24t 2I 2I2I 2I 	2I
 2I 2I 2I 2I 2I 2I 2Ih +D# +D +DRZ[]_b[bRc +D +D +D ^+DZJ
 J
 J
X& & & &>
 >
 >
 >
 >
r   rv   _metrics_collectorc                      t           S )zs
    Get the global metrics collector instance.

    Returns a no-op collector if metrics are not initialized.
    )r   r$   r   r   get_metrics_collectorr   i  s
     r   c                  ,    t                      at          S )zh
    Create and set the global metrics collector.

    Should be called after initialize_metrics().
    )rv   r   r$   r   r   create_metrics_collectorr   r  s     *++r   )r&   r'   )/rk   	importlibloggingr   util	find_specimport_moduler   r   r   
contextlibr   typingr   r   opentelemetryr   !opentelemetry.exporter.prometheusr   opentelemetry.sdk.metricsr	   opentelemetry.sdk.metrics.viewr
   r   opentelemetry.sdk.resourcesr   asyncpgrl   r   r4   r5   r6   rm   r%   	getLoggerr9   loggerr:   r@   r8   rD   rq   rv   r   __annotations__r   r   r$   r   r   <module>r      s          				7@~7O7OPZ7[7[e'	'
333ae      % % % % % % * * * * * * * * ! ! ! ! ! ! D D D D D D 3 3 3 3 3 3 S S S S S S S S 0 0 0 0 0 0 NNN S         h  [  ^ "# "# " " " "J 
	8	$	$ 
9 9S 9S 9 9 9 9x  . . . . . . . .b    /   Dl
 l
 l
 l
 l
+ l
 l
 l
` ,@+?+A+A ( A A A3    "2      r   