diff --git a/package-lock.json b/package-lock.json index 073ab00c..aeb6e23b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@myrotvorets/facex": "^2.6.1", "@myrotvorets/oav-installer": "^4.1.1", "@myrotvorets/opentelemetry-configurator": "^7.0.0", - "@myrotvorets/otel-utils": "^0.0.9", + "@myrotvorets/otel-utils": "^0.0.10", "@opentelemetry/api": "^1.6.0", "@opentelemetry/core": "^1.17.1", "@opentelemetry/semantic-conventions": "^1.17.1", @@ -498,9 +498,9 @@ } }, "node_modules/@myrotvorets/otel-utils": { - "version": "0.0.9", - "resolved": "https://npm.pkg.github.com/download/@myrotvorets/otel-utils/0.0.9/94321f8648f8f993697b59611d003836400ffb98", - "integrity": "sha512-ZZeBWAGs3ioZUpQYhtXrVi6Iwov0fNwK96kKcZRHOjjVkg6RGzIP8qHvpfw2+0eJTBtUFpQo3KjFCb0xydSAtg==", + "version": "0.0.10", + "resolved": "https://npm.pkg.github.com/download/@myrotvorets/otel-utils/0.0.10/c4a88d023db5334adc92439a113bbe0b96e484b2", + "integrity": "sha512-1s5cpfplVjKUUrJxm30+ZC5E5mH291APDtatt+KRd6Mje3yzsEQ49GWGDppg6ObjZK5/DtfBFlNF8vjGMIwwxA==", "license": "MIT", "dependencies": { "@myrotvorets/create-server": "^2.2.0" diff --git a/package.json b/package.json index a885eb56..53e68811 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@myrotvorets/facex": "^2.6.1", "@myrotvorets/oav-installer": "^4.1.1", "@myrotvorets/opentelemetry-configurator": "^7.0.0", - "@myrotvorets/otel-utils": "^0.0.9", + "@myrotvorets/otel-utils": "^0.0.10", "@opentelemetry/api": "^1.6.0", "@opentelemetry/core": "^1.17.1", "@opentelemetry/semantic-conventions": "^1.17.1", diff --git a/src/controllers/compare.mts b/src/controllers/compare.mts index 979b3aa0..db15595b 100644 --- a/src/controllers/compare.mts +++ b/src/controllers/compare.mts @@ -57,13 +57,10 @@ async function statusHandler( } export function compareController(): Router { - const router = Router(); + const router = Router({ strict: true, caseSensitive: true }); router.post('/compare', asyncWrapperMiddleware(startCompareHandler)); - router.get( - '/compare/:guid([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})', - asyncWrapperMiddleware(statusHandler), - ); + router.get('/compare/:guid', asyncWrapperMiddleware(statusHandler)); router.use(uploadErrorHandlerMiddleware); router.use(faceXErrorHandlerMiddleware); diff --git a/src/controllers/count.mts b/src/controllers/count.mts index 711edcdd..7ce29b84 100644 --- a/src/controllers/count.mts +++ b/src/controllers/count.mts @@ -21,7 +21,7 @@ async function countHandler( } export function countController(): Router { - const router = Router(); + const router = Router({ strict: true, caseSensitive: true }); router.get('/count', asyncWrapperMiddleware(countHandler)); router.use(faceXErrorHandlerMiddleware); return router; diff --git a/src/controllers/search.mts b/src/controllers/search.mts index 145fda39..6af1179d 100644 --- a/src/controllers/search.mts +++ b/src/controllers/search.mts @@ -1,4 +1,4 @@ -import { type Request, type Response, Router } from 'express'; +import { type NextFunction, type Request, type Response, Router } from 'express'; import type { SearchStats } from '@myrotvorets/facex'; import { asyncWrapperMiddleware } from '@myrotvorets/express-async-middleware-wrapper'; import type { MatchedFace, RecoginizedFace } from '../services/search.mjs'; @@ -73,9 +73,9 @@ async function capturedFacesHandler( interface MatchedFacesParams { guid: string; - faceid: string; - offset: string; - count: string; + faceid: number; + offset: number; + count: number; } interface MatchedFacesResponse { @@ -87,23 +87,34 @@ async function matchedFacesHandler( req: Request, res: Response, ): Promise { - const { guid } = req.params; + const { guid, faceid, offset, count } = req.params; const service = res.locals.container.resolve('searchService'); - const faceid = parseInt(req.params.faceid, 10); - const offset = parseInt(req.params.offset, 10); - const count = parseInt(req.params.count, 10); const result = await service.matchedFaces(guid, faceid, offset, count); res.json({ success: true, matches: result }); } +function intParamHandler( + req: Request>, + _res: Response, + next: NextFunction, + value: string, + name: string, +): void { + req.params[name] = +value; + next(); +} + export function searchController(): Router { - const router = Router(); - const guid = '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}'; + const router = Router({ strict: true, caseSensitive: true }); + + router.param('offset', intParamHandler); + router.param('count', intParamHandler); + router.param('faceid', intParamHandler); router.post('/search', asyncWrapperMiddleware(startSearchHandler)); - router.get(`/search/:guid(${guid})`, asyncWrapperMiddleware(statusHandler)); - router.get(`/search/:guid(${guid})/captured`, asyncWrapperMiddleware(capturedFacesHandler)); - router.get(`/search/:guid(${guid})/matches/:faceid/:offset/:count`, asyncWrapperMiddleware(matchedFacesHandler)); + router.get(`/search/:guid`, asyncWrapperMiddleware(statusHandler)); + router.get(`/search/:guid/captured`, asyncWrapperMiddleware(capturedFacesHandler)); + router.get(`/search/:guid/matches/:faceid/:offset/:count`, asyncWrapperMiddleware(matchedFacesHandler)); router.use(uploadErrorHandlerMiddleware); router.use(faceXErrorHandlerMiddleware); diff --git a/src/lib/metrics.mts b/src/lib/metrics.mts new file mode 100644 index 00000000..019abda9 --- /dev/null +++ b/src/lib/metrics.mts @@ -0,0 +1,31 @@ +/* c8 ignore start */ +import { ValueType } from '@opentelemetry/api'; +import { getMeter } from '@myrotvorets/otel-utils'; +import type { Container } from './container.mjs'; + +type AsyncMetricOptions = Pick; + +export function initAsyncMetrics({ meter, faceXClient }: AsyncMetricOptions): void { + meter + .createObservableUpDownCounter('facex.faces.count', { + description: 'Number of stored faces', + unit: '{count}', + valueType: ValueType.INT, + }) + .addCallback(async (result) => { + try { + const r = await faceXClient.baseStatus(); + result.observe(r.numberOfRecords); + } catch { + result.observe(NaN); + } + }); +} + +export const requestDurationHistogram = getMeter().createHistogram('psbapi.request.duration', { + description: 'Measures the duration of requests.', + unit: 'ms', + valueType: ValueType.DOUBLE, +}); + +/* c8 ignore stop */ diff --git a/src/lib/otel.mts b/src/lib/otel.mts deleted file mode 100644 index 60f9ad8d..00000000 --- a/src/lib/otel.mts +++ /dev/null @@ -1,11 +0,0 @@ -/* c8 ignore start */ -import { getMeter } from '@myrotvorets/otel-utils'; -import { ValueType } from '@opentelemetry/api'; - -export const requestDurationHistogram = getMeter().createHistogram('psbapi.request.duration', { - description: 'Measures the duration of requests.', - unit: 'ms', - valueType: ValueType.DOUBLE, -}); - -/* c8 ignore stop */ diff --git a/src/middleware/duration.mts b/src/middleware/duration.mts index 7524dc31..6f043ad6 100644 --- a/src/middleware/duration.mts +++ b/src/middleware/duration.mts @@ -1,7 +1,7 @@ import type { RequestHandler } from 'express'; import { hrTime, hrTimeDuration, hrTimeToMilliseconds } from '@opentelemetry/core'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { requestDurationHistogram } from '../lib/otel.mjs'; +import { requestDurationHistogram } from '../lib/metrics.mjs'; export const requestDurationMiddleware: RequestHandler = (req, res, next): void => { const start = hrTime(); diff --git a/src/server.mts b/src/server.mts index 632afa03..07d999fe 100644 --- a/src/server.mts +++ b/src/server.mts @@ -7,9 +7,10 @@ import express, { type Express } from 'express'; import { cleanUploadedFilesMiddleware } from '@myrotvorets/clean-up-after-multer'; import { errorMiddleware, notFoundMiddleware } from '@myrotvorets/express-microservice-middlewares'; import { installOpenApiValidator } from '@myrotvorets/oav-installer'; - import { createServer, getTracer, recordErrorToSpan } from '@myrotvorets/otel-utils'; + import { initializeContainer, scopedContainerMiddleware } from './lib/container.mjs'; +import { initAsyncMetrics } from './lib/metrics.mjs'; import { requestDurationMiddleware } from './middleware/duration.mjs'; import { loggerMiddleware } from './middleware/logger.mjs'; @@ -58,6 +59,7 @@ export function configureApp(app: Express): Promise