Skip to content

Commit cdf0121

Browse files
committed
Enhance deduplication to consider PID and PPNAME
- Update deduplication unique key to include: name + UID + PID + PPNAME - Same process with different PID or parent is now tracked separately - Captures process lifecycle and parent relationship changes - Increases entries from ~2K to ~4K in typical datasets (more accurate tracking)
1 parent 340cd1c commit cdf0121

1 file changed

Lines changed: 25 additions & 21 deletions

File tree

src/sysdiagnose/analysers/ps_everywhere.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def __extract_ps_base_file(self) -> Generator[dict, None, None]:
272272
module=self.module_name,
273273
data={'source': entity_type, 'uid': uid, 'pid': pid, 'ppid': ppid, 'ppname': ppname}
274274
)
275-
if self.add_if_full_command_is_not_in_set(ps_event.message, ps_event.datetime, uid):
275+
if self.add_if_full_command_is_not_in_set(ps_event.message, ps_event.datetime, uid, pid, ppname):
276276
yield ps_event.to_dict()
277277
except Exception as e:
278278
logger.exception(f"ERROR while extracting {entity_type} file. {e}")
@@ -297,7 +297,7 @@ def __extract_ps_thread_file(self) -> Generator[dict, None, None]:
297297
module=self.module_name,
298298
data={'source': entity_type, 'uid': uid, 'pid': pid, 'ppid': ppid, 'ppname': ppname}
299299
)
300-
if self.add_if_full_command_is_not_in_set(ps_event.message, ps_event.datetime, uid):
300+
if self.add_if_full_command_is_not_in_set(ps_event.message, ps_event.datetime, uid, pid, ppname):
301301
yield ps_event.to_dict()
302302
except Exception as e:
303303
logger.exception(f"ERROR while extracting {entity_type} file. {e}")
@@ -322,7 +322,7 @@ def __extract_ps_spindump_nosymbols_file(self) -> Generator[dict, None, None]:
322322
# Spindump has a direct 'parent' field with the parent process name
323323
ppname = p.get('parent')
324324

