atomicshop 2.14.1__py3-none-any.whl → 2.14.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

Files changed (28) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/etws/trace.py +5 -21
  3. atomicshop/etws/traces/trace_dns.py +17 -14
  4. atomicshop/etws/traces/trace_sysmon_process_creation.py +34 -22
  5. atomicshop/get_process_list.py +133 -0
  6. atomicshop/monitor/change_monitor.py +1 -0
  7. atomicshop/monitor/checks/dns.py +2 -1
  8. atomicshop/process.py +3 -3
  9. atomicshop/process_poller/__init__.py +0 -0
  10. atomicshop/process_poller/pollers/__init__.py +0 -0
  11. atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
  12. atomicshop/process_poller/process_pool.py +208 -0
  13. atomicshop/process_poller/simple_process_pool.py +112 -0
  14. atomicshop/process_poller/tracer_base.py +45 -0
  15. atomicshop/process_poller/tracers/__init__.py +0 -0
  16. atomicshop/process_poller/tracers/event_log.py +46 -0
  17. atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
  18. atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +106 -43
  19. atomicshop/wrappers/pywin32w/win_event_log/subscribes/{subscribe_to_process_create.py → process_create.py} +18 -4
  20. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +103 -0
  21. atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py +97 -0
  22. {atomicshop-2.14.1.dist-info → atomicshop-2.14.3.dist-info}/METADATA +1 -1
  23. {atomicshop-2.14.1.dist-info → atomicshop-2.14.3.dist-info}/RECORD +27 -16
  24. atomicshop/process_poller.py +0 -346
  25. /atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +0 -0
  26. {atomicshop-2.14.1.dist-info → atomicshop-2.14.3.dist-info}/LICENSE.txt +0 -0
  27. {atomicshop-2.14.1.dist-info → atomicshop-2.14.3.dist-info}/WHEEL +0 -0
  28. {atomicshop-2.14.1.dist-info → atomicshop-2.14.3.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """Atomic Basic functions and classes to make developer life easier"""
2
2
 
3
3
  __author__ = "Den Kras"
4
- __version__ = '2.14.1'
4
+ __version__ = '2.14.3'
atomicshop/etws/trace.py CHANGED
@@ -8,12 +8,10 @@ import etw
8
8
 
9
9
  from ..print_api import print_api
10
10
  from . import sessions
11
- from .. import process_poller
11
+ from ..process_poller import simple_process_pool
12
12
  from ..wrappers.psutilw import psutilw
13
13
 
14
14
 
15
- PROCESS_POLLER_ETW_DEFAULT_SESSION_NAME: str = 'AtomicShopProcessTrace'
16
-
17
15
  WAIT_FOR_PROCESS_POLLER_PID_SECONDS: int = 3
18
16
  WAIT_FOR_PROCESS_POLLER_PID_COUNTS: int = WAIT_FOR_PROCESS_POLLER_PID_SECONDS * 10
19
17
 
@@ -26,8 +24,7 @@ class EventTrace(etw.ETW):
26
24
  event_id_filters: list = None,
27
25
  session_name: str = None,
28
26
  close_existing_session_name: bool = True,
29
- enable_process_poller: bool = False,
30
- process_poller_method: Literal['psutil', 'pywin32', 'process_dll', 'sysmon_etw', 'event_log'] = 'event_log'
27
+ enable_process_poller: bool = False
31
28
  ):
