mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: bulk impact metrics (#10251)
This commit is contained in:
		
							parent
							
								
									0a42d22c52
								
							
						
					
					
						commit
						661fd6febf
					
				| @ -196,8 +196,14 @@ export default class ClientMetricsServiceV2 { | ||||
|     } | ||||
| 
 | ||||
|     async registerImpactMetrics(impactMetrics: Metric[]) { | ||||
|         const value = await impactMetricsSchema.validateAsync(impactMetrics); | ||||
|         this.impactMetricsTranslator.translateMetrics(value); | ||||
|         try { | ||||
|             const value = | ||||
|                 await impactMetricsSchema.validateAsync(impactMetrics); | ||||
|             this.impactMetricsTranslator.translateMetrics(value); | ||||
|         } catch (e) { | ||||
|             // impact metrics should not affect other metrics on failure
 | ||||
|             this.logger.warn(e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async registerClientMetrics( | ||||
|  | ||||
| @ -26,6 +26,20 @@ const sendImpactMetrics = async (impactMetrics: Metric[], status = 202) => | ||||
|         }) | ||||
|         .expect(status); | ||||
| 
 | ||||
| const sendBulkMetricsWithImpact = async ( | ||||
|     impactMetrics: Metric[], | ||||
|     status = 202, | ||||
| ) => { | ||||
|     return app.request | ||||
|         .post('/api/client/metrics/bulk') | ||||
|         .send({ | ||||
|             applications: [], | ||||
|             metrics: [], | ||||
|             impactMetrics, | ||||
|         }) | ||||
|         .expect(status); | ||||
| }; | ||||
| 
 | ||||
| beforeAll(async () => { | ||||
|     db = await dbInit('impact_metrics', getLogger); | ||||
|     app = await setupAppWithCustomConfig(db.stores, { | ||||
| @ -72,7 +86,7 @@ test('should store impact metrics in memory and be able to retrieve them', async | ||||
|     ]); | ||||
| 
 | ||||
|     await sendImpactMetrics([]); | ||||
|     // missing help
 | ||||
|     // missing help = no error but value ignored
 | ||||
|     await sendImpactMetrics( | ||||
|         [ | ||||
|             // @ts-expect-error
 | ||||
| @ -87,7 +101,7 @@ test('should store impact metrics in memory and be able to retrieve them', async | ||||
|                 ], | ||||
|             }, | ||||
|         ], | ||||
|         400, | ||||
|         202, | ||||
|     ); | ||||
| 
 | ||||
|     const response = await app.request | ||||
| @ -101,3 +115,48 @@ test('should store impact metrics in memory and be able to retrieve them', async | ||||
|     expect(metricsText).toContain('# TYPE labeled_counter counter'); | ||||
|     expect(metricsText).toMatch(/labeled_counter{foo="bar"} 15/); | ||||
| }); | ||||
| 
 | ||||
| test('should store impact metrics sent via bulk metrics endpoint', async () => { | ||||
|     await sendBulkMetricsWithImpact([ | ||||
|         { | ||||
|             name: 'bulk_counter', | ||||
|             help: 'bulk counter with labels', | ||||
|             type: 'counter', | ||||
|             samples: [ | ||||
|                 { | ||||
|                     labels: { source: 'bulk' }, | ||||
|                     value: 7, | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|     ]); | ||||
| 
 | ||||
|     await sendBulkMetricsWithImpact([ | ||||
|         { | ||||
|             name: 'bulk_counter', | ||||
|             help: 'bulk counter with labels', | ||||
|             type: 'counter', | ||||
|             samples: [ | ||||
|                 { | ||||
|                     labels: { source: 'bulk' }, | ||||
|                     value: 8, | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|     ]); | ||||
| 
 | ||||
|     await sendBulkMetricsWithImpact([]); | ||||
| 
 | ||||
|     const response = await app.request | ||||
|         .get('/internal-backstage/impact/metrics') | ||||
|         .expect('Content-Type', /text/) | ||||
|         .expect(200); | ||||
| 
 | ||||
|     const metricsText = response.text; | ||||
| 
 | ||||
|     expect(metricsText).toContain( | ||||
|         '# HELP bulk_counter bulk counter with labels', | ||||
|     ); | ||||
|     expect(metricsText).toContain('# TYPE bulk_counter counter'); | ||||
|     expect(metricsText).toMatch(/bulk_counter{source="bulk"} 15/); | ||||
| }); | ||||
|  | ||||
| @ -230,7 +230,7 @@ export default class ClientMetricsController extends Controller { | ||||
|             res.status(204).end(); | ||||
|         } else { | ||||
|             const { body, ip: clientIp } = req; | ||||
|             const { metrics, applications } = body; | ||||
|             const { metrics, applications, impactMetrics } = body; | ||||
|             try { | ||||
|                 const promises: Promise<void>[] = []; | ||||
|                 for (const app of applications) { | ||||
| @ -275,6 +275,17 @@ export default class ClientMetricsController extends Controller { | ||||
|                     ); | ||||
|                     this.config.eventBus.emit(CLIENT_METRICS, data); | ||||
|                 } | ||||
| 
 | ||||
|                 if ( | ||||
|                     this.flagResolver.isEnabled('impactMetrics') && | ||||
|                     impactMetrics && | ||||
|                     impactMetrics.length > 0 | ||||
|                 ) { | ||||
|                     promises.push( | ||||
|                         this.metricsV2.registerImpactMetrics(impactMetrics), | ||||
|                     ); | ||||
|                 } | ||||
| 
 | ||||
|                 await Promise.all(promises); | ||||
| 
 | ||||
|                 res.status(202).end(); | ||||
|  | ||||
| @ -2,6 +2,7 @@ import type { FromSchema } from 'json-schema-to-ts'; | ||||
| import { bulkRegistrationSchema } from './bulk-registration-schema.js'; | ||||
| import { dateSchema } from './date-schema.js'; | ||||
| import { clientMetricsEnvSchema } from './client-metrics-env-schema.js'; | ||||
| import { impactMetricsSchema } from './impact-metrics-schema.js'; | ||||
| 
 | ||||
| export const bulkMetricsSchema = { | ||||
|     $id: '#/components/schemas/bulkMetricsSchema', | ||||
| @ -25,12 +26,21 @@ export const bulkMetricsSchema = { | ||||
|                 $ref: '#/components/schemas/clientMetricsEnvSchema', | ||||
|             }, | ||||
|         }, | ||||
|         impactMetrics: { | ||||
|             description: | ||||
|                 'a list of custom impact metrics registered by downstream providers. (Typically Unleash Edge)', | ||||
|             type: 'array', | ||||
|             items: { | ||||
|                 $ref: '#/components/schemas/impactMetricsSchema', | ||||
|             }, | ||||
|         }, | ||||
|     }, | ||||
|     components: { | ||||
|         schemas: { | ||||
|             bulkRegistrationSchema, | ||||
|             dateSchema, | ||||
|             clientMetricsEnvSchema, | ||||
|             impactMetricsSchema, | ||||
|         }, | ||||
|     }, | ||||
| } as const; | ||||
|  | ||||
							
								
								
									
										60
									
								
								src/lib/openapi/spec/impact-metrics-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/lib/openapi/spec/impact-metrics-schema.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | ||||
| import type { FromSchema } from 'json-schema-to-ts'; | ||||
| 
 | ||||
| export const impactMetricsSchema = { | ||||
|     $id: '#/components/schemas/impactMetricsSchema', | ||||
|     type: 'object', | ||||
|     required: ['name', 'help', 'type', 'samples'], | ||||
|     description: 'Used for reporting impact metrics from SDKs', | ||||
|     properties: { | ||||
|         name: { | ||||
|             type: 'string', | ||||
|             description: 'Name of the impact metric', | ||||
|             example: 'my-counter', | ||||
|         }, | ||||
|         help: { | ||||
|             description: | ||||
|                 'Human-readable description of what the metric measures', | ||||
|             type: 'string', | ||||
|             example: 'Counts the number of operations', | ||||
|         }, | ||||
|         type: { | ||||
|             description: 'Type of the metric', | ||||
|             type: 'string', | ||||
|             enum: ['counter', 'gauge'], | ||||
|             example: 'counter', | ||||
|         }, | ||||
|         samples: { | ||||
|             description: 'Samples of the metric', | ||||
|             type: 'array', | ||||
|             items: { | ||||
|                 type: 'object', | ||||
|                 required: ['value'], | ||||
|                 description: | ||||
|                     'A sample of a metric with a value and optional labels', | ||||
|                 properties: { | ||||
|                     value: { | ||||
|                         type: 'number', | ||||
|                         description: 'The value of the metric sample', | ||||
|                         example: 10, | ||||
|                     }, | ||||
|                     labels: { | ||||
|                         description: 'Optional labels for the metric sample', | ||||
|                         type: 'object', | ||||
|                         additionalProperties: { | ||||
|                             type: 'string', | ||||
|                         }, | ||||
|                         example: { | ||||
|                             application: 'my-app', | ||||
|                             environment: 'production', | ||||
|                         }, | ||||
|                     }, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|     }, | ||||
|     components: { | ||||
|         schemas: {}, | ||||
|     }, | ||||
| } as const; | ||||
| 
 | ||||
| export type ImpactMetricsSchema = FromSchema<typeof impactMetricsSchema>; | ||||
| @ -113,6 +113,7 @@ export * from './health-overview-schema.js'; | ||||
| export * from './health-report-schema.js'; | ||||
| export * from './id-schema.js'; | ||||
| export * from './ids-schema.js'; | ||||
| export * from './impact-metrics-schema.js'; | ||||
| export * from './import-toggles-schema.js'; | ||||
| export * from './import-toggles-validate-item-schema.js'; | ||||
| export * from './import-toggles-validate-schema.js'; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user