Skip to content

Commit

Permalink
Merge pull request elastic#22 from Elastic-AWP-Platform/refactor-use-…
Browse files Browse the repository at this point in the history
…process-tree

Refactor use process tree and unit tests
  • Loading branch information
Jack committed Dec 7, 2021
2 parents 0c2c821 + 1ee6b41 commit ce68be2
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 138 deletions.
Expand Up @@ -5,9 +5,17 @@
* 2.0.
*/

import { Process, ProcessEvent, EventAction, EventKind } from '../../types/process_tree';
import {
Process,
ProcessEvent,
ProcessFields,
EventAction,
EventKind,
ProcessMap,
User,
} from '../../types/process_tree';

const mockEvents = [
export const mockEvents = [
{
'@timestamp': new Date('2021-11-23T15:25:04.210Z'),
process: {
Expand Down Expand Up @@ -91,7 +99,7 @@ const mockEvents = [
},
executable: '/usr/bin/vi',
interactive: true,
entity_id: '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3726',
entity_id: '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3727',
parent: {
pid: 2442,
pgid: 2442,
Expand Down Expand Up @@ -163,7 +171,7 @@ const mockEvents = [
},
executable: '/usr/bin/vi',
interactive: true,
entity_id: '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3726',
entity_id: '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3728',
parent: {
pid: 2442,
pgid: 2442,
Expand Down Expand Up @@ -226,7 +234,7 @@ const mockEvents = [
},
];

const mockAlerts = [
export const mockAlerts = [
{
kibana: {
alert: {
Expand Down Expand Up @@ -419,8 +427,8 @@ const mockAlerts = [
},
];

const processMock: Process = {
id: '3f44d854-fe8d-5666-abc9-9efe7f970b4b',
export const processMock: Process = {
id: '3d0192c6-7c54-5ee6-a110-3539a7cf42bc',
events: [],
children: [],
autoExpand: false,
Expand All @@ -431,7 +439,31 @@ const processMock: Process = {
getAlerts: () => [],
hasExec: () => false,
getOutput: () => '',
getDetails: () => ({} as ProcessEvent),
getDetails: () =>
({
'@timestamp': new Date('2021-11-23T15:25:04.210Z'),
event: {
kind: EventKind.event,
category: 'process',
action: EventAction.exec,
},
process: {
args: [],
args_count: 0,
command_line: '',
entity_id: '3d0192c6-7c54-5ee6-a110-3539a7cf42bc',
executable: '',
interactive: false,
name: '',
working_directory: '/home/vagrant',
pid: 1,
pgid: 1,
user: {} as User,
parent: {} as ProcessFields,
session: {} as ProcessFields,
entry: {} as ProcessFields,
},
} as ProcessEvent),
isUserEntered: () => false,
getMaxAlertLevel: () => null,
};
Expand All @@ -451,3 +483,28 @@ export const sessionViewAlertProcessMock: Process = {
hasExec: () => true,
isUserEntered: () => true,
};

export const mockProcessMap = mockEvents.reduce(
(processMap, event) => {
processMap[event.process.entity_id] = {
id: event.process.entity_id,
events: [event],
children: [],
parent: undefined,
autoExpand: false,
searchMatched: null,
hasOutput: () => false,
hasAlerts: () => false,
getAlerts: () => [],
hasExec: () => false,
getOutput: () => '',
getDetails: () => event,
isUserEntered: () => false,
getMaxAlertLevel: () => null,
};
return processMap;
},
{
[sessionViewBasicProcessMock.id]: sessionViewBasicProcessMock,
} as ProcessMap
);
14 changes: 14 additions & 0 deletions x-pack/plugins/session_view/common/types/process_tree/index.ts
Expand Up @@ -29,6 +29,16 @@ export interface User {
name: string;
}

export interface EventResultBody {
hits: any[];
total: number;
}

export interface ProcessEventResults {
events: EventResultBody;
alerts: EventResultBody;
}

export interface ProcessFields {
args: string[];
args_count: number;
Expand Down Expand Up @@ -125,3 +135,7 @@ export interface Process {
isUserEntered(): boolean;
getMaxAlertLevel(): number | null;
}

export type ProcessMap = {
[key: string]: Process;
};
@@ -0,0 +1,74 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
mockEvents,
mockProcessMap,
} from '../../../common/mocks/constants/session_view_process.mock';
import { Process, ProcessMap } from '../../../common/types/process_tree';
import {
updateProcessMap,
buildProcessTree,
searchProcessTree,
autoExpandProcessTree,
} from './helpers';

const SESSION_ENTITY_ID = '3d0192c6-7c54-5ee6-a110-3539a7cf42bc';
const SEARCH_QUERY = 'vi';
const SEARCH_RESULT_PROCESS_ID = '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3727';

describe('process tree hook helpers tests', () => {
let processMap: ProcessMap;

beforeEach(() => {
processMap = {};
});

it('updateProcessMap works', () => {
processMap = updateProcessMap(processMap, mockEvents);

// processes are added to processMap
mockEvents.forEach((event) => {
expect(processMap[event.process.entity_id]).toBeTruthy();
});
});

it('buildProcessTree works', () => {
processMap = mockProcessMap;
const orphans: Process[] = [];
processMap = buildProcessTree(processMap, mockEvents, orphans, SESSION_ENTITY_ID);

const sessionLeaderChildrenIds = new Set(
processMap[SESSION_ENTITY_ID].children.map((child) => child.id)
);

// processes are added under their parent's childrean array in processMap
mockEvents.forEach((event) => {
expect(sessionLeaderChildrenIds.has(event.process.entity_id));
});
});

it('searchProcessTree works', () => {
const searchResults = searchProcessTree(mockProcessMap, SEARCH_QUERY);

// search returns the process with search query in its event args
expect(searchResults[0].id).toBe(SEARCH_RESULT_PROCESS_ID);
});

it('autoExpandProcessTree works', () => {
processMap = mockProcessMap;
// mock what buildProcessTree does
const childProcesses = Object.values(processMap).filter(
(process) => process.id !== SESSION_ENTITY_ID
);
processMap[SESSION_ENTITY_ID].children = childProcesses;

expect(processMap[SESSION_ENTITY_ID].autoExpand).toBeFalsy();
processMap = autoExpandProcessTree(processMap);
// session leader should have autoExpand to be true
expect(processMap[SESSION_ENTITY_ID].autoExpand).toBeTruthy();
});
});
122 changes: 122 additions & 0 deletions x-pack/plugins/session_view/public/components/ProcessTree/helpers.ts
@@ -0,0 +1,122 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Process, ProcessEvent, ProcessMap } from '../../../common/types/process_tree';
import { ProcessImpl } from './hooks';

export const updateProcessMap = (processMap: ProcessMap, events: ProcessEvent[]) => {
events.forEach((event) => {
const { entity_id: id } = event.process;
let process = processMap[id];

if (!process) {
process = new ProcessImpl(id);
processMap[id] = process;
}

process.events.push(event);
});

return processMap;
};

export const buildProcessTree = (
processMap: ProcessMap,
events: ProcessEvent[],
orphans: Process[],
sessionEntityId: string,
backwardDirection: boolean = false
) => {
events.forEach((event) => {
const process = processMap[event.process.entity_id];
const parentProcess = processMap[event.process.parent?.entity_id];

if (parentProcess) {
process.parent = parentProcess; // handy for recursive operations (like auto expand)

if (!parentProcess.children.includes(process) && parentProcess.id !== process.id) {
if (backwardDirection) {
parentProcess.children.unshift(process);
} else {
parentProcess.children.push(process);
}
}
} else if (process.id !== sessionEntityId && !orphans.includes(process)) {
// if no parent process, process is probably orphaned
orphans.push(process);
}
});

return processMap;
};

export const searchProcessTree = (processMap: ProcessMap, searchQuery: string | undefined) => {
const results = [];

if (searchQuery) {
for (const processId of Object.keys(processMap)) {
const process = processMap[processId];
const event = process.getDetails();
const { working_directory: workingDirectory, args } = event.process;

// TODO: the text we search is the same as what we render.
// should this be customizable??
const text = `${workingDirectory} ${args.join(' ')}`;

process.searchMatched = text.includes(searchQuery) ? searchQuery : null;

if (process.searchMatched) {
results.push(process);
}
}
} else {
for (const processId of Object.keys(processMap)) {
processMap[processId].searchMatched = null;
processMap[processId].autoExpand = false;
}
}

return results;
};

export const autoExpandProcessTree = (processMap: ProcessMap) => {
for (const processId of Object.keys(processMap)) {
const process = processMap[processId];

if (process.searchMatched || process.isUserEntered()) {
let { parent } = process;

while (parent && parent.id !== parent.parent?.id) {
parent.autoExpand = true;
parent = parent.parent;
}
}
}

return processMap;
};

export const processNewEvents = (
eventsProcessMap: ProcessMap,
events: ProcessEvent[] | undefined,
orphans: Process[],
sessionEntityId: string,
backwardDirection: boolean = false
) => {
if (!events || events.length === 0) {
return eventsProcessMap;
}

const updatedProcessMap = updateProcessMap(eventsProcessMap, events);
const builtProcessMap = buildProcessTree(
updatedProcessMap,
events,
orphans,
sessionEntityId,
backwardDirection
);
return autoExpandProcessTree(builtProcessMap);
};

0 comments on commit ce68be2

Please sign in to comment.