Compare commits

..

No commits in common. "86f6614f56e632093c5888d45b16beb00bbbd255" and "8466aa1f93e7c6e5efa1cd57df5dbac030c6d333" have entirely different histories.

18 changed files with 78 additions and 397 deletions

21
.env
View File

@ -1,9 +1,9 @@
# Прометеус #Прометеус
#PROMETHEUS_API=http://192.168.2.34:9090/api/v1 #PROMETHEUS_API=http://192.168.2.34:9090/api/v1
#FRONTEND_URL=192.168.2.39:5173 #FRONTEND_URL=192.168.2.39:5173
# Постгресс #Постгресс
#DB_HOST=192.168.2.37 #DB_HOST=192.168.2.37
#DB_PORT=5432 #DB_PORT=5432
#DB_USER=trust #DB_USER=trust
@ -11,28 +11,19 @@
#DB_NAME=trust-db #DB_NAME=trust-db
# JWT #JWT
#JWT_SECRET=x7F!2p9L#q1$z0*8R5vYgMnBk #JWT_SECRET=x7F!2p9L#q1$z0*8R5vYgMnBk
#JWT_SECRET=x7Fcdp9L#q1$z0*8R5vYgMnBk #JWT_SECRET=x7Fcdp9L#q1$z0*8R5vYgMnBk
# COOKIE #COOKIE
# Для production # Для production
#COOKIE_SECURE=true #COOKIE_SECURE=true
#COOKIE_SAME_SITE=strict #COOKIE_SAME_SITE=strict
# Для development # Для development
#COOKIE_SECURE=false # COOKIE_SECURE=false
#COOKIE_SAME_SITE=lax # COOKIE_SAME_SITE=lax
# Для меню # Для меню
#RANGES_API_URL=http://192.168.2.39:9999 #RANGES_API_URL=http://192.168.2.39:9999
#RANGES_API_ENDPOINT=/api/ranges/9999 #RANGES_API_ENDPOINT=/api/ranges/9999
# ClickHouse
#CLICKHOUSE_HOST=http://192.168.2.37:8123
#CLICKHOUSE_USER=vlad
#CLICKHOUSE_PASSWORD=vlad
#CLICKHOUSE_DB=zvks
# Для ai api
#AI_SERVICE_URL=http://192.168.2.39:5134

View File

@ -44,10 +44,7 @@
"@types/cookie-parser": "^1.4.8", "@types/cookie-parser": "^1.4.8",
"@nestjs/jwt": "^11.0.0", "@nestjs/jwt": "^11.0.0",
"@nestjs/passport": "^11.0.5", "@nestjs/passport": "^11.0.5",
"@nestjs/swagger": "11.1.4", "@nestjs/swagger": "11.1.4"
"@clickhouse/client": "^1.11.2",
"date-fns": "4.1.0",
"@clickhouse/client-web": "^1.11.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",

View File

View File

View File

@ -4,9 +4,7 @@ import { HttpModule } from '@nestjs/axios';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module'; import { AuthModule } from './auth/auth.module';
import { MenuModule } from './menu/menu.module'; import { MenuModule } from './menu/menu.module';
import { PrometheusModule } from './prometheus/prometheus.module'; import { PrometheusModule } from './prometheus.module';
import { ClickHouseModule } from './clickhouse/clickhouse.module';
import { ClickHouseController } from './clickhouse/clickhouse.controller';
@Module({ @Module({
imports: [ imports: [
@ -29,8 +27,6 @@ import { ClickHouseController } from './clickhouse/clickhouse.controller';
AuthModule, AuthModule,
PrometheusModule, PrometheusModule,
MenuModule, MenuModule,
ClickHouseModule,
], ],
controllers: [ClickHouseController],
}) })
export class AppModule { } export class AppModule {}

View File