325-
if self.add_if_full_command_is_not_in_set(self._strip_flags(process_name), event_datetime, uid):
325+
if self.add_if_full_command_is_not_in_set(self._strip_flags(process_name), event_datetime, uid, pid, ppname):
326326
yield Event(
327327
datetime=event_datetime,
328328
message=self._strip_flags(process_name),
@@ -334,7 +334,7 @@ def __extract_ps_spindump_nosymbols_file(self) -> Generator[dict, None, None]:
334334
for t in p['threads']:
335335
try:
336336
thread_name = f"{self._strip_flags(process_name)}::{t['thread_name']}"
337-
if self.add_if_full_command_is_not_in_set(thread_name, event_datetime, uid):
337+
if self.add_if_full_command_is_not_in_set(thread_name, event_datetime, uid, pid, ppname):
338338
yield Event(
339339
datetime=event_datetime,
340340
message=self._strip_flags(thread_name),
@@ -391,7 +391,7 @@ def __extract_ps_logarchive(self) -> Generator[dict, None, None]:
391391
# Handle the case where extracted_process is a list of paths
392392
if isinstance(extracted_process, list):
393393
for proc_path in extracted_process:
394-
if self.add_if_full_command_is_not_in_set(self._strip_flags(proc_path), p_datetime, None):
394+
if self.add_if_full_command_is_not_in_set(self._strip_flags(proc_path), p_datetime, None, None, None):
395395
yield Event(
396396
p_datetime,
397397
message=self._strip_flags(proc_path),
@@ -401,7 +401,7 @@ def __extract_ps_logarchive(self) -> Generator[dict, None, None]:
401401
).to_dict()
402402
else:
403403
# Handle the case where it's a single string
404-
if self.add_if_full_command_is_not_in_set(self._strip_flags(extracted_process), p_datetime, None):
404+
if self.add_if_full_command_is_not_in_set(self._strip_flags(extracted_process), p_datetime, None, None, None):
405405
yield Event(
406406
datetime=p_datetime,
407407
message=self._strip_flags(extracted_process),
@@ -411,7 +411,7 @@ def __extract_ps_logarchive(self) -> Generator[dict, None, None]:
411411
).to_dict()
412412

413413
# Process the original process name
414-
if self.add_if_full_command_is_not_in_set(self._strip_flags(p['data']['process']), p_datetime, euid):
414+
if self.add_if_full_command_is_not_in_set(self._strip_flags(p['data']['process']), p_datetime, euid, pid, None):
415415
yield Event(
416416
datetime=p_datetime,
417417
message=self._strip_flags(p['data']['process']),
@@ -431,7 +431,7 @@ def __extract_ps_uuid2path(self) -> Generator[dict, None, None]:
431431
entity_type = 'uuid2path'
432432
try:
433433
for p in UUID2PathParser(self.config, self.case_id).get_result().values():
434-
if self.add_if_full_command_is_not_in_set(self._strip_flags(p), self.sysdiagnose_creation_datetime, None):
434+
if self.add_if_full_command_is_not_in_set(self._strip_flags(p), self.sysdiagnose_creation_datetime, None, None, None):
435435
yield Event(
436436
datetime=self.sysdiagnose_creation_datetime,
437437
message=self._strip_flags(p),
@@ -456,7 +456,7 @@ def __extract_ps_taskinfo(self) -> Generator[dict, None, None]:
456456

457457
p_datetime = datetime.fromisoformat(p['datetime'])
458458
pid = p['data'].get('pid')
459-
if self.add_if_full_path_is_not_in_set(self._strip_flags(p['data']['name']), p_datetime, None):
459+
if self.add_if_full_path_is_not_in_set(self._strip_flags(p['data']['name']), p_datetime, None, pid, None):
460460
yield Event(
461461
datetime=p_datetime,
462462
message=self._strip_flags(p['data']['name']),
@@ -468,7 +468,7 @@ def __extract_ps_taskinfo(self) -> Generator[dict, None, None]:
468468
for t in p['data']['threads']:
469469
try:
470470
thread_name = f"{self._strip_flags(p['data']['name'])}::{t['thread name']}"
471-
if self.add_if_full_path_is_not_in_set(thread_name, p_datetime, None):
471+
if self.add_if_full_path_is_not_in_set(thread_name, p_datetime, None, pid, None):
472472
yield Event(
473473
p_datetime,
474474
message=thread_name,
@@ -492,7 +492,7 @@ def __extract_ps_remotectl_dumpstate(self) -> Generator[dict, None, None]:
492492
remotectl_dumpstate_json = RemotectlDumpstateParser(self.config, self.case_id).get_result()
493493
if remotectl_dumpstate_json:
494494
for p in remotectl_dumpstate_json['Local device']['Services']:
495-
if self.add_if_full_path_is_not_in_set(self._strip_flags(p), self.sysdiagnose_creation_datetime, None):
495+
if self.add_if_full_path_is_not_in_set(self._strip_flags(p), self.sysdiagnose_creation_datetime, None, None, None):
496496
yield Event(
497497
datetime=self.sysdiagnose_creation_datetime,
498498
message=self._strip_flags(p),
@@ -513,7 +513,7 @@ def __extract_ps_logdata_statistics(self) -> Generator[dict, None, None]:
513513
try:
514514
for p in LogDataStatisticsParser(self.config, self.case_id).get_result():
515515
p_datetime = datetime.fromisoformat(p['datetime'])
516-
if self.add_if_full_command_is_not_in_set(self._strip_flags(p['data']['process']), p_datetime, None):
516+
if self.add_if_full_command_is_not_in_set(self._strip_flags(p['data']['process']), p_datetime, None, None, None):
517517
yield Event(
518518
datetime=p_datetime,
519519
message=self._strip_flags(p['data']['process']),
@@ -535,7 +535,7 @@ def __extract_ps_logdata_statistics_txt(self) -> Generator[dict, None, None]:
535535
try:
536536
for p in LogDataStatisticsTxtParser(self.config, self.case_id).get_result():
537537
p_datetime = datetime.fromisoformat(p['datetime'])
538-
if self.add_if_full_path_is_not_in_set(self._strip_flags(p['data']['process']), p_datetime, None):
538+
if self.add_if_full_path_is_not_in_set(self._strip_flags(p['data']['process']), p_datetime, None, None, None):
539539
yield Event(
540540
datetime=p_datetime,
541541
message=self._strip_flags(p['data']['process']),
@@ -547,19 +547,21 @@ def __extract_ps_logdata_statistics_txt(self) -> Generator[dict, None, None]:
547547
except Exception as e:
548548
logger.exception(f"ERROR while extracting {entity_type}. {e}")
549549

550-
def add_if_full_path_is_not_in_set(self, name: str, timestamp: Optional[datetime] = None, uid: Optional[int] = None) -> bool:
550+
def add_if_full_path_is_not_in_set(self, name: str, timestamp: Optional[datetime] = None, uid: Optional[int] = None, pid: Optional[int] = None, ppname: Optional[str] = None) -> bool:
551551
"""
552552
Ensures that a process path is unique before adding it to the shared set,
553553
with time-based deduplication: only keep duplicates if they occur more than 1 hour apart.
554-
UID is considered part of the uniqueness - same process with different UID is treated as separate.
554+
UID, PID, and PPNAME are considered part of the uniqueness - same process with different values is treated as separate.
555555
556556
:param name: Process path name
557557
:param timestamp: Timestamp of the process occurrence (optional, for time-based deduplication)
558558
:param uid: User ID of the process (optional, considered in uniqueness check)
559+
:param pid: Process ID (optional, considered in uniqueness check)
560+
:param ppname: Parent process name (optional, considered in uniqueness check)
559561
:return: True if the process was not in the set or last seen > 1 hour ago, False otherwise.
560562
"""
561-
# Create a unique key that includes both name and UID
562-
unique_key = f"{name}|uid:{uid}"
563+
# Create a unique key that includes name, UID, PID, and PPNAME
564+
unique_key = f"{name}|uid:{uid}|pid:{pid}|ppname:{ppname}"
563565

564566
# If no timestamp provided, use old behavior (always check for duplicates)
565567
if timestamp is None:
@@ -585,19 +587,21 @@ def add_if_full_path_is_not_in_set(self, name: str, timestamp: Optional[datetime
585587
self.process_last_seen[unique_key] = timestamp
586588
return True
587589

588-
def add_if_full_command_is_not_in_set(self, name: str, timestamp: Optional[datetime] = None, uid: Optional[int] = None) -> bool:
590+
def add_if_full_command_is_not_in_set(self, name: str, timestamp: Optional[datetime] = None, uid: Optional[int] = None, pid: Optional[int] = None, ppname: Optional[str] = None) -> bool:
589591
"""
590592
Ensures that a process command is unique before adding it to the shared set,
591593
with time-based deduplication: only keep duplicates if they occur more than 1 hour apart.
592-
UID is considered part of the uniqueness - same process with different UID is treated as separate.
594+
UID, PID, and PPNAME are considered part of the uniqueness - same process with different values is treated as separate.
593595
594596
:param name: Process command name
595597
:param timestamp: Timestamp of the process occurrence (optional, for time-based deduplication)
596598
:param uid: User ID of the process (optional, considered in uniqueness check)
599+
:param pid: Process ID (optional, considered in uniqueness check)
600+
:param ppname: Parent process name (optional, considered in uniqueness check)
597601
:return: True if the process was not in the set or last seen > 1 hour ago, False otherwise.
598602
"""
599-
# Create a unique key that includes both name and UID
600-
unique_key = f"{name}|uid:{uid}"
603+
# Create a unique key that includes name, UID, PID, and PPNAME
604+
unique_key = f"{name}|uid:{uid}|pid:{pid}|ppname:{ppname}"
601605

602606
# If no timestamp provided, use old behavior (always check for duplicates)
603607
if timestamp is None:

0 commit comments

Comments
 (0)