added a garbage filter

pull/17/head
DmitriyA 2025-05-26 08:53:22 -04:00
parent 1a63f20bb0
commit 319e2cdd69
2 changed files with 99 additions and 52 deletions

View File

@ -0,0 +1,5 @@
export interface MetricMetadata {
metric: string;
type: string;
help: string;
}

View File

@ -1,15 +1,12 @@
import { Injectable, Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PrometheusService } from '../prometheus.service'; import { PrometheusService } from '../prometheus.service';
import { MenuItem } from './menu.interface'; import { MenuItem } from './menu.interface';
@Injectable() @Injectable()
export class MenuService { export class MenuService {
constructor( constructor(private readonly prometheusService: PrometheusService) {}
private readonly prometheusService: PrometheusService // Просто объявляем зависимость
) {}
async getFullMenu(): Promise<MenuItem> { async getFullMenu(): Promise<MenuItem> {
// Реализация остается прежней
const dynamicItems = await this.generateDynamicItems(); const dynamicItems = await this.generateDynamicItems();
return this.injectDynamicItems(this.getStaticStructure(), dynamicItems); return this.injectDynamicItems(this.getStaticStructure(), dynamicItems);
} }
@ -35,30 +32,65 @@ export class MenuService {
} }
private async generateDynamicItems(): Promise<MenuItem[]> { private async generateDynamicItems(): Promise<MenuItem[]> {
const metricNames = await this.prometheusService.fetchAllMetrics(); const metricNames = await this.prometheusService.fetchAllMetrics();
const allSeries = await Promise.all( // Получаем все серии для каждой метрики
metricNames.map(async name => { const allSeries = (
const series = await this.prometheusService.fetchMetricSeries(name); await Promise.all(
return series.map(s => ({ metricNames.map(async name => {
metric: name, const series = await this.prometheusService.fetchMetricSeries(name);
labels: s return series.map(s => ({
})); metric: name,
}) labels: s
); }));
})
)
).flat();
const flatSeries = allSeries.flat(); // Загружаем мета-информацию по каждой метрике
const metadataMap = new Map<string, string>(); // metric -> help
const devices = this.extractUniqueEntities(flatSeries, 'device'); await Promise.all(
metricNames.map(async metric => {
try {
const meta = await this.prometheusService.fetchMetricMetadata(metric);
if (meta?.help) {
metadataMap.set(metric, meta.help);
}
} catch (e) {
console.warn(`No metadata for metric ${metric}`);
}
})
);
return devices.map(device => ({ const isGarbageDevice = (device: string) =>
id: `device_${device}`, device.startsWith('/dev') ||
title: `Graviton S2082I (${device})`, device.startsWith('/proc') ||
items: this.generateModuleItems(device, flatSeries), device.startsWith('/sys') ||
isDynamic: true device.startsWith('/rootfs') ||
})); device.startsWith('/var') ||
} device.startsWith('overlay') ||
device.startsWith('br') ||
device.startsWith('docker0') ||
device.startsWith('ens18') ||
device.startsWith('sda') ||
device.startsWith('sr0') ||
device.startsWith('tmpfs') ||
device.startsWith('veth') ||
device.startsWith('gvfsd') ||
device.startsWith('lo') ||
device.startsWith('/run');
const devices = this.extractUniqueEntities(allSeries, 'device')
.filter(device => !isGarbageDevice(device));
return devices.map(device => ({
id: `device_${device}`,
title: `Graviton S2082I (${device})`,
items: this.generateModuleItems(device, allSeries, metadataMap),
isDynamic: true
}));
}
private extractUniqueEntities(metrics: any[], field: string): string[] { private extractUniqueEntities(metrics: any[], field: string): string[] {
const entities = new Set<string>(); const entities = new Set<string>();
@ -70,76 +102,86 @@ export class MenuService {
return Array.from(entities); return Array.from(entities);
} }
private generateModuleItems(device: string, seriesData: { metric: string, labels: Record<string, string> }[]): MenuItem[] { private generateModuleItems(
device: string,
seriesData: { metric: string; labels: Record<string, string> }[],
metadataMap: Map<string, string>
): MenuItem[] {
const modules = new Set<string>(); const modules = new Set<string>();
seriesData.forEach(({ labels }) => { seriesData.forEach(({ labels }) => {
if (labels.device === device && labels.source_id) { if (labels.device === device && labels.source_id) {
modules.add(labels.source_id); modules.add(labels.source_id);
} }
}); });
return Array.from(modules).map(module => ({ return Array.from(modules).map(module => ({
id: `module_${module.replace('module$', '')}`, id: `module_${device}_${module}`,
title: `OS Linux АО (${module})`, title: `Module ${module.replace('module$', '')}`,
items: this.generateMetricItems(device, module, seriesData), items: this.generateMetricItems(device, module, seriesData, metadataMap),
isDynamic: true isDynamic: true
})); }));
} }
private generateMetricItems(device: string, module: string, seriesData: { metric: string, labels: Record<string, string> }[]): MenuItem[] { private generateMetricItems(
device: string,
module: string,
seriesData: { metric: string; labels: Record<string, string> }[],
metadataMap: Map<string, string>
): MenuItem[] {
const filtered = seriesData.filter( const filtered = seriesData.filter(
({ labels }) => labels.device === device && labels.source_id === module ({ labels }) => labels.device === device && labels.source_id === module
); );
const uniqueMetrics = new Set(filtered.map(entry => entry.metric)); const uniqueMetrics = new Set(filtered.map(entry => entry.metric));
return Array.from(uniqueMetrics).map(metric => ({ return Array.from(uniqueMetrics).map(metric => {
id: `metric_${device}_${module}_${metric}`, const description = metadataMap.get(metric) || metric;
title: metric, // или запрашивать описание отдельно
metric, return {
filters: { id: `metric_${device}_${module}_${metric}`,
device, title: description,
source_id: module metric,
}, filters: {
isDynamic: true device,
})); source_id: module
},
isDynamic: true
};
});
} }
private injectDynamicItems(menu: MenuItem, dynamicItems: MenuItem[]): MenuItem { private injectDynamicItems(menu: MenuItem, dynamicItems: MenuItem[]): MenuItem {
if (menu.id === 'media_servers') { if (menu.id === 'media_servers') {
return { ...menu, items: dynamicItems }; return { ...menu, items: dynamicItems };
} }
return { return {
...menu, ...menu,
items: menu.items?.map(item => this.injectDynamicItems(item, dynamicItems)) || [] items: menu.items?.map(item => this.injectDynamicItems(item, dynamicItems)) || []
}; };
} }
async updateMenuItem(id: string, update: Partial<MenuItem>): Promise<MenuItem> { async updateMenuItem(id: string, update: Partial<MenuItem>): Promise<MenuItem> {
const fullMenu = await this.getFullMenu(); const fullMenu = await this.getFullMenu();
const item = this.findMenuItem(fullMenu, id); const item = this.findMenuItem(fullMenu, id);
if (!item) throw new Error('Menu item not found'); if (!item) throw new Error('Menu item not found');
Object.assign(item, update); Object.assign(item, update);
return item; return item;
} }
private findMenuItem(menu: MenuItem, id: string): MenuItem | null { private findMenuItem(menu: MenuItem, id: string): MenuItem | null {
if (menu.id === id) return menu; if (menu.id === id) return menu;
if (menu.items) { if (menu.items) {
for (const item of menu.items) { for (const item of menu.items) {
const found = this.findMenuItem(item, id); const found = this.findMenuItem(item, id);
if (found) return found; if (found) return found;
} }
} }
return null; return null;
} }
} }