trust-module-backend/src/prometheus.service.ts

236 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<string>('PROMETHEUS_API', 'http://localhost:9090');
console.log('Prometheus API URL:', this.prometheusUrl);
}
async fetchMetricType(metric: string): Promise<string | null> {
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<string | undefined> {
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<PrometheusMetric[]> {
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<string, string>): Promise<PrometheusMetric[]> {
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, string>): 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<string, string> = {}): Promise<PrometheusMetric[]> {
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<PrometheusMetric[]> {
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<Record<string, string>[]> {
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<string[]> {
const response = await lastValueFrom(
this.httpService.get(`${this.prometheusUrl}/label/__name__/values`)
);
return response.data.data;
}
async fetchAllMetricsWithValues(): Promise<any[]> {
const metricNames = await this.fetchAllMetrics();
const promises = metricNames.map(async (metric) => {
const data = await this.fetchMetrics(metric);
return { metric, data };
});
return Promise.all(promises);
}
}