@ -1,62 +0,0 @@
import { Controller, Get, Post } from '@nestjs/common';
import { ClickHouseService } from './clickhouse.service';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import { ConfigService } from '@nestjs/config';
@ApiTags('Clickhouse')
@Controller('clickhouse')
export class ClickHouseController {
constructor(
private readonly clickhouseService: ClickHouseService,
private readonly httpService: HttpService,
private readonly configService: ConfigService,
) { }
@Get()
@ApiOperation({ summary: 'Get metrics from ClickHouse' })
@ApiResponse({
status: 200,
description: 'Metrics data',
schema: {
type: 'array',
items: {
type: 'object',
properties: {
description: { type: 'string' },
device: { type: 'number' },
id: { type: 'string' },
name: { type: 'string' },
source: { type: 'string' },
status: { type: 'number' },
timestamp: { type: 'number' },
value: { type: 'string' },
},
},
},
})
async getClckhouse() {
return this.clickhouseService.getClckhouse();
}
@Post('send-to-ai')
@ApiOperation({ summary: 'Send metrics to AI service' })
@ApiResponse({
status: 200,
description: 'AI service response',
})
async sendToAI() {
const metrics = await this.clickhouseService.getClckhouse();
const aiServiceUrl = this.configService.get('AI_SERVICE_URL/api/metrics/rest');
try {
const response = await firstValueFrom(
this.httpService.post(aiServiceUrl, metrics)
);
return response.data;
} catch (error) {
throw new Error(`Failed to send data to AI: ${error.message}`);
}
}
}

View File

@ -1,25 +0,0 @@
import { Module, Global } from '@nestjs/common';
import { createClient, ClickHouseClient } from '@clickhouse/client';
import { ClickHouseService } from './clickhouse.service';
import { HttpModule } from '@nestjs/axios';
@Global()
@Module({
imports: [HttpModule],
providers: [
{
provide: 'CLICKHOUSE_CLIENT',
useFactory: (): ClickHouseClient => {
return createClient({
host: process.env.CLICKHOUSE_HOST || 'http://localhost:8123',
username: process.env.CLICKHOUSE_USER || 'default',
password: process.env.CLICKHOUSE_PASSWORD || '',
database: process.env.CLICKHOUSE_DB || 'default',
});
},
},
ClickHouseService,
],
exports: ['CLICKHOUSE_CLIENT', ClickHouseService],
})
export class ClickHouseModule { }

View File

@ -1,72 +0,0 @@
import { Injectable, Inject } from '@nestjs/common';
import { ClickHouseClient } from '@clickhouse/client';
interface ClickHouseRow {
EventDataTime: string;
ParameterBody: string;
CreateDataTime: string;
}
interface MetricData {
id: string;
name: string;
type: string;
addr?: string;
value: number | string | null;
description: string;
status: number;
device: number;
source: string;
}
interface ParameterBody {
service_name: string;
metrics: MetricData[];
}
@Injectable()
export class ClickHouseService {
constructor(
@Inject('CLICKHOUSE_CLIENT')
private readonly clickhouseClient: ClickHouseClient,
) { }
async getClckhouse() {
const query = `
SELECT
EventDataTime,
ParameterBody,
CreateDataTime
FROM zvks.complex_parameters
ORDER BY EventDataTime DESC
LIMIT 100
`;
const result = await this.clickhouseClient.query({
query,
format: 'JSONEachRow',
});
const rows = await result.json<ClickHouseRow>();
// Парсинг данных
return rows.flatMap((row: ClickHouseRow) => {
try {
const parameterBody: ParameterBody = JSON.parse(row.ParameterBody);
return parameterBody.metrics.map((metric: MetricData) => ({
id: metric.id,
name: metric.name,
value: metric.value !== null ? metric.value.toString() : 'null',
description: metric.description,
status: metric.status,
device: metric.device,
source: metric.source,
timestamp: new Date(row.EventDataTime).getTime(),
}));
} catch (e) {
console.error('Error parsing metric:', e);
return [];
}
});
}
}

View File

