From c4ece458e499b4275aa5a82914a8d04ea4feaff4 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 26 Feb 2025 10:28:09 +0000 Subject: [PATCH 1/6] =?UTF-8?q?=D0=A5=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20api=20=D0=B2=20.env=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 ++ package.json | 4 +++- src/app.module.ts | 8 +++++++- src/main.ts | 1 + src/prometheus.service.ts | 38 ++++++++++++++++---------------------- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/Dockerfile b/Dockerfile index 82cfd43..b86a806 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,6 @@ RUN npm install COPY . . +ENV NODE_ENV=development + CMD ["npm", "run", "start:dev"] diff --git a/package.json b/package.json index 5aecf10..3ec4f33 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,11 @@ "@nestjs/axios": "^4.0.0", "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", + "@nestjs/config": "^4.0.0", "@nestjs/platform-express": "^11.0.1", "axios": "^1.7.9", "reflect-metadata": "^0.2.2", + "dotenv": "^16.3.1", "rxjs": "^7.8.1" }, "devDependencies": { @@ -72,4 +74,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} +} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 24e27bd..ee3d951 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,9 +2,15 @@ import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; import { PrometheusService } from './prometheus.service'; import { MetricsController } from './metrics.controller'; +import { ConfigModule } from '@nestjs/config'; @Module({ - imports: [HttpModule], // Используем новый HttpModule + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + envFilePath: '.env', + }), + HttpModule], controllers: [MetricsController], providers: [PrometheusService], }) diff --git a/src/main.ts b/src/main.ts index 98d5844..f8694c6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; + async function bootstrap() { const app = await NestFactory.create(AppModule); diff --git a/src/prometheus.service.ts b/src/prometheus.service.ts index a444ab3..b0398dd 100644 --- a/src/prometheus.service.ts +++ b/src/prometheus.service.ts @@ -1,15 +1,20 @@ import { Injectable } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; import { lastValueFrom } from 'rxjs'; import { PrometheusMetric } from './prometheus-metric.interface'; @Injectable() export class PrometheusService { - private readonly prometheusUrl = 'http://192.168.2.37:9090/api/v1'; + private readonly prometheusUrl: string; - constructor(private readonly httpService: HttpService) { } - - //Получаем тип метрики + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService + ) { + this.prometheusUrl = this.configService.get('PROMETHEUS_API', 'http://localhost:9090'); + console.log('Prometheus API URL:', this.prometheusUrl); + } async fetchMetricType(metric: string): Promise { try { @@ -20,20 +25,13 @@ export class PrometheusService { ); const metadata = response.data.data[metric]; - - if (metadata && metadata.length > 0) { - return metadata[0].type; // Возвращаем тип метрики - } - - return null; + return metadata?.length ? metadata[0].type : null; } catch (error) { console.error(`Ошибка при получении типа метрики ${metric}:`, error); return null; } } - //Данные конкретной метрики, включая ее тип - async fetchMetrics(metric: string): Promise { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/query`, { @@ -41,27 +39,23 @@ export class PrometheusService { }) ); - const metricType = await this.fetchMetricType(metric); // Получаем тип + const metricType = await this.fetchMetricType(metric); return response.data.data.result.map((entry): PrometheusMetric => ({ ...entry.metric, - timestamp: entry.value[0] * 1000, // Преобразуем в миллисекунды - value: parseFloat(entry.value[1]), // Преобразуем в число - type: metricType || 'unknown', // Добавляем тип метрики + timestamp: entry.value[0] * 1000, + value: parseFloat(entry.value[1]), + type: metricType || 'unknown', })); } - //Получаем данные всех метрик - async fetchAllMetrics(): Promise { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/label/__name__/values`) ); - return response.data.data; // Это массив с именами метрик + return response.data.data; } - // Получаем список всех метрик - async fetchAllMetricsWithValues(): Promise { const metricNames = await this.fetchAllMetrics(); const promises = metricNames.map(async (metric) => { @@ -70,4 +64,4 @@ export class PrometheusService { }); return Promise.all(promises); } -} \ No newline at end of file +} -- 2.40.1 From 76fa8931464878273bb8eae4ec9ca50d9fc31e40 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 26 Feb 2025 16:33:57 +0300 Subject: [PATCH 2/6] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20.env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 1 + 1 file changed, 1 insertion(+) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..2979ce2 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +#PROMETHEUS_API=http://192.168.2.37:9090/api/v1 \ No newline at end of file -- 2.40.1 From 5ed31984f897eb2c4a2cd214da8be0661faf0e4a Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Tue, 4 Mar 2025 11:11:54 -0500 Subject: [PATCH 3/6] =?UTF-8?q?=D0=9C=D0=B5=D1=82=D1=80=D0=B8=D0=BA=D0=B8?= =?UTF-8?q?=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=B7=D0=B0=D0=B1?= =?UTF-8?q?=D0=B8=D1=80=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D0=B7=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B2=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 ++- src/app.module.ts | 8 +++++- src/main.ts | 3 +- src/metrics.controller.ts | 17 ++++++++---- src/prometheus.service.ts | 58 +++++++++++++++++++++++++-------------- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 5aecf10..f14298a 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,14 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { - "@nestjs/axios": "^4.0.0", + "@nestjs/axios": "^4.0.0", "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", + "@nestjs/config": "^4.0.0", "@nestjs/platform-express": "^11.0.1", "axios": "^1.7.9", "reflect-metadata": "^0.2.2", + "dotenv": "^16.3.1", "rxjs": "^7.8.1" }, "devDependencies": { diff --git a/src/app.module.ts b/src/app.module.ts index 24e27bd..ee3d951 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,9 +2,15 @@ import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; import { PrometheusService } from './prometheus.service'; import { MetricsController } from './metrics.controller'; +import { ConfigModule } from '@nestjs/config'; @Module({ - imports: [HttpModule], // Используем новый HttpModule + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + envFilePath: '.env', + }), + HttpModule], controllers: [MetricsController], providers: [PrometheusService], }) diff --git a/src/main.ts b/src/main.ts index 98d5844..ac585d9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; + async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -11,4 +12,4 @@ async function bootstrap() { }); await app.listen(process.env.PORT ?? 3000); } -bootstrap(); +bootstrap(); \ No newline at end of file diff --git a/src/metrics.controller.ts b/src/metrics.controller.ts index acf26da..850510d 100644 --- a/src/metrics.controller.ts +++ b/src/metrics.controller.ts @@ -4,20 +4,27 @@ import { PrometheusService } from './prometheus.service'; @Controller('metrics') export class MetricsController { constructor(private readonly prometheusService: PrometheusService) { } - //Получение конкретной метрики + @Get() - async getMetrics(@Query('metric') metric: string) { + async getMetrics( + @Query('metric') metric: string, + @Query('start') start: number, + @Query('end') end: number, + @Query('step') step: number, + ) { + if (start && end && step) { + return this.prometheusService.fetchMetricsRange(metric, start, end, step); + } return this.prometheusService.fetchMetrics(metric); } - // Получить список всех метрик + @Get('/all') async getAllMetrics() { return this.prometheusService.fetchAllMetrics(); } - // Получить ВСЕ метрики со значениями @Get('/all-values') async getAllMetricsWithValues() { return this.prometheusService.fetchAllMetricsWithValues(); } -} +} \ No newline at end of file diff --git a/src/prometheus.service.ts b/src/prometheus.service.ts index a444ab3..b829278 100644 --- a/src/prometheus.service.ts +++ b/src/prometheus.service.ts @@ -1,15 +1,20 @@ import { Injectable } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; import { lastValueFrom } from 'rxjs'; import { PrometheusMetric } from './prometheus-metric.interface'; @Injectable() export class PrometheusService { - private readonly prometheusUrl = 'http://192.168.2.37:9090/api/v1'; + private readonly prometheusUrl: string; - constructor(private readonly httpService: HttpService) { } - - //Получаем тип метрики + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService + ) { + this.prometheusUrl = this.configService.get('PROMETHEUS_API', 'http://localhost:9090'); + console.log('Prometheus API URL:', this.prometheusUrl); + } async fetchMetricType(metric: string): Promise { try { @@ -20,20 +25,13 @@ export class PrometheusService { ); const metadata = response.data.data[metric]; - - if (metadata && metadata.length > 0) { - return metadata[0].type; // Возвращаем тип метрики - } - - return null; + return metadata?.length ? metadata[0].type : null; } catch (error) { console.error(`Ошибка при получении типа метрики ${metric}:`, error); return null; } } - //Данные конкретной метрики, включая ее тип - async fetchMetrics(metric: string): Promise { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/query`, { @@ -41,27 +39,47 @@ export class PrometheusService { }) ); - const metricType = await this.fetchMetricType(metric); // Получаем тип + const metricType = await this.fetchMetricType(metric); return response.data.data.result.map((entry): PrometheusMetric => ({ ...entry.metric, - timestamp: entry.value[0] * 1000, // Преобразуем в миллисекунды - value: parseFloat(entry.value[1]), // Преобразуем в число - type: metricType || 'unknown', // Добавляем тип метрики + timestamp: entry.value[0] * 1000, + value: parseFloat(entry.value[1]), + type: metricType || 'unknown', })); } - //Получаем данные всех метрик + async fetchMetricsRange(metric: string, start: number, end: number, step: number): Promise { + const response = await lastValueFrom( + this.httpService.get(`${this.prometheusUrl}/query_range`, { + params: { + query: metric, + start, + end, + step, + }, + }) + ); + + const metricType = await this.fetchMetricType(metric); + + return response.data.data.result.flatMap((entry) => + entry.values.map((value): PrometheusMetric => ({ + ...entry.metric, + timestamp: value[0] * 1000, + value: parseFloat(value[1]), + type: metricType || 'unknown', + })) + ); + } async fetchAllMetrics(): Promise { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/label/__name__/values`) ); - return response.data.data; // Это массив с именами метрик + return response.data.data; } - // Получаем список всех метрик - async fetchAllMetricsWithValues(): Promise { const metricNames = await this.fetchAllMetrics(); const promises = metricNames.map(async (metric) => { -- 2.40.1 From 085d48cdbf268e42f940143d6fb9ec0c7fc40c12 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Thu, 6 Mar 2025 03:43:53 -0500 Subject: [PATCH 4/6] =?UTF-8?q?=D0=9C=D0=B5=D1=82=D1=80=D0=B8=D0=BA=D0=B8?= =?UTF-8?q?=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=B7=D0=B0=D0=B1?= =?UTF-8?q?=D0=B8=D1=80=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D0=B7=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B2=D0=B0=D0=BB=20=D0=B2=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/prometheus-metric.interface.ts | 3 ++- src/prometheus.service.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/prometheus-metric.interface.ts b/src/prometheus-metric.interface.ts index 646cbab..2f30262 100644 --- a/src/prometheus-metric.interface.ts +++ b/src/prometheus-metric.interface.ts @@ -4,4 +4,5 @@ export interface PrometheusMetric { timestamp: number; value: number; type: string; // Тип метрики ("gauge", "counter", и т. д.) -} + description?: string; // Описание метрики +} \ No newline at end of file diff --git a/src/prometheus.service.ts b/src/prometheus.service.ts index b829278..333292c 100644 --- a/src/prometheus.service.ts +++ b/src/prometheus.service.ts @@ -16,6 +16,7 @@ export class PrometheusService { console.log('Prometheus API URL:', this.prometheusUrl); } + // Получаем тип метрики async fetchMetricType(metric: string): Promise { try { const response = await lastValueFrom( @@ -32,6 +33,24 @@ export class PrometheusService { } } + // Получаем описание метрики + async fetchMetricDescription(metric: string): Promise { + try { + const response = await lastValueFrom( + this.httpService.get(`${this.prometheusUrl}/metadata`, { + params: { metric }, + }) + ); + + const metadata = response.data.data[metric]; + return metadata?.length ? metadata[0].help : undefined; + } catch (error) { + console.error(`Ошибка при получении описания метрики ${metric}:`, error); + return undefined; + } + } + + // Получаем данные метрики (текущие значения) async fetchMetrics(metric: string): Promise { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/query`, { @@ -40,15 +59,18 @@ export class PrometheusService { ); const metricType = await this.fetchMetricType(metric); + const metricDescription = await this.fetchMetricDescription(metric); return response.data.data.result.map((entry): PrometheusMetric => ({ ...entry.metric, timestamp: entry.value[0] * 1000, value: parseFloat(entry.value[1]), type: metricType || 'unknown', + description: metricDescription, // Добавляем описание })); } + // Получаем данные метрики за интервал async fetchMetricsRange(metric: string, start: number, end: number, step: number): Promise { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/query_range`, { @@ -62,6 +84,7 @@ export class PrometheusService { ); const metricType = await this.fetchMetricType(metric); + const metricDescription = await this.fetchMetricDescription(metric); return response.data.data.result.flatMap((entry) => entry.values.map((value): PrometheusMetric => ({ @@ -69,10 +92,12 @@ export class PrometheusService { timestamp: value[0] * 1000, value: parseFloat(value[1]), type: metricType || 'unknown', + description: metricDescription, // Добавляем описание })) ); } + // Получаем список всех метрик async fetchAllMetrics(): Promise { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/label/__name__/values`) @@ -80,6 +105,7 @@ export class PrometheusService { return response.data.data; } + // Получаем все метрики с их значениями async fetchAllMetricsWithValues(): Promise { const metricNames = await this.fetchAllMetrics(); const promises = metricNames.map(async (metric) => { -- 2.40.1 From 78edc4e3a4776f8d7cf957d173755f1df2a8e912 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Thu, 6 Mar 2025 03:51:55 -0500 Subject: [PATCH 5/6] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BA=D0=BE=D0=BD=D1=84=D0=BB=D0=B8=D0=BA=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/prometheus.service.ts | 72 ++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/src/prometheus.service.ts b/src/prometheus.service.ts index 407cdb0..333292c 100644 --- a/src/prometheus.service.ts +++ b/src/prometheus.service.ts @@ -8,10 +8,15 @@ import { PrometheusMetric } from './prometheus-metric.interface'; export class PrometheusService { private readonly prometheusUrl: string; - constructor(private readonly httpService: HttpService) { } - - //Получаем тип метрики + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService + ) { + this.prometheusUrl = this.configService.get('PROMETHEUS_API', 'http://localhost:9090'); + console.log('Prometheus API URL:', this.prometheusUrl); + } + // Получаем тип метрики async fetchMetricType(metric: string): Promise { try { const response = await lastValueFrom( @@ -28,8 +33,24 @@ export class PrometheusService { } } - //Данные конкретной метрики, включая ее тип + // Получаем описание метрики + async fetchMetricDescription(metric: string): Promise { + try { + const response = await lastValueFrom( + this.httpService.get(`${this.prometheusUrl}/metadata`, { + params: { metric }, + }) + ); + const metadata = response.data.data[metric]; + return metadata?.length ? metadata[0].help : undefined; + } catch (error) { + console.error(`Ошибка при получении описания метрики ${metric}:`, error); + return undefined; + } + } + + // Получаем данные метрики (текущие значения) async fetchMetrics(metric: string): Promise { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/query`, { @@ -37,18 +58,46 @@ export class PrometheusService { }) ); - const metricType = await this.fetchMetricType(metric); // Получаем тип + const metricType = await this.fetchMetricType(metric); + const metricDescription = await this.fetchMetricDescription(metric); return response.data.data.result.map((entry): PrometheusMetric => ({ ...entry.metric, - timestamp: entry.value[0] * 1000, // Преобразуем в миллисекунды - value: parseFloat(entry.value[1]), // Преобразуем в число - type: metricType || 'unknown', // Добавляем тип метрики + timestamp: entry.value[0] * 1000, + value: parseFloat(entry.value[1]), + type: metricType || 'unknown', + description: metricDescription, // Добавляем описание })); } - //Получаем данные всех метрик + // Получаем данные метрики за интервал + async fetchMetricsRange(metric: string, start: number, end: number, step: number): Promise { + const response = await lastValueFrom( + this.httpService.get(`${this.prometheusUrl}/query_range`, { + params: { + query: metric, + start, + end, + step, + }, + }) + ); + const metricType = await this.fetchMetricType(metric); + const metricDescription = await this.fetchMetricDescription(metric); + + return response.data.data.result.flatMap((entry) => + entry.values.map((value): PrometheusMetric => ({ + ...entry.metric, + timestamp: value[0] * 1000, + value: parseFloat(value[1]), + type: metricType || 'unknown', + description: metricDescription, // Добавляем описание + })) + ); + } + + // Получаем список всех метрик async fetchAllMetrics(): Promise { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/label/__name__/values`) @@ -56,8 +105,7 @@ export class PrometheusService { return response.data.data; } - // Получаем список всех метрик - + // Получаем все метрики с их значениями async fetchAllMetricsWithValues(): Promise { const metricNames = await this.fetchAllMetrics(); const promises = metricNames.map(async (metric) => { @@ -66,4 +114,4 @@ export class PrometheusService { }); return Promise.all(promises); } -} +} \ No newline at end of file -- 2.40.1 From 7b7b7f703414f653c9f89330a03e92e1a44af433 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Thu, 6 Mar 2025 11:55:17 +0300 Subject: [PATCH 6/6] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20.env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 2979ce2..f60a9b4 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -#PROMETHEUS_API=http://192.168.2.37:9090/api/v1 \ No newline at end of file +#PROMETHEUS_API=http://192.168.2.34:9090/api/v1 \ No newline at end of file -- 2.40.1