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'; import { MenuItem } from './menu/menu.interface'; @Injectable() export class PrometheusService { private readonly prometheusUrl: string; 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( this.httpService.get(`${this.prometheusUrl}/metadata`, { params: { metric }, }) ); const metadata = response.data.data[metric]; 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 { try { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/query`, { params: { query: metric }, }) ); const metricType = await this.fetchMetricType(metric); const metricDescription = await this.fetchMetricDescription(metric); return response.data.data.result.map((entry): PrometheusMetric => ({ __name__: entry.metric.__name__ || metric, device: entry.metric.device, instance: entry.metric.instance, job: entry.metric.job, source_id: entry.metric.source_id, status: entry.metric.status || '0', timestamp: entry.value[0] * 1000, value: parseFloat(entry.value[1]), type: metricType || 'gauge', description: metricDescription, ...entry.metric })); } catch (error) { console.error(`Error fetching metrics for ${metric}:`, error); throw error; } } async fetchMetricsWithFilters(metric: string, filters: Record): Promise { try { const query = this.buildFilteredQuery(metric, filters); const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/query`, { params: { query } }) ); const metricType = await this.fetchMetricType(metric); const metricDescription = await this.fetchMetricDescription(metric); return response.data.data.result.map((entry): PrometheusMetric => ({ __name__: entry.metric.__name__ || metric, device: entry.metric.device, instance: entry.metric.instance, job: entry.metric.job, source_id: entry.metric.source_id, status: entry.metric.status || '0', timestamp: entry.value[0] * 1000, value: parseFloat(entry.value[1]), type: metricType || 'gauge', description: metricDescription, ...entry.metric })); } catch (error) { console.error(`Error fetching metrics with filters for ${metric}:`, error); throw error; } } private buildFilteredQuery(metric: string, filters: Record): string { const filterParts = Object.entries(filters) .filter(([_, value]) => value !== undefined && value !== null && value !== "") .map(([key, value]) => { if (key === 'source_id' && !value.startsWith('module$')) { return `${key}="module$${value}"`; } return `${key}="${value}"`; }); return filterParts.length > 0 ? `${metric}{${filterParts.join(',')}}` : metric; } async fetchMetricsRange(metric: string, start: number, end: number, step: number, filters: Record = {}): Promise { const query = this.buildFilteredQuery(metric, filters); try { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/query_range`, { params: { query, start, end, step: step.toString() }, }) ); const metricType = await this.fetchMetricType(metric); const metricDescription = await this.fetchMetricDescription(metric); return response.data.data.result.flatMap((entry) => entry.values.map((value): PrometheusMetric => ({ __name__: entry.metric.__name__ || metric, device: entry.metric.device, instance: entry.metric.instance, job: entry.metric.job, source_id: entry.metric.source_id, status: entry.metric.status || '0', timestamp: value[0] * 1000, value: parseFloat(value[1]), type: metricType || 'gauge', description: metricDescription, ...entry.metric })) ); } catch (error) { console.error('Error in fetchMetricsRange:', { error: error.response?.data || error.message, query, filters }); throw error; } } async getMetricsForMenuItem(menuItem: MenuItem): Promise { if (!menuItem.metric || !menuItem.filters) { throw new Error('MenuItem is not a metric item'); } return this.fetchMetricsWithFilters(menuItem.metric, menuItem.filters); } // ✅ Новый метод: получает базовое описание метрики (help, type) async fetchMetricMetadata(metric: string): Promise<{ name: string; help?: string; type?: string; }> { try { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/metadata`, { params: { metric } }) ); const data = response.data?.data?.[metric]?.[0]; return { name: metric, help: data?.help, type: data?.type }; } catch (error) { console.error(`Error fetching metadata for ${metric}:`, error); return { name: metric }; } } // ✅ Новый метод: получает ВСЕ серии метрики (все комбинации label-ов) async fetchMetricSeries(metric: string): Promise[]> { try { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/series`, { params: { 'match[]': metric } }) ); return response.data.data || []; } catch (error) { console.error(`Error fetching series for ${metric}:`, error); return []; } } async fetchAllMetrics(): Promise { const response = await lastValueFrom( this.httpService.get(`${this.prometheusUrl}/label/__name__/values`) ); return response.data.data; } async fetchAllMetricsWithValues(): Promise { const metricNames = await this.fetchAllMetrics(); const promises = metricNames.map(async (metric) => { const data = await this.fetchMetrics(metric); return { metric, data }; }); return Promise.all(promises); } }