webview-ui/src/InspektorGadget/helpers/gadgets.ts (152 lines of code) (raw):

import { Filters, GadgetArguments, TraceOutputItem, } from "../../../../src/webview-contract/webviewDefinitions/inspektorGadget"; import { ProcessThreadKey } from "./gadgets/common"; import { cpuProfileMetadata } from "./gadgets/profile"; import { processSnapshotMetadata, socketSnapshotMetadata } from "./gadgets/snapshot"; import { blockIOTopMetadata, ebpfTopMetadata, fileTopMetadata, tcpTopMetadata } from "./gadgets/top"; import { dnsTraceMetadata, execTraceMetadata, tcpTraceMetadata } from "./gadgets/trace"; import { DataItem, GadgetCategory, GadgetMetadata, GadgetProfileResource, GadgetSnapshotResource, GadgetTopResource, GadgetTraceResource, ItemProperty, SortDirection, SortSpecifier, isDerivedProperty, toSortString, } from "./gadgets/types"; export type GadgetConfiguration = { category: GadgetCategory; resource: string; displayProperties: ItemProperty<string>[]; sortSpecifiers: SortSpecifier<string>[]; filters: Filters; maxItemCount?: number; timeout?: number; excludeThreads?: boolean; }; export function toGadgetArguments(config: GadgetConfiguration): GadgetArguments { return { gadgetCategory: config.category, gadgetResource: config.resource, filters: config.filters, // TODO: interval maxRows: config.maxItemCount, timeout: config.timeout, sortString: toSortString(config.sortSpecifiers), }; } export type TraceGadget = GadgetConfiguration & { traceId: number; output: TraceOutputItem[] | null; }; export type ConfiguredGadgetResources<T extends string> = Partial<{ [key in T]: GadgetMetadata<string> }>; const profileGadgetResources: ConfiguredGadgetResources<GadgetProfileResource> = { cpu: cpuProfileMetadata, }; const snapshotGadgetResources: ConfiguredGadgetResources<GadgetSnapshotResource> = { process: processSnapshotMetadata, socket: socketSnapshotMetadata, }; const topGadgetResources: ConfiguredGadgetResources<GadgetTopResource> = { tcp: tcpTopMetadata, "block-io": blockIOTopMetadata, ebpf: ebpfTopMetadata, file: fileTopMetadata, }; const traceGadgetResources: ConfiguredGadgetResources<GadgetTraceResource> = { dns: dnsTraceMetadata, tcp: tcpTraceMetadata, exec: execTraceMetadata, }; export const configuredGadgetResources: Record<GadgetCategory, ConfiguredGadgetResources<string>> = { profile: profileGadgetResources, snapshot: snapshotGadgetResources, top: topGadgetResources, trace: traceGadgetResources, }; export function getGadgetMetadata( gadgetCategory: GadgetCategory, gadgetResource: string, ): GadgetMetadata<string> | null { return configuredGadgetResources[gadgetCategory][gadgetResource] || null; } export function enrich(configuration: GadgetConfiguration, items: TraceOutputItem[]): TraceOutputItem[] { return items.map((item) => enrichItem(configuration, item)); } export function enrichSortAndFilter(configuration: GadgetConfiguration, items: TraceOutputItem[]): TraceOutputItem[] { items = enrich(configuration, items); items = filterItems(configuration, items); if (configuration.sortSpecifiers.length) { items = sortItems(items, configuration.sortSpecifiers); } if (configuration.maxItemCount) { items = takeFirstItems(items, configuration.maxItemCount); } return items; } function enrichItem(configuration: GadgetConfiguration, item: TraceOutputItem): TraceOutputItem { const itemEntries = Object.entries(item); const metadata = getGadgetMetadata(configuration.category, configuration.resource); const derivedEntries = metadata ? metadata.allProperties.filter(isDerivedProperty).map((p) => [p.key, p.valueGetter(item)]) : []; return Object.fromEntries([...itemEntries, ...derivedEntries]); } type SortFunction = (a: TraceOutputItem, b: TraceOutputItem) => number; function negateSortFunction(fn: SortFunction): SortFunction { return (a, b) => -fn(a, b); } function asSortFunction(specifier: SortSpecifier<string>): SortFunction { const key = specifier.property.key; const descending = specifier.direction === SortDirection.Descending; const fn: SortFunction = (a, b) => { const aValue = a[key]; const bValue = b[key]; if (aValue === null && bValue === null) { return 0; } else if (aValue === null) { return -1; } else if (bValue === null) { return 1; } else if (aValue > bValue) { return 1; } else if (aValue < bValue) { return -1; } return 0; }; return descending ? negateSortFunction(fn) : fn; } function combineSortFunctions(prev: SortFunction, current: SortFunction): SortFunction { return (a, b) => { const result = prev(a, b); return result === 0 ? current(a, b) : result; }; } function getSortFunction(sortSpecifiers: SortSpecifier<string>[]): SortFunction { return sortSpecifiers.map(asSortFunction).reduce(combineSortFunctions); } function sortItems(items: TraceOutputItem[], sortSpecifiers: SortSpecifier<string>[]): TraceOutputItem[] { if (sortSpecifiers.length === 0) { return items; } return [...items].sort(getSortFunction(sortSpecifiers)); } function takeFirstItems(items: TraceOutputItem[], maxRows: number): TraceOutputItem[] { return items.slice(0, maxRows); } function filterItems(configuration: GadgetConfiguration, items: TraceOutputItem[]): TraceOutputItem[] { if (configuration.excludeThreads) { items = items.filter( (item) => (item as DataItem<ProcessThreadKey>).pid === (item as DataItem<ProcessThreadKey>).tid, ); } return items; }