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

235 lines
7.6 KiB
TypeScript

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]) => {
// Убираем автоматическое добавление "module$" для source_id
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,
instance: '192.168.2.34:9050'
});
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);
}
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
};
}
}
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);
}
}