@ -42,7 +42,7 @@ async function bootstrap() {
}); });
// Настройка CORS // Настройка CORS
app.enableCors({//ПОСТАВИТЬ ПРОКСИ, ЧТОБЫ КОРС НЕ РУГАЛСЯ, ИЗМЕНЕНИЕ ПОЛИТИКИ СЕТЕВЫХ ПАКЕТОВ. ПИШУ IP СВОЙ, А ПОРТ ПРОКСИ. REVERSE PROXY. app.enableCors({
origin: [process.env.FRONTEND_URL, "http://dev.msf.enode"], origin: [process.env.FRONTEND_URL, "http://dev.msf.enode"],
credentials: true, credentials: true,
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',

View File

@ -8,7 +8,6 @@ export class MenuController {
@Get('full') @Get('full')
async getFullMenu(@Headers('if-modified-since') ifModifiedSince?: string) { async getFullMenu(@Headers('if-modified-since') ifModifiedSince?: string) {
console.log('GET /menu/full requested');
try { try {
const result = await this.menuService.getFullMenuWithCache(ifModifiedSince); const result = await this.menuService.getFullMenuWithCache(ifModifiedSince);
@ -27,12 +26,6 @@ export class MenuController {
); );
} }
} }
/*
@Get('full')
async getFullMenu() {
console.log('Simplified endpoint called');
return { test: 'OK' }; // Простейший ответ
} */
@Get('check-updates') @Get('check-updates')
async checkUpdates(@Headers('if-modified-since') ifModifiedSince: string) { async checkUpdates(@Headers('if-modified-since') ifModifiedSince: string) {

View File

@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
import { MenuController } from './menu.controller'; import { MenuController } from './menu.controller';
import { HttpModule } from '@nestjs/axios'; import { HttpModule } from '@nestjs/axios';
import { MenuService } from './menu.service'; import { MenuService } from './menu.service';
import { PrometheusModule } from '../prometheus/prometheus.module'; import { PrometheusModule } from '../prometheus.module';
import { RangeService } from './range.service'; import { RangeService } from './range.service';
import { RangeController } from './range.controller'; import { RangeController } from './range.controller';

View File

@ -1,5 +1,5 @@
import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { PrometheusService } from '../prometheus/prometheus.service'; import { PrometheusService } from '../prometheus.service';
import { MenuItem } from './menu.interface'; import { MenuItem } from './menu.interface';
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import * as path from 'path'; import * as path from 'path';

View File

@ -26,7 +26,6 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
stopUpdates: () => void; stopUpdates: () => void;
clients: Set<string>; clients: Set<string>;
}>(); }>();
private lastSentData = new Map<string, any>(); // Кэш последних отправленных данных
constructor(private readonly prometheusService: PrometheusService) { } constructor(private readonly prometheusService: PrometheusService) { }
@ -49,7 +48,6 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
if (subscription.clients.size === 0) { if (subscription.clients.size === 0) {
subscription.stopUpdates(); subscription.stopUpdates();
this.metricSubscriptions.delete(metric); this.metricSubscriptions.delete(metric);
this.lastSentData.delete(metric);
} }
} }
} }
@ -61,7 +59,6 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
if (subscription.clients.size === 0) { if (subscription.clients.size === 0) {
subscription.stopUpdates(); subscription.stopUpdates();
this.metricSubscriptions.delete(metric); this.metricSubscriptions.delete(metric);
this.lastSentData.delete(metric);
} }
} }
} }
@ -93,28 +90,17 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
} }
try { try {
const subscriptionKey = this.getSubscriptionKey(metric, filters);
// Отправляем текущие данные сразу при запросе
const initialData = await this.prometheusService.fetchMetricsWithFilters(metric, filters);
client.emit('metrics-data', { metric, data: initialData, requestId });
this.lastSentData.set(subscriptionKey, initialData);
const stopUpdates = await this.sendPeriodicUpdates( const stopUpdates = await this.sendPeriodicUpdates(
metric, metric,
step || 5000, step || 5000,
(data) => { (data) => {
const lastData = this.lastSentData.get(subscriptionKey);
if (!this.isDataEqual(lastData, data)) {
client.emit('metrics-data', { metric, data, requestId }); client.emit('metrics-data', { metric, data, requestId });
this.lastSentData.set(subscriptionKey, data);
}
}, },
filters filters
); );
const cleanup = () => { const cleanup = () => {
stopUpdates(); stopUpdates();
this.lastSentData.delete(subscriptionKey);
client.off('disconnect', cleanup); client.off('disconnect', cleanup);
client.off('unsubscribe-metric', cleanup); client.off('unsubscribe-metric', cleanup);
}; };
@ -136,20 +122,6 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
return `${metric}${filterString ? `?${filterString}` : ''}`; return `${metric}${filterString ? `?${filterString}` : ''}`;
} }
// Сравниваем данные, чтобы избежать лишних отправок
private isDataEqual(oldData: any[], newData: any[]): boolean {
if (!oldData || !newData || oldData.length !== newData.length) return false;
return oldData.every((oldItem, index) => {
const newItem = newData[index];
return (
oldItem.value === newItem.value &&
oldItem.status === newItem.status &&
oldItem.timestamp === newItem.timestamp
);
});
}
@SubscribeMessage('subscribe-metric') @SubscribeMessage('subscribe-metric')
async handleSubscribeMetric( async handleSubscribeMetric(
client: Socket, client: Socket,
@ -159,35 +131,18 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
filters?: Record<string, string>; filters?: Record<string, string>;
} }
) { ) {
const { metric, interval = 60000, filters = {} } = payload; // По умолчанию 60 секунд const { metric, interval = 5000, filters = {} } = payload;
const subscriptionKey = this.getSubscriptionKey(metric, filters); const subscriptionKey = this.getSubscriptionKey(metric, filters);
if (!this.metricSubscriptions.has(subscriptionKey)) { if (!this.metricSubscriptions.has(subscriptionKey)) {
// Отправляем текущие данные сразу при подписке
try {
const initialData = await this.prometheusService.fetchMetricsWithFilters(metric, filters);
client.emit('metrics-data', {
metric: subscriptionKey,
data: initialData
});
this.lastSentData.set(subscriptionKey, initialData);
} catch (error) {
this.logger.error(`Error fetching initial data for ${metric}:`, error.message);
}
const stopUpdates = await this.sendPeriodicUpdates( const stopUpdates = await this.sendPeriodicUpdates(
metric, metric,
interval, interval,
(data) => { (data) => {
// Отправляем только если данные изменились
const lastData = this.lastSentData.get(subscriptionKey);
if (!this.isDataEqual(lastData, data)) {
this.server.emit('metrics-data', { this.server.emit('metrics-data', {
metric: subscriptionKey, metric: subscriptionKey,
data data
}); });
this.lastSentData.set(subscriptionKey, data);
}
}, },
filters filters
); );
@ -198,14 +153,6 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
}); });
} else { } else {
this.metricSubscriptions.get(subscriptionKey)?.clients.add(client.id); this.metricSubscriptions.get(subscriptionKey)?.clients.add(client.id);
// Отправляем кэшированные данные новому клиенту
const cachedData = this.lastSentData.get(subscriptionKey);
if (cachedData) {
client.emit('metrics-data', {
metric: subscriptionKey,
data: cachedData
});
}
} }
const unsubscribe = () => { const unsubscribe = () => {
@ -215,7 +162,6 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
if (subscription.clients.size === 0) { if (subscription.clients.size === 0) {
subscription.stopUpdates(); subscription.stopUpdates();
this.metricSubscriptions.delete(subscriptionKey); this.metricSubscriptions.delete(subscriptionKey);
this.lastSentData.delete(subscriptionKey);
} }
} }
}; };
@ -230,11 +176,6 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
callback: (data: any) => void, callback: (data: any) => void,
filters: Record<string, string> = {} filters: Record<string, string> = {}
) { ) {
// Добавляем небольшую случайную задержку, чтобы избежать пиковой нагрузки
const initialDelay = Math.floor(Math.random() * 5000);
await new Promise(resolve => setTimeout(resolve, initialDelay));
const timer = setInterval(async () => { const timer = setInterval(async () => {
try { try {
const data = await this.prometheusService.fetchMetricsWithFilters(metric, filters); const data = await this.prometheusService.fetchMetricsWithFilters(metric, filters);
@ -246,7 +187,6 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
return () => { return () => {
clearInterval(timer); clearInterval(timer);
this.lastSentData.delete(this.getSubscriptionKey(metric, filters));
this.logger.log(`Stopped updates for ${metric}`); this.logger.log(`Stopped updates for ${metric}`);
}; };
} }

View File

@ -3,13 +3,11 @@ import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { PrometheusMetric } from './prometheus-metric.interface'; import { PrometheusMetric } from './prometheus-metric.interface';
import { MenuItem } from '../menu/menu.interface'; import { MenuItem } from './menu/menu.interface';
@Injectable() @Injectable()
export class PrometheusService { export class PrometheusService {
private readonly prometheusUrl: string; private readonly prometheusUrl: string;
private metricCache = new Map<string, { data: any; timestamp: number }>();
private metadataCache = new Map<string, { type: string | null; description: string | undefined; timestamp: number }>();
constructor( constructor(
private readonly httpService: HttpService, private readonly httpService: HttpService,
@ -20,13 +18,6 @@ export class PrometheusService {
} }
async fetchMetricType(metric: string): Promise<string | null> { async fetchMetricType(metric: string): Promise<string | null> {
const cacheKey = `metadata-type-${metric}`;
const cacheEntry = this.metadataCache.get(cacheKey);
if (cacheEntry && Date.now() - cacheEntry.timestamp < 30000) {
return cacheEntry.type;
}
try { try {
const response = await lastValueFrom( const response = await lastValueFrom(
this.httpService.get(`${this.prometheusUrl}/metadata`, { this.httpService.get(`${this.prometheusUrl}/metadata`, {
@ -34,29 +25,14 @@ export class PrometheusService {
}) })
); );
const metadata = response.data.data[metric]; const metadata = response.data.data[metric];
const result = metadata?.length ? metadata[0].type : null; return metadata?.length ? metadata[0].type : null;
this.metadataCache.set(cacheKey, {
type: result,
description: cacheEntry?.description,
timestamp: Date.now()
});
return result;
} catch (error) { } catch (error) {
console.error(`Ошибка при получении типа метрики ${metric}:`, error); console.error(`Ошибка при получении типа метрики ${metric}:`, error);
return cacheEntry?.type || null; return null;
} }
} }
async fetchMetricDescription(metric: string): Promise<string | undefined> { async fetchMetricDescription(metric: string): Promise<string | undefined> {
const cacheKey = `metadata-description-${metric}`;
const cacheEntry = this.metadataCache.get(cacheKey);
if (cacheEntry && Date.now() - cacheEntry.timestamp < 30000) {
return cacheEntry.description;
}
try { try {
const response = await lastValueFrom( const response = await lastValueFrom(
this.httpService.get(`${this.prometheusUrl}/metadata`, { this.httpService.get(`${this.prometheusUrl}/metadata`, {
@ -64,29 +40,14 @@ export class PrometheusService {
}) })
); );
const metadata = response.data.data[metric]; const metadata = response.data.data[metric];
const result = metadata?.length ? metadata[0].help : undefined; return metadata?.length ? metadata[0].help : undefined;
this.metadataCache.set(cacheKey, {
type: cacheEntry?.type ?? null,
description: result,
timestamp: Date.now()
});
return result;
} catch (error) { } catch (error) {
console.error(`Ошибка при получении описания метрики ${metric}:`, error); console.error(`Ошибка при получении описания метрики ${metric}:`, error);
return cacheEntry?.description; return undefined;
} }
} }
async fetchMetrics(metric: string): Promise<PrometheusMetric[]> { async fetchMetrics(metric: string): Promise<PrometheusMetric[]> {
const cacheKey = `${metric}:{}`;
const cacheEntry = this.metricCache.get(cacheKey);
if (cacheEntry && Date.now() - cacheEntry.timestamp < 5000) {
return cacheEntry.data;
}
try { try {
const response = await lastValueFrom( const response = await lastValueFrom(
this.httpService.get(`${this.prometheusUrl}/query`, { this.httpService.get(`${this.prometheusUrl}/query`, {
@ -97,7 +58,7 @@ export class PrometheusService {
const metricType = await this.fetchMetricType(metric); const metricType = await this.fetchMetricType(metric);
const metricDescription = await this.fetchMetricDescription(metric); const metricDescription = await this.fetchMetricDescription(metric);
const result = response.data.data.result.map((entry): PrometheusMetric => ({ return response.data.data.result.map((entry): PrometheusMetric => ({
__name__: entry.metric.__name__ || metric, __name__: entry.metric.__name__ || metric,
device: entry.metric.device, device: entry.metric.device,
instance: entry.metric.instance, instance: entry.metric.instance,
@ -110,24 +71,13 @@ export class PrometheusService {
description: metricDescription, description: metricDescription,
...entry.metric ...entry.metric
})); }));
this.metricCache.set(cacheKey, { data: result, timestamp: Date.now() });
return result;
} catch (error) { } catch (error) {
console.error(`Error fetching metrics for ${metric}:`, error); console.error(`Error fetching metrics for ${metric}:`, error);
if (cacheEntry) return cacheEntry.data;
throw error; throw error;
} }
} }
async fetchMetricsWithFilters(metric: string, filters: Record<string, string>): Promise<PrometheusMetric[]> { async fetchMetricsWithFilters(metric: string, filters: Record<string, string>): Promise<PrometheusMetric[]> {
const cacheKey = `${metric}:${JSON.stringify(filters)}`;
const cacheEntry = this.metricCache.get(cacheKey);
if (cacheEntry && Date.now() - cacheEntry.timestamp < 5000) {
return cacheEntry.data;
}
try { try {
const query = this.buildFilteredQuery(metric, filters); const query = this.buildFilteredQuery(metric, filters);
const response = await lastValueFrom( const response = await lastValueFrom(
@ -139,7 +89,7 @@ export class PrometheusService {
const metricType = await this.fetchMetricType(metric); const metricType = await this.fetchMetricType(metric);
const metricDescription = await this.fetchMetricDescription(metric); const metricDescription = await this.fetchMetricDescription(metric);
const result = response.data.data.result.map((entry): PrometheusMetric => ({ return response.data.data.result.map((entry): PrometheusMetric => ({
__name__: entry.metric.__name__ || metric, __name__: entry.metric.__name__ || metric,
device: entry.metric.device, device: entry.metric.device,
instance: entry.metric.instance, instance: entry.metric.instance,
@ -152,12 +102,8 @@ export class PrometheusService {
description: metricDescription, description: metricDescription,
...entry.metric ...entry.metric
})); }));
this.metricCache.set(cacheKey, { data: result, timestamp: Date.now() });
return result;
} catch (error) { } catch (error) {
console.error(`Error fetching metrics with filters for ${metric}:`, error); console.error(`Error fetching metrics with filters for ${metric}:`, error);
if (cacheEntry) return cacheEntry.data;
throw error; throw error;
} }
} }
@ -166,24 +112,20 @@ export class PrometheusService {
const filterParts = Object.entries(filters) const filterParts = Object.entries(filters)
.filter(([_, value]) => value !== undefined && value !== null && value !== "") .filter(([_, value]) => value !== undefined && value !== null && value !== "")
.map(([key, value]) => { .map(([key, value]) => {
// Убираем автоматическое добавление "module$" для source_id
return `${key}="${value}"`; return `${key}="${value}"`;
}); });
return filterParts.length > 0 return filterParts.length > 0
? `${metric}{${filterParts.join(',')}}` ? `${metric}{${filterParts.join(',')}}`
: metric; : metric;
} }
async fetchMetricsRange(metric: string, start: number, end: number, step: number, filters: Record<string, string> = {}): Promise<PrometheusMetric[]> { async fetchMetricsRange(metric: string, start: number, end: number, step: number, filters: Record<string, string> = {}): Promise<PrometheusMetric[]> {
// Рассчитываем оптимальный шаг, если не указан
const duration = end - start;
const optimalStep = Math.max(Math.floor(duration / 1000), 15); // Минимум 15 секунд
const query = this.buildFilteredQuery(metric, { const query = this.buildFilteredQuery(metric, {
...filters, ...filters,
instance: '192.168.2.34:9050' instance: '192.168.2.34:9050'
}); });
try { try {
const response = await lastValueFrom( const response = await lastValueFrom(
this.httpService.get(`${this.prometheusUrl}/query_range`, { this.httpService.get(`${this.prometheusUrl}/query_range`, {
@ -191,7 +133,7 @@ export class PrometheusService {
query, query,
start, start,
end, end,
step: optimalStep.toString() step: step.toString()
}, },
}) })
); );
@ -275,40 +217,21 @@ export class PrometheusService {
} }
async fetchAllMetrics(): Promise<string[]> { async fetchAllMetrics(): Promise<string[]> {
try {
const response = await lastValueFrom( const response = await lastValueFrom(
this.httpService.get(`${this.prometheusUrl}/label/__name__/values`) this.httpService.get(`${this.prometheusUrl}/label/__name__/values`)
); );
return response.data.data; return response.data.data;
} catch (error) {
console.error('Error fetching all metrics:', error);
return [];
}
} }
async fetchAllMetricsWithValues(): Promise<any[]> { async fetchAllMetricsWithValues(): Promise<any[]> {
const metricNames = await this.fetchAllMetrics(); const metricNames = await this.fetchAllMetrics();
const zvksMetrics = metricNames.filter(metric => const zvksMetrics = metricNames.filter(metric => metric.startsWith('zvks'));
metric.startsWith('zvks') ||
metric.includes('server_li') ||
metric.includes('application_li')
);
const promises = zvksMetrics.map(async (metric) => { const promises = zvksMetrics.map(async (metric) => {
try {
const data = await this.fetchMetrics(metric); const data = await this.fetchMetrics(metric);
return { metric, data }; return { metric, data };
} catch (error) {
console.error(`Error fetching data for metric ${metric}:`, error);
return { metric, data: [] };
}
}); });
return Promise.all(promises); return Promise.all(promises);
} }
clearCache(): void {
this.metricCache.clear();
this.metadataCache.clear();
}
} }