@ -57,7 +57,7 @@ export class TelemetryManager {
this . createGauge ( 'starred_projects_api_throughput_rps' , 'API throughput in requests per second' ) ) ;
}
private createCounter ( name : string , help : string ) : Record < string , unknown > {
private createCounter ( name : string , help : string ) : { name : string ; help : string ; type : string ; value : number ; inc : ( ) = > void } {
// Mock counter implementation
return {
name ,
@ -66,12 +66,14 @@ export class TelemetryManager {
value : 0 ,
inc : ( ) : void = > {
const metric = this . metrics . get ( name ) ;
if ( metric ) metric . value ++ ;
if ( metric && typeof metric === 'object' && 'value' in metric ) {
( metric as { value : number } ) . value ++ ;
}
}
} ;
}
private createHistogram ( name : string , help : string , buckets : number [ ] ) : Record < string , unknown > {
private createHistogram ( name : string , help : string , buckets : number [ ] ) : { name : string ; help : string ; type : string ; buckets : number [ ] ; values : number [ ] ; observe : ( value : number ) = > void } {
// Mock histogram implementation
return {
name ,
@ -81,20 +83,21 @@ export class TelemetryManager {
values : new Array ( buckets . length + 1 ) . fill ( 0 ) ,
observe : ( value : number ) : void = > {
const metric = this . metrics . get ( name ) ;
if ( ! metric ) return ;
if ( ! metric || typeof metric !== 'object' || ! ( 'values' in metric ) || ! ( 'buckets' in metric ) ) return ;
const metricObj = metric as { values : number [ ] ; buckets : number [ ] } ;
// Find bucket and increment
for ( let i = 0 ; i < buckets . length ; i ++ ) {
if ( value <= buckets [ i ] ) {
metric . values [ i ] ++ ;
for ( let i = 0 ; i < metricObj . buckets . length ; i ++ ) {
if ( value <= metricObj . buckets [ i ] ) {
metricObj . values [ i ] ++ ;
return ;
}
}
metric . values [ buckets . length ] ++ ; // +Inf bucket
metricObj . values [ metricObj . buckets . length ] ++ ; // +Inf bucket
}
} ;
}
private createGauge ( name : string , help : string ) : Record < string , unknown > {
private createGauge ( name : string , help : string ) : { name : string ; help : string ; type : string ; value : number ; set : ( value : number ) = > void } {
// Mock gauge implementation
return {
name ,
@ -103,59 +106,97 @@ export class TelemetryManager {
value : 0 ,
set : ( value : number ) : void = > {
const metric = this . metrics . get ( name ) ;
if ( metric ) metric . value = value ;
if ( metric && typeof metric === 'object' && 'value' in metric ) {
( metric as { value : number } ) . value = value ;
}
}
} ;
}
// Low-cardinality metric recording
recordPollAttempt ( ) : void {
this . metrics . get ( 'starred_projects_poll_attempts_total' ) ? . inc ( ) ;
const metric = this . metrics . get ( 'starred_projects_poll_attempts_total' ) ;
if ( metric && typeof metric === 'object' && 'inc' in metric ) {
( metric as { inc : ( ) = > void } ) . inc ( ) ;
}
}
recordPollSuccess ( durationSeconds : number ) : void {
this . metrics . get ( 'starred_projects_poll_success_total' ) ? . inc ( ) ;
this . metrics . get ( 'starred_projects_poll_duration_seconds' ) ? . observe ( durationSeconds ) ;
const successMetric = this . metrics . get ( 'starred_projects_poll_success_total' ) ;
if ( successMetric && typeof successMetric === 'object' && 'inc' in successMetric ) {
( successMetric as { inc : ( ) = > void } ) . inc ( ) ;
}
const durationMetric = this . metrics . get ( 'starred_projects_poll_duration_seconds' ) ;
if ( durationMetric && typeof durationMetric === 'object' && 'observe' in durationMetric ) {
( durationMetric as { observe : ( value : number ) = > void } ) . observe ( durationSeconds ) ;
}
}
recordPollFailure ( ) : void {
this . metrics . get ( 'starred_projects_poll_failure_total' ) ? . inc ( ) ;
const metric = this . metrics . get ( 'starred_projects_poll_failure_total' ) ;
if ( metric && typeof metric === 'object' && 'inc' in metric ) {
( metric as { inc : ( ) = > void } ) . inc ( ) ;
}
}
recordChangesFound ( count : number ) : void {
for ( let i = 0 ; i < count ; i ++ ) {
this . metrics . get ( 'starred_projects_changes_found_total' ) ? . inc ( ) ;
const metric = this . metrics . get ( 'starred_projects_changes_found_total' ) ;
if ( metric && typeof metric === 'object' && 'inc' in metric ) {
for ( let i = 0 ; i < count ; i ++ ) {
( metric as { inc : ( ) = > void } ) . inc ( ) ;
}
}
}
recordNotificationsGenerated ( count : number ) : void {
for ( let i = 0 ; i < count ; i ++ ) {
this . metrics . get ( 'starred_projects_notifications_generated_total' ) ? . inc ( ) ;
const metric = this . metrics . get ( 'starred_projects_notifications_generated_total' ) ;
if ( metric && typeof metric === 'object' && 'inc' in metric ) {
for ( let i = 0 ; i < count ; i ++ ) {
( metric as { inc : ( ) = > void } ) . inc ( ) ;
}
}
}
recordError ( ) : void {
this . metrics . get ( 'starred_projects_error_total' ) ? . inc ( ) ;
const metric = this . metrics . get ( 'starred_projects_error_total' ) ;
if ( metric && typeof metric === 'object' && 'inc' in metric ) {
( metric as { inc : ( ) = > void } ) . inc ( ) ;
}
}
recordRateLimit ( ) : void {
this . metrics . get ( 'starred_projects_rate_limit_total' ) ? . inc ( ) ;
const metric = this . metrics . get ( 'starred_projects_rate_limit_total' ) ;
if ( metric && typeof metric === 'object' && 'inc' in metric ) {
( metric as { inc : ( ) = > void } ) . inc ( ) ;
}
}
recordApiLatency ( latencySeconds : number ) : void {
this . metrics . get ( 'starred_projects_api_latency_seconds' ) ? . observe ( latencySeconds ) ;
const metric = this . metrics . get ( 'starred_projects_api_latency_seconds' ) ;
if ( metric && typeof metric === 'object' && 'observe' in metric ) {
( metric as { observe : ( value : number ) = > void } ) . observe ( latencySeconds ) ;
}
}
recordOutboxSize ( size : number ) : void {
this . metrics . get ( 'starred_projects_outbox_size' ) ? . set ( size ) ;
const metric = this . metrics . get ( 'starred_projects_outbox_size' ) ;
if ( metric && typeof metric === 'object' && 'set' in metric ) {
( metric as { set : ( value : number ) = > void } ) . set ( size ) ;
}
}
recordBackpressureActive ( active : boolean ) : void {
this . metrics . get ( 'starred_projects_outbox_backpressure_active' ) ? . set ( active ? 1 : 0 ) ;
const metric = this . metrics . get ( 'starred_projects_outbox_backpressure_active' ) ;
if ( metric && typeof metric === 'object' && 'set' in metric ) {
( metric as { set : ( value : number ) = > void } ) . set ( active ? 1 : 0 ) ;
}
}
recordApiThroughput ( rps : number ) : void {
this . metrics . get ( 'starred_projects_api_throughput_rps' ) ? . set ( rps ) ;
const metric = this . metrics . get ( 'starred_projects_api_throughput_rps' ) ;
if ( metric && typeof metric === 'object' && 'set' in metric ) {
( metric as { set : ( value : number ) = > void } ) . set ( rps ) ;
}
}
// High-cardinality data (logs only, not metrics)
@ -207,27 +248,32 @@ export class TelemetryManager {
getMetrics ( ) : TelemetryMetrics {
const metrics : Record < string , unknown > = { } ;
for ( const [ name , metric ] of this . metrics ) {
metrics [ name ] = metric . value ;
if ( metric && typeof metric === 'object' && 'value' in metric ) {
metrics [ name ] = ( metric as { value : unknown } ) . value ;
}
}
return metrics as TelemetryMetrics ;
return metrics as unknown as TelemetryMetrics ;
}
// Get metrics in Prometheus format
getPrometheusMetrics ( ) : string {
let output = '' ;
for ( const [ name , metric ] of this . metrics ) {
output += ` # HELP ${ name } ${ metric . help } \ n ` ;
output += ` # TYPE ${ name } ${ metric . type } \ n ` ;
if ( ! metric || typeof metric !== 'object' ) continue ;
const metricObj = metric as { help : string ; type : string ; value? : number ; buckets? : number [ ] ; values? : number [ ] } ;
output += ` # HELP ${ name } ${ metricObj . help } \ n ` ;
output += ` # TYPE ${ name } ${ metricObj . type } \ n ` ;
if ( metric . type === 'histogram' ) {
if ( metricObj . type === 'histogram' && metricObj . buckets && metricObj . values ) {
// Export histogram buckets
for ( let i = 0 ; i < metric . buckets . length ; i ++ ) {
output += ` ${ name } _bucket{le=" ${ metric . buckets [ i ] } "} ${ metric . values [ i ] } \ n ` ;
for ( let i = 0 ; i < metricObj . buckets . length ; i ++ ) {
output += ` ${ name } _bucket{le=" ${ metricObj . buckets [ i ] } "} ${ metricObj . values [ i ] } \ n ` ;
}
output += ` ${ name } _bucket{le="+Inf"} ${ metric . values [ metric . buckets . length ] } \ n ` ;
output += ` ${ name } _count ${ metric . values . reduce ( ( a : number , b : number ) = > a + b , 0 ) } \ n ` ;
} else {
output += ` ${ name } ${ metric . value } \ n ` ;
output += ` ${ name } _bucket{le="+Inf"} ${ metricObj . values [ metricObj . buckets . length ] } \ n ` ;
output += ` ${ name } _count ${ metricObj . values . reduce ( ( a : number , b : number ) = > a + b , 0 ) } \ n ` ;
} else if ( metricObj . value !== undefined ) {
output += ` ${ name } ${ metricObj . value } \ n ` ;
}
}
return output ;