diff --git a/.env b/.env new file mode 100644 index 0000000..f60a9b4 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +#PROMETHEUS_API=http://192.168.2.34:9090/api/v1 \ No newline at end of file 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..5e92a63 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": { @@ -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..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-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 a444ab3..333292c 100644 --- a/src/prometheus.service.ts +++ b/src/prometheus.service.ts @@ -1,16 +1,22 @@ 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 { const response = await lastValueFrom( @@ -20,20 +26,31 @@ 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 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`, { @@ -41,27 +58,54 @@ 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`) ); - return response.data.data; // Это массив с именами метрик + return response.data.data; } - // Получаем список всех метрик - + // Получаем все метрики с их значениями async fetchAllMetricsWithValues(): Promise { const metricNames = await this.fetchAllMetrics(); const promises = metricNames.map(async (metric) => {