32
29
  """
33
30
  :param providers: List of tuples with provider name and provider GUID.
@@ -41,13 +38,6 @@ class EventTrace(etw.ETW):
41
38
  :param enable_process_poller: Boolean to enable process poller. Gets the process PID, Name and CommandLine.
42
39
  Since the DNS events doesn't contain the process name and command line, only PID.
43
40
  Then DNS events will be enriched with the process name and command line from the process poller.
44
- :param process_poller_method: The method to get the process information. For more information, see the
45
- 'process_poller.ProcessPollerPool' class. Summary:
46
- 'psutil': Uses 'psutil' library to get the process information.
47
- 'pywin32': Uses 'pywin32' library to get the process information.
48
- 'process_dll': Uses 'process' custom DLL to get the process information.
49
- 'sysmon_etw': Uses 'sysmon_etw' uses sysmon and ETW to get the process information.
50
- 'event_log': Uses Security Windows EVent Log channel (event id 4688) to get the process information.
51
41
 
52
42
  ------------------------------------------
53
43
 
@@ -84,14 +74,8 @@ class EventTrace(etw.ETW):
84
74
  for provider in providers:
85
75
  etw_format_providers.append(etw.ProviderInfo(provider[0], etw.GUID(provider[1])))
86
76
 
87
- process_poller_etw_session_name = None
88
- if process_poller_method == 'sysmon_etw':
89
- process_poller_etw_session_name = PROCESS_POLLER_ETW_DEFAULT_SESSION_NAME
90
-
91
77
  if self.enable_process_poller:
92
- self.process_poller = process_poller.ProcessPollerPool(
93
- operation='process', poller_method=process_poller_method,
94
- sysmon_etw_session_name=process_poller_etw_session_name)
78
+ self.process_poller = simple_process_pool.SimpleProcessPool()
95
79
 
96
80
  super().__init__(
97
81
  providers=etw_format_providers, event_callback=function_callable, event_id_filters=event_id_filters,
@@ -152,8 +136,8 @@ class EventTrace(etw.ETW):
152
136
  event: tuple = self.event_queue.get()
153
137
 
154
138
  event_dict: dict = {
155
- 'event_id': event[0],
156
- 'event': event[1],
139
+ 'EventId': event[0],
140
+ 'EventHeader': event[1],
157
141
  'pid': event[1]['EventHeader']['ProcessId']
158
142
  }
159
143
 
@@ -1,10 +1,5 @@
1
- import time
2
-
3
1
  from .. import trace, const
4
- from ...wrappers.psutilw import psutilw
5
2
  from ...basics import dicts
6
- from ...process_poller import ProcessPollerPool
7
- from ...print_api import print_api
8
3
  from ... import dns
9
4
 
10
5
 
@@ -24,7 +19,8 @@ class DnsRequestResponseTrace:
24
19
  self,
25
20
  attrs: list = None,
26
21
  session_name: str = None,
27
- close_existing_session_name: bool = True
22
+ close_existing_session_name: bool = True,
23
+ skip_record_list: list = None
28
24
  ):
29
25
  """
30
26
  :param attrs: List of attributes to return. If None, all attributes will be returned.
@@ -35,6 +31,7 @@ class DnsRequestResponseTrace:
35
31
  False: if ETW session with 'session_name' exists, you will be notified and the new session will not be
36
32
  created. Instead, the existing session will be used. If there is a buffer from the previous session,
37
33
  you will get the events from the buffer.
34
+ :param skip_record_list: List of DNS Records to skip emitting. Example: ['PTR', 'SRV']
38
35
 
39
36
  -------------------------------------------------
40
37
 
@@ -56,6 +53,7 @@ class DnsRequestResponseTrace:
56
53
  """
57
54
 
58
55
  self.attrs = attrs
56
+ self.skip_record_list = skip_record_list
59
57
 
60
58
  if not session_name:
61
59
  session_name = ETW_DEFAULT_SESSION_NAME
@@ -91,23 +89,28 @@ class DnsRequestResponseTrace:
91
89
  event = self.event_trace.emit()
92
90
 
93
91
  event_dict: dict = {
94
- 'event_id': event['event_id'],
95
- 'domain': event['event']['QueryName'],
96
- 'query_type_id': str(event['event']['QueryType']),
97
- 'query_type': dns.TYPES_DICT[str(event['event']['QueryType'])],
92
+ 'event_id': event['EventId'],
93
+ 'domain': event['EventHeader']['QueryName'],
94
+ 'query_type_id': str(event['EventHeader']['QueryType']),
95
+ 'query_type': dns.TYPES_DICT[str(event['EventHeader']['QueryType'])],
98
96
  'pid': event['pid'],
99
97
  'name': event['name'],
100
98
  'cmdline': event['cmdline']
101
99
  }
102
100
 
101
+ # Skip emitting the record if it is in the 'skip_record_list'.
102
+ # Just recall the function to get the next event and return it.
103
+ if event_dict['query_type'] in self.skip_record_list:
104
+ return self.emit()
105
+
103
106
  # Defining list if ips and other answers, which aren't IPs.
104
107
  list_of_ips = list()
105
108
  list_of_other_domains = list()
106
109
  # Parse DNS results, only if 'QueryResults' key isn't empty, since many of the events are, mostly due errors.
107
- if event['event']['QueryResults']:
110
+ if event['EventHeader']['QueryResults']:
108
111
  # 'QueryResults' key contains a string with all the 'Answers' divided by type and ';' character.
109
112
  # Basically, we can parse each type out of string, but we need only IPs and other answers.
110
- list_of_parameters = event['event']['QueryResults'].split(';')
113
+ list_of_parameters = event['EventHeader']['QueryResults'].split(';')
111
114
 
112
115
  # Iterating through all the parameters that we got from 'QueryResults' key.
113
116
  for parameter in list_of_parameters:
@@ -127,11 +130,11 @@ class DnsRequestResponseTrace:
127
130
  event_dict['other_domains'] = list_of_other_domains
128
131
 
129
132
  # Getting the 'QueryStatus' key.
130
- event_dict['status_id'] = event['event']['QueryStatus']
133
+ event_dict['status_id'] = event['EventHeader']['QueryStatus']
131
134
 
132
135
  # Getting the 'QueryStatus' key. If DNS Query Status is '0' then it was executed successfully.
133
136
  # And if not, it means there was an error. The 'QueryStatus' indicate what number of an error it is.
134
- if event['event']['QueryStatus'] == '0':
137
+ if event['EventHeader']['QueryStatus'] == '0':
135
138
  event_dict['status'] = 'Success'
136
139
  else:
137
140
  event_dict['status'] = 'Error'
@@ -3,6 +3,8 @@ from ...wrappers import sysmonw
3
3
  from ...basics import dicts
4
4
 
5
5
 
6
+ DEFAULT_SESSION_NAME: str = 'AtomicShopSysmonProcessCreationTrace'
7
+
6
8
  PROVIDER_NAME: str = const.ETW_SYSMON['provider_name']
7
9
  PROVIDER_GUID: str = const.ETW_SYSMON['provider_guid']
8
10
  PROCESS_CREATION_EVENT_ID: int = const.ETW_SYSMON['event_ids']['process_create']
@@ -53,6 +55,9 @@ class SysmonProcessCreationTrace:
53
55
  self.attrs = attrs
54
56
  self.sysmon_directory: str = sysmon_directory
55
57
 
58
+ if not session_name:
59
+ session_name = DEFAULT_SESSION_NAME
60
+
56
61
  self.event_trace = trace.EventTrace(
57
62
  providers=[(PROVIDER_NAME, PROVIDER_GUID)],
58
63
  # lambda x: self.event_queue.put(x),
@@ -80,32 +85,39 @@ class SysmonProcessCreationTrace:
80
85
  print(dns_dict)
81
86
 
82
87
  :return: Dictionary with the event data.
88
+
89
+ -----------------------------------------------
90
+
91
+ Structure of the returned dictionary:
92
+ {
93
+ 'event_id': int,
94
+ 'ProcessId': int,
95
+ 'ProcessGuid': str,
96
+ 'Image': str,
97
+ 'FileVersion': str,
98
+ 'Product': str,
99
+ 'Company': str,
100
+ 'OriginalFileName': str,
101
+ 'CommandLine': str,
102
+ 'CurrentDirectory': str,
103
+ 'User': str,
104
+ 'LogonId': str,
105
+ 'LogonGuid': str,
106
+ 'TerminalSessionId': int,
107
+ 'IntegrityLevel': str,
108
+ 'Hashes': dict,
109
+ 'ParentProcessGuid': str,
110
+ 'ParentProcessId': int,
111
+ 'ParentImage': str,
112
+ 'ParentCommandLine': str
113
+ }
114
+
83
115
  """
84
116
 
85
117
  event = self.event_trace.emit()
86
118
 
87
- event_dict: dict = {
88
- 'event_id': event['event_id'],
89
- 'pid': event['event']['ProcessId'],
90
- 'process_guid': event['event']['ProcessGuid'],
91
- 'image': event['event']['Image'],
92
- 'file_version': event['event']['FileVersion'],
93
- 'product': event['event']['Product'],
94
- 'company': event['event']['Company'],
95
- 'original_file_name': event['event']['OriginalFileName'],
96
- 'command_line': event['event']['CommandLine'],
97
- 'current_directory': event['event']['CurrentDirectory'],
98
- 'user': event['event']['User'],
99
- 'logon_id': event['event']['LogonId'],
100
- 'logon_guid': event['event']['LogonGuid'],
101
- 'terminal_session_id': event['event']['TerminalSessionId'],
102
- 'integrity_level': event['event']['IntegrityLevel'],
103
- 'hashes': event['event']['Hashes'],
104
- 'parent_process_guid': event['event']['ParentProcessGuid'],
105
- 'parent_process_id': event['event']['ParentProcessId'],
106
- 'parent_image': event['event']['ParentImage'],
107
- 'parent_command_line': event['event']['ParentCommandLine']
108
- }
119
+ event_dict = {'EventId': event['EventId']}
120
+ event_dict.update(event['EventHeader'])
109
121
 
110
122
  if self.attrs:
111
123
  event_dict = dicts.reorder_keys(
@@ -0,0 +1,133 @@
1
+ from typing import Union, Literal
2
+
3
+ from .wrappers.pywin32w import wmi_win32process
4
+ from .wrappers.psutilw import psutilw
5
+ from .basics import dicts
6
+ from . import get_process_name_cmd_dll
7
+
8
+
9
+ class GetProcessList:
10
+ """
11
+ The class is responsible for getting the list of running processes.
12
+
13
+ Example of one time polling with 'pywin32' method:
14
+ from atomicshop import process_poller
15
+ process_list: dict = \
16
+ process_poller.GetProcessList(get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
17
+ """
18
+ def __init__(
19
+ self,
20
+ get_method: Literal['psutil', 'pywin32', 'process_dll'] = 'process_dll',
21
+ connect_on_init: bool = False
22
+ ):
23
+ """
24
+ :param get_method: str, The method to get the list of processes. Default is 'process_list_dll'.
25
+ 'psutil': Get the list of processes by 'psutil' library. Resource intensive and slow.
26
+ 'pywin32': Get the list of processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
27
+ 'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64.
28
+ :param connect_on_init: bool, if True, will connect to the service on init. 'psutil' don't need to connect.
29
+ """
30
+ self.get_method = get_method
31
+ self.process_polling_instance = None
32
+
33
+ self.connected = False
34
+
35
+ if self.get_method == 'psutil':
36
+ self.process_polling_instance = psutilw.PsutilProcesses()
37
+ self.connected = True
38
+ elif self.get_method == 'pywin32':
39
+ self.process_polling_instance = wmi_win32process.Pywin32Processes()
40
+ elif self.get_method == 'process_dll':
41
+ self.process_polling_instance = get_process_name_cmd_dll.ProcessNameCmdline()
42
+
43
+ if connect_on_init:
44
+ self.connect()
45
+
46
+ def connect(self):
47
+ """
48
+ Connect to the service. 'psutil' don't need to connect.
49
+ """
50
+
51
+ # If poller method is none of the allowed methods.
52
+ if self.get_method not in ['psutil', 'pywin32', 'process_dll']:
53
+ raise ValueError(f"Method '{self.get_method}' is not allowed.")
54
+
55
+ # If the service is not connected yet. Since 'psutil' don't need to connect.
56
+ if not self.connected:
57
+ if self.get_method == 'pywin32':
58
+ self.process_polling_instance.connect()
59
+ self.connected = True
60
+ elif self.get_method == 'process_dll':
61
+ self.process_polling_instance.load()
62
+ self.connected = True
63
+
64
+ def get_processes(self, as_dict: bool = True) -> Union[list, dict]:
65
+ """
66
+ The function will get the list of opened processes and return it as a list of dicts.
67
+
68
+ :return: dict while key is pid or list of dicts, of opened processes (depending on 'as_dict' setting).
69
+ """
70
+
71
+ if as_dict:
72
+ if self.get_method == 'psutil':
73
+ return self.process_polling_instance.get_processes_as_dict(
74
+ attrs=['pid', 'name', 'cmdline'], cmdline_to_string=True)
75
+ elif self.get_method == 'pywin32':
76
+ processes = self.process_polling_instance.get_processes_as_dict(
77
+ attrs=['ProcessId', 'Name', 'CommandLine'])
78
+
79
+ # Convert the keys from WMI to the keys that are used in 'psutil'.
80
+ converted_process_dict = dict()
81
+ for pid, process_info in processes.items():
82
+ converted_process_dict[pid] = dicts.convert_key_names(
83
+ process_info, {'Name': 'name', 'CommandLine': 'cmdline'})
84
+
85
+ return converted_process_dict
86
+ elif self.get_method == 'process_dll':
87
+ return self.process_polling_instance.get_process_details(as_dict=True)
88
+ else:
89
+ if self.get_method == 'psutil':
90
+ return self.process_polling_instance.get_processes_as_list_of_dicts(
91
+ attrs=['pid', 'name', 'cmdline'], cmdline_to_string=True)
92
+ elif self.get_method == 'pywin32':
93
+ processes = self.process_polling_instance.get_processes_as_list_of_dicts(
94
+ attrs=['ProcessId', 'Name', 'CommandLine'])
95
+
96
+ # Convert the keys from WMI to the keys that are used in 'psutil'.
97
+ for process_index, process_info in enumerate(processes):
98
+ processes[process_index] = dicts.convert_key_names(
99
+ process_info, {'ProcessId': 'pid', 'Name': 'name', 'CommandLine': 'cmdline'})
100
+
101
+ return processes
102
+ elif self.get_method == 'process_dll':
103
+ return self.process_polling_instance.get_process_details(as_dict=as_dict)
104
+
105
+
106
+ def get_process_time_tester(
107
+ get_method: Literal['psutil', 'pywin32', 'process_dll'] = 'process_dll',
108
+ times_to_test: int = 50
109
+ ):
110
+ """
111
+ The function will test the time it takes to get the list of processes with different methods and cycles.
112
+
113
+ :param get_method: str, The method to get the list of processes. Default is 'process_list_dll'.
114
+ 'psutil': Get the list of processes by 'psutil' library. Resource intensive and slow.
115
+ 'pywin32': Get the list of processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
116
+ 'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64
117
+ :param times_to_test: int, how many times to test the function.
118
+ """
119
+
120
+ import timeit
121
+
122
+ setup_code = '''
123
+ from atomicshop import process_poller
124
+ get_process_list = process_poller.GetProcessList(get_method=get_method, connect_on_init=True)
125
+ '''
126
+
127
+ test_code = '''
128
+ test = get_process_list.get_processes()
129
+ '''
130
+
131
+ # globals need to be specified, otherwise the setup_code won't work with passed variables.
132
+ times = timeit.timeit(setup=setup_code, stmt=test_code, number=times_to_test, globals=locals())
133
+ print(f'Execution time: {times}')
@@ -4,6 +4,7 @@ from .checks import dns, network, file, url, process_running
4
4
 
5
5
 
6
6
  DNS__DEFAULT_SETTINGS = {
7
+ 'skip_record_list': [], # List of DNS Records to skip emitting. Example: ['PTR', 'SRV']
7
8
  'learning_mode_create_unique_entries_list': True,
8
9
  'learning_hours': 24, # 0 - the learning will never stop.
9
10
  'alert_about_missing_entries_after_learning': False,
@@ -27,7 +27,8 @@ class DnsCheck:
27
27
  trace_dns.DnsRequestResponseTrace(
28
28
  attrs=['name', 'cmdline', 'domain', 'query_type'],
29
29
  session_name=self.etw_session_name,
30
- close_existing_session_name=True
30
+ close_existing_session_name=True,
31
+ skip_record_list=self.settings['skip_record_list'],
31
32
  )
32
33
  )
33
34
 
atomicshop/process.py CHANGED
@@ -11,7 +11,7 @@ from .basics import strings
11
11
  from .wrappers import ubuntu_terminal
12
12
 
13
13
  if os.name == 'nt':
14
- from . import process_poller
14
+ from . import get_process_list
15
15
 
16
16
 
17
17
  def is_command_exists(cmd: str) -> bool:
@@ -265,8 +265,8 @@ def match_pattern_against_running_processes_cmdlines(
265
265
  """
266
266
 
267
267
  # Get the list of all the currently running processes.
268
- get_process_list = process_poller.GetProcessList(get_method='pywin32', connect_on_init=True)
269
- processes = get_process_list.get_processes(as_dict=False)
268
+ get_process_list_instance = get_process_list.GetProcessList(get_method='pywin32', connect_on_init=True)
269
+ processes = get_process_list_instance.get_processes(as_dict=False)
270
270
 
271
271
  # Iterate through all the current process, while fetching executable file 'name' and the command line.
272
272
  # Name is always populated, while command line is not.
File without changes
File without changes
@@ -0,0 +1,95 @@
1
+ from typing import Union, Literal
2
+ import threading
3
+ import time
4
+
5
+ from ... import get_process_list
6
+
7
+
8
+ class PollerPsutilPywin32Dll:
9
+ """
10
+ The class is responsible for getting the list of opened processes by using mentioned libraries.
11
+ """
12
+ def __init__(
13
+ self,
14
+ interval_seconds: Union[int, float] = 0,
15
+ process_get_method: Literal[
16
+ 'poll_psutil',
17
+ 'poll_pywin32',
18
+ 'poll_process_dll'
19
+ ] = 'poll_process_dll',
20
+ process_queue=None
21
+ ):
22
+ """
23
+ :param interval_seconds: works only for pollers, float, how many seconds to wait between each cycle.
24
+ Default is 0, which means that the polling will be as fast as possible.
25
+
26
+ Basically, you want it to be '0' if you want to get the most recent processes.
27
+ Any polling by itself takes time, so if you want to get the most recent processes, you want to do it as fast
28
+ as possible.
29
+ :param process_get_method: str. Default is 'process_dll'. Available:
30
+ 'poll_psutil': Poller, Get the list of processes by 'psutil' library. Resource intensive and slow.
31
+ 'poll_pywin32': Poller, processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
32
+ 'poll_process_dll'. Poller, Not resource intensive and fast. Probably works only in Windows 10 x64.
33
+ 'trace_sysmon_etw': Tracer, Get the list of processes with running SysMon by ETW - Event Tracing.
34
+ In this case 'interval_seconds' is irrelevant, since the ETW is real-time.
35
+ Steps we take:
36
+ 1. Check if SysMon is Running. If not, check if the executable exists in specified
37
+ location and start it as a service.
38
+ 2. Start the "Microsoft-Windows-Sysmon" ETW session.
39
+ 3. Take a snapshot of current processes and their CMDs with psutil and store it in a dict.
40
+ 4. Each new process creation from ETW updates the dict.
41
+ 'trace_event_log': Get the list of processes by subscribing to the Windows Event Log.
42
+ Log Channel: Security, Event ID: 4688.
43
+ We enable the necessary prerequisites in registry and subscribe to the event.
44
+ :param process_queue: Queue. The queue to put the processes in. If None, the processes will not be put in the
45
+ queue.
46
+ """
47
+
48
+ self.interval_seconds: Union[int, float] = interval_seconds
49
+ self.process_get_method = process_get_method.replace('poll_', '')
50
+ self.process_queue = process_queue
51
+
52
+ # noinspection PyTypeChecker
53
+ self.poller_instance = get_process_list.GetProcessList(get_method=self.process_get_method)
54
+
55
+ self._processes = {}
56
+
57
+ def start(self):
58
+ """
59
+ Start the poller.
60
+ """
61
+
62
+ thread = threading.Thread(target=self.emit_loop)
63
+ thread.daemon = True
64
+ thread.start()
65
+
66
+ def emit_loop(self):
67
+ """
68
+ Get the list of processes.
69
+ """
70
+
71
+ self.poller_instance.connect()
72
+
73
+ while True:
74
+ current_processes = self.poller_instance.get_processes(as_dict=True)
75
+
76
+ # Remove Command lines that contains only numbers, since they are useless.
77
+ for pid, process_info in current_processes.items():
78
+ if process_info['cmdline'].isnumeric():
79
+ current_processes[pid]['cmdline'] = str()
80
+ elif process_info['cmdline'] == 'Error':
81
+ current_processes[pid]['cmdline'] = str()
82
+
83
+ # This loop is essential for keeping the command lines.
84
+ # When the process unloads from memory, the last polling will have only pid and executable name, but not
85
+ # the command line. This loop will keep the command line from the previous polling if this happens.
86
+ for pid, process_info in current_processes.items():
87
+ if pid in self._processes:
88
+ if self._processes[pid]['name'] == current_processes[pid]['name']:
89
+ if current_processes[pid]['cmdline'] == '':
90
+ current_processes[pid]['cmdline'] = self._processes[pid]['cmdline']
91
+ self._processes.update(current_processes)
92
+
93
+ self.process_queue.put(self._processes)
94
+
95
+ time.sleep(self.interval_seconds)