From 3dc0310a9faa7b01a9ae696307ed724c2ef94252 Mon Sep 17 00:00:00 2001 From: ankitkumar25-rk Date: Thu, 18 Jun 2026 10:50:03 +0530 Subject: [PATCH 01/47] created table for metrics and collected them in database --- config.py | 2 +- db.py | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/config.py b/config.py index be02e34..c4c8d21 100644 --- a/config.py +++ b/config.py @@ -1 +1 @@ -"""CogniOS configuration.""" +DB_PATH = "cognios_telemetry.db" \ No newline at end of file diff --git a/db.py b/db.py index e5c2395..8f84e63 100644 --- a/db.py +++ b/db.py @@ -1 +1,107 @@ -"""Shared database schema and read/write interface.""" +import sqlite3 +from config import DB_PATH + +db_path = DB_PATH + +# Creating table for all the metrics collected from the system + +def create_connection(db_path): + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + cursor.execute('''CREATE TABLE IF NOT EXISTS layer1_sys ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL, + + --CPU Metrics + cpu_usage_percent REAL, + cpu_freq REAL, + cpu_user_time REAL, + cpu_system_time REAL, + cpu_idle_time REAL, + cpu_iowait_time REAL, + cpu_busy_time REAL, + cpu_ctx_switches REAL, + + --Memory Metrics + memory_percent REAL, + memory_used INTEGER, + memory_available INTEGER, + memory_cached INTEGER, + memory_buffers INTEGER, + swap_percent REAL, + swap_sin INTEGER, + swap_sout INTEGER + + --Disk Metrics + disk_usage_percent REAL, + disk_read_mb_s REAL, + disk_write_mb_s REAL, + disk_read_time INTEGER, + disk_write_time INTEGER, + + --Network Metrics + net_rate_mb_s REAL, + net_bytes_sent INTEGER, + net_bytes_recv INTEGER, + net_packets_sent INTEGER, + net_packets_recv INTEGER, + net_errs INTEGER, + net_drops INTEGER, + + --System Metrics + load_avg_1 REAL, + load_avg_5 REAL, + load_avg_15 REAL, + total_processes INTEGER, + running_processes INTEGER, + sleeping_processes INTEGER, + zombie_processes INTEGER, + + --Hardware Metrics + avg_temp REAL, + max_temp REAL, + battery_percent REAL, + ) + ''') + + conn.commit() + return conn + +# Function to write the collected metrics into the database + +def write_layer1(conn, timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cpu_system_time, cpu_idle_time, cpu_iowait_time, cpu_busy_time, cpu_ctx_switches, memory_percent, memory_used, memory_available, memory_cached, memory_buffers, swap_percent, swap_sin, swap_sout, disk_usage_percent, disk_read_mb_s, disk_write_mb_s, disk_read_time, disk_write_time, load_avg_1, load_avg_5, load_avg_15, total_processes, running_processes, sleeping_processes, zombie_processes, avg_temp, max_temp, battery_percent): + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO layer1_sys (timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cpu_system_time, cpu_idle_time, cpu_iowait_time, cpu_busy_time, cpu_ctx_switches, memory_percent, memory_used, memory_available, memory_cached, memory_buffers, swap_percent, swap_sin, swap_sout, disk_usage_percent, disk_read_mb_s, disk_write_mb_s, disk_read_time, disk_write_time, load_avg_1, load_avg_5, load_avg_15, total_processes, running_processes, sleeping_processes, zombie_processes, avg_temp, max_temp, battery_percent) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', (timestamp, + cpu_usage_percent, + cpu_freq, cpu_user_time, + cpu_system_time, + cpu_idle_time, + cpu_iowait_time, + cpu_busy_time, + cpu_ctx_switches, + memory_percent, + memory_used, + memory_available, + memory_cached, + memory_buffers, + swap_percent, + swap_sin, + swap_sout, + disk_usage_percent, + disk_read_mb_s, + disk_write_mb_s, + disk_read_time, + disk_write_time, + load_avg_1, + load_avg_5, + load_avg_15, + total_processes, + running_processes, + sleeping_processes, + zombie_processes, + avg_temp, + max_temp, + battery_percent)) From 6e94438913b7fe3b34992100f8d406cdecc5a4bc Mon Sep 17 00:00:00 2001 From: ankitkumar25-rk Date: Thu, 18 Jun 2026 12:01:10 +0530 Subject: [PATCH 02/47] added CPU metrics collections --- collectors/layer1_system.py | 38 ++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/collectors/layer1_system.py b/collectors/layer1_system.py index 291e147..0619501 100644 --- a/collectors/layer1_system.py +++ b/collectors/layer1_system.py @@ -1 +1,37 @@ -"""Layer 1 system telemetry collection.""" +import psutil +import time +from datetime import datetime,timezone + +def collect_layer1_metrics(): + timestamp = datetime.now(timezone.utc).isoformat() + + cpu_usage_percent = psutil.cpu_percent(interval=None) + cpu_times = psutil.cpu_times() + cpu_user_time = cpu_times.user + cpu_system_time = cpu_times.system + cpu_idle_time = cpu_times.idle + cpu_iowait_time = getattr(cpu_times, 'iowait', None) # iowait for Linux only + cpu_busy_time = 100.0 - cpu_usage_percent if cpu_usage_percent is not None else None + + try: + freq = psutil.cpu_freq() + cpu_current_freq = freq.current if freq else None + except Exception: + cpu_current_freq = None + + try: + cpu_ctx_switches = psutil.cpu_stats().ctx_switches + except Exception: + cpu_ctx_switches = None + + return { + "timestamp": timestamp, + "cpu_usage_percent": cpu_usage_percent, + "cpu_current_freq": cpu_current_freq, + "cpu_user_time": cpu_user_time, + "cpu_system_time": cpu_system_time, + "cpu_idle_time": cpu_idle_time, + "cpu_iowait_time": cpu_iowait_time, + "cpu_busy_time": cpu_busy_time, + "cpu_ctx_switches": cpu_ctx_switches + } \ No newline at end of file From 9d226701a3f0a936fd7ec22e5ae60cbafa771157 Mon Sep 17 00:00:00 2001 From: ankitkumar25-rk Date: Thu, 18 Jun 2026 12:08:34 +0530 Subject: [PATCH 03/47] cmnts --- collectors/layer1_system.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/collectors/layer1_system.py b/collectors/layer1_system.py index 0619501..2bce363 100644 --- a/collectors/layer1_system.py +++ b/collectors/layer1_system.py @@ -2,9 +2,13 @@ import time from datetime import datetime,timezone +# collects metriics + def collect_layer1_metrics(): timestamp = datetime.now(timezone.utc).isoformat() + + cpu_usage_percent = psutil.cpu_percent(interval=None) cpu_times = psutil.cpu_times() cpu_user_time = cpu_times.user @@ -13,6 +17,8 @@ def collect_layer1_metrics(): cpu_iowait_time = getattr(cpu_times, 'iowait', None) # iowait for Linux only cpu_busy_time = 100.0 - cpu_usage_percent if cpu_usage_percent is not None else None +# freq , ctx_switches are not available on all systems + try: freq = psutil.cpu_freq() cpu_current_freq = freq.current if freq else None From d1623bd2b29fc7eef6233659f4834e5ad63fbeb1 Mon Sep 17 00:00:00 2001 From: NikhByte Date: Thu, 18 Jun 2026 15:12:29 +0530 Subject: [PATCH 04/47] added more metrics --- collectors/layer1_system.py | 120 +++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 15 deletions(-) diff --git a/collectors/layer1_system.py b/collectors/layer1_system.py index 2bce363..f30362a 100644 --- a/collectors/layer1_system.py +++ b/collectors/layer1_system.py @@ -1,34 +1,91 @@ import psutil import time from datetime import datetime,timezone - # collects metriics - def collect_layer1_metrics(): timestamp = datetime.now(timezone.utc).isoformat() - - - cpu_usage_percent = psutil.cpu_percent(interval=None) cpu_times = psutil.cpu_times() cpu_user_time = cpu_times.user cpu_system_time = cpu_times.system cpu_idle_time = cpu_times.idle cpu_iowait_time = getattr(cpu_times, 'iowait', None) # iowait for Linux only - cpu_busy_time = 100.0 - cpu_usage_percent if cpu_usage_percent is not None else None - + cpu_busy_time=cpu_user_time + cpu_system_time + vmem=psutil.virtual_memory() + memory_percent=vmem.percent + memory_used=vmem.used + memory_available=vmem.available + memory_cached=getattr(vmem, 'cached', 0) #cached and buffers not available in the mac and windows + memory_buffers=getattr(vmem, 'buffers', 0) + swap=psutil.swap_memory() + swap_percent=swap.percent + swap_sin=swap.sin + swap_sout=swap.sout + disk_usage=psutil.disk_usage('/').percent + disk_io=psutil.disk_io_counters() + disk_read=disk_io.read_count / 1024 if disk_io else 0 + disk_write=disk_io.write_count / 1024 if disk_io else 0 + disk_read_time=disk_io.read_time if disk_io else 0 + disk_write_time=disk_io.write_time if disk_io else 0 + net_io=psutil.net_io_counters() + net_bytes_sent=net_io.bytes_sent if net_io else 0 + net_bytes_received=net_io.bytes_recv if net_io else 0 + net_packets_sent=net_io.packets_sent if net_io else 0 + net_packets_received=net_io.packets_recv if net_io else 0 + net_errs=(net_io.errin + net_io.errout) if net_io else 0 + net_drops=(net_io.dropin + net_io.dropout) if net_io else 0 + try: + load_avg1, load_avg5, load_avg15=psutil.getloadavg() + except AttributeError: + load_avg1=load_avg5=load_avg15=None + #initialising the process_type_count variables + total_processes=0 + running_processes=0 + sleeping_processes=0 + zombie_processes=0 + for p in psutil.process_iter(['status']): + total_processes+= 1 + try: + status=p.info['status'] + if status==psutil.STATUS_RUNNING: + running_processes+=1 + elif status==psutil.STATUS_SLEEPING: + sleeping_processes+=1 + elif status==psutil.STATUS_ZOMBIE: + zombie_processes+=1 + except (psutil.NoSuchProcess,psutil.AccessDenied): + pass + # putting temp vars in conditions sensors might not be available for all the syst + try: + temps=psutil.sensors_temperatures() + if 'coretemp' in temps and temps['coretemp']: + avg_temp=temps['coretemp'][0].current + max_temp=temps['coretemp'][0].high + else: + avg_temp=None + max_temp=None + except Exception: + avg_temp=None + max_temp=None + try: + battery=psutil.sensors_battery() + if battery: + battery_percent=battery.percent + else: + battery_percent=None + except Exception: + battery_percent=None # freq , ctx_switches are not available on all systems - try: - freq = psutil.cpu_freq() - cpu_current_freq = freq.current if freq else None + freq=psutil.cpu_freq() + cpu_current_freq=freq.current if freq else None except Exception: - cpu_current_freq = None + cpu_current_freq=None try: - cpu_ctx_switches = psutil.cpu_stats().ctx_switches + cpu_ctx_switches=psutil.cpu_stats().ctx_switches except Exception: - cpu_ctx_switches = None + cpu_ctx_switches=None return { "timestamp": timestamp, @@ -39,5 +96,38 @@ def collect_layer1_metrics(): "cpu_idle_time": cpu_idle_time, "cpu_iowait_time": cpu_iowait_time, "cpu_busy_time": cpu_busy_time, - "cpu_ctx_switches": cpu_ctx_switches - } \ No newline at end of file + "cpu_ctx_switches": cpu_ctx_switches, + "memory_percent": memory_percent, + "memory_used":memory_used, + "memory_available":memory_available, + "memory_cached":memory_cached, + "memory_buffers":memory_buffers, + "swap_percent":swap_percent, + "swap_sin":swap_sin, + "swap_sout":swap_sout, + "disk_usage_percent":disk_usage, + "disk_read":disk_read, + "disk_write":disk_write, + "disk_read_time":disk_read_time, + "disk_write_time":disk_write_time, + "net_bytes_sent":net_bytes_sent, + "net_bytes_received":net_bytes_received, + "net_packets_sent":net_packets_sent, + "net_packets_received":net_packets_received, + "net_errs":net_errs, + "net_drops":net_drops, + "load_avg1":load_avg1, + "load_avg5":load_avg5, + "load_avg15":load_avg15, + "total_processes":total_processes, + "running_processes":running_processes, + "sleeping_processes":sleeping_processes, + "zombie_processes":zombie_processes, + "avg_temp":avg_temp, + "max_temp":max_temp, + "battery_percent":battery_percent + } +if __name__ == "__main__": + import json + metrics = collect_layer1_metrics() + print(json.dumps(metrics, indent=4)) \ No newline at end of file From 9524c33fb70af210aeb76435a1d5b47de3169de8 Mon Sep 17 00:00:00 2001 From: ankitkumar25-rk Date: Thu, 18 Jun 2026 17:14:48 +0530 Subject: [PATCH 05/47] resolve all issue related to exception handling, disk rate.... --- collectors/layer1_system.py | 139 ++++++++++++++++++++++++++---------- utils/helpers.py | 9 +++ 2 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 utils/helpers.py diff --git a/collectors/layer1_system.py b/collectors/layer1_system.py index f30362a..967a8d2 100644 --- a/collectors/layer1_system.py +++ b/collectors/layer1_system.py @@ -1,9 +1,23 @@ import psutil import time from datetime import datetime,timezone +from utils.helpers import rate_mb_s + +_last = { + "time": None, + "disk_read_bytes": None, + "disk_write_bytes": None, + "net_bytes_sent": None, + "net_bytes_recv": None, +} + + # collects metriics def collect_layer1_metrics(): + now = time.time() timestamp = datetime.now(timezone.utc).isoformat() + + # CPU Metrics cpu_usage_percent = psutil.cpu_percent(interval=None) cpu_times = psutil.cpu_times() cpu_user_time = cpu_times.user @@ -11,22 +25,63 @@ def collect_layer1_metrics(): cpu_idle_time = cpu_times.idle cpu_iowait_time = getattr(cpu_times, 'iowait', None) # iowait for Linux only cpu_busy_time=cpu_user_time + cpu_system_time + + # freq , ctx_switches are not available on all systems + try: + freq=psutil.cpu_freq() + cpu_current_freq=freq.current if freq else None + except Exception: + cpu_current_freq=None + + try: + cpu_ctx_switches=psutil.cpu_stats().ctx_switches + except Exception: + cpu_ctx_switches=None + + # Memory Metrics + vmem=psutil.virtual_memory() memory_percent=vmem.percent memory_used=vmem.used memory_available=vmem.available - memory_cached=getattr(vmem, 'cached', 0) #cached and buffers not available in the mac and windows - memory_buffers=getattr(vmem, 'buffers', 0) + memory_cached=getattr(vmem, 'cached', None) #cached and buffers not available in the mac and windows + memory_buffers=getattr(vmem, 'buffers', None) # None- like value nahi mili aur 0- ek valid value hai + swap=psutil.swap_memory() swap_percent=swap.percent swap_sin=swap.sin swap_sout=swap.sout - disk_usage=psutil.disk_usage('/').percent - disk_io=psutil.disk_io_counters() - disk_read=disk_io.read_count / 1024 if disk_io else 0 - disk_write=disk_io.write_count / 1024 if disk_io else 0 - disk_read_time=disk_io.read_time if disk_io else 0 - disk_write_time=disk_io.write_time if disk_io else 0 + + # Disk Metrics + + # same reason, in windows "/" is not valid + try: + disk_usage=psutil.disk_usage('/').percent + except Exception: + disk_usage=None + + try: + disk_io = psutil.disk_io_counters() + except Exception: + disk_io=None + + disk_read=None + disk_write=None + disk_read_time=None + disk_write_time=None + + if disk_io is not None: + elapsed_time = (now - _last["time"]) if _last["time"] else 0 + disk_read = rate_mb_s(disk_io.read_bytes, _last["disk_read_bytes"], elapsed_time) + disk_write = rate_mb_s(disk_io.write_bytes, _last["disk_write_bytes"], elapsed_time) + disk_read_time = getattr(disk_io, 'read_time', None) + disk_write_time = getattr(disk_io, 'write_time', None) + + _last["disk_read_bytes"] = disk_io.read_bytes + _last["disk_write_bytes"] = disk_io.write_bytes + + + # Network Metrics net_io=psutil.net_io_counters() net_bytes_sent=net_io.bytes_sent if net_io else 0 net_bytes_received=net_io.bytes_recv if net_io else 0 @@ -34,15 +89,30 @@ def collect_layer1_metrics(): net_packets_received=net_io.packets_recv if net_io else 0 net_errs=(net_io.errin + net_io.errout) if net_io else 0 net_drops=(net_io.dropin + net_io.dropout) if net_io else 0 + + net_rate_mb_s=None + if _last["time"]: + elapsed_time = now - _last["time"] + sent_rate = rate_mb_s(net_bytes_sent, _last["net_bytes_sent"], elapsed_time) + recv_rate = rate_mb_s(net_bytes_received, _last["net_bytes_recv"], elapsed_time) + if sent_rate is not None and recv_rate is not None: + net_rate_mb_s = sent_rate + recv_rate + + _last["net_bytes_sent"] = net_bytes_sent + _last["net_bytes_recv"] = net_bytes_received + _last["time"] = now + + + #b Load Average Metrics try: load_avg1, load_avg5, load_avg15=psutil.getloadavg() - except AttributeError: + + # except AttributeError: # catches specific error when getloadavg is not support + except Exception: load_avg1=load_avg5=load_avg15=None + #initialising the process_type_count variables - total_processes=0 - running_processes=0 - sleeping_processes=0 - zombie_processes=0 + total_processes, running_processes, sleeping_processes, zombie_processes = 0, 0, 0, 0 for p in psutil.process_iter(['status']): total_processes+= 1 try: @@ -55,37 +125,25 @@ def collect_layer1_metrics(): zombie_processes+=1 except (psutil.NoSuchProcess,psutil.AccessDenied): pass - # putting temp vars in conditions sensors might not be available for all the syst + + + # Temperature and Battery Metrics (if available) + temp_avg, temp_max = None, None try: temps=psutil.sensors_temperatures() - if 'coretemp' in temps and temps['coretemp']: - avg_temp=temps['coretemp'][0].current - max_temp=temps['coretemp'][0].high - else: - avg_temp=None - max_temp=None + all_temps=[t.current for sensors in temps.values() for t in sensors] + if all_temps: + temp_avg = sum(all_temps) / len(all_temps) + temp_max = max(all_temps) except Exception: - avg_temp=None - max_temp=None + pass + battery_percent=None try: battery=psutil.sensors_battery() if battery: battery_percent=battery.percent - else: - battery_percent=None - except Exception: - battery_percent=None -# freq , ctx_switches are not available on all systems - try: - freq=psutil.cpu_freq() - cpu_current_freq=freq.current if freq else None except Exception: - cpu_current_freq=None - - try: - cpu_ctx_switches=psutil.cpu_stats().ctx_switches - except Exception: - cpu_ctx_switches=None + pass return { "timestamp": timestamp, @@ -97,6 +155,7 @@ def collect_layer1_metrics(): "cpu_iowait_time": cpu_iowait_time, "cpu_busy_time": cpu_busy_time, "cpu_ctx_switches": cpu_ctx_switches, + "memory_percent": memory_percent, "memory_used":memory_used, "memory_available":memory_available, @@ -105,17 +164,20 @@ def collect_layer1_metrics(): "swap_percent":swap_percent, "swap_sin":swap_sin, "swap_sout":swap_sout, + "disk_usage_percent":disk_usage, "disk_read":disk_read, "disk_write":disk_write, "disk_read_time":disk_read_time, "disk_write_time":disk_write_time, + "net_bytes_sent":net_bytes_sent, "net_bytes_received":net_bytes_received, "net_packets_sent":net_packets_sent, "net_packets_received":net_packets_received, "net_errs":net_errs, "net_drops":net_drops, + "load_avg1":load_avg1, "load_avg5":load_avg5, "load_avg15":load_avg15, @@ -123,10 +185,11 @@ def collect_layer1_metrics(): "running_processes":running_processes, "sleeping_processes":sleeping_processes, "zombie_processes":zombie_processes, - "avg_temp":avg_temp, - "max_temp":max_temp, + "avg_temp":temp_avg, + "max_temp":temp_max, "battery_percent":battery_percent } + if __name__ == "__main__": import json metrics = collect_layer1_metrics() diff --git a/utils/helpers.py b/utils/helpers.py new file mode 100644 index 0000000..7a92516 --- /dev/null +++ b/utils/helpers.py @@ -0,0 +1,9 @@ + + +def rate_mb_s(current_bytes, last_bytes, elapsed_sec): + + if last_bytes is None or elapsed_sec <= 0: + return None + # how many bytes have been sent/received since the last check, and convert to MB/s by dividing by 1 MB + delta_bytes = current_bytes - last_bytes + return (delta_bytes / elapsed_sec) / (1024 * 1024) \ No newline at end of file From 000d7c52df75204d529a850809bd0e4124b58ed4 Mon Sep 17 00:00:00 2001 From: ankitkumar25-rk Date: Thu, 18 Jun 2026 17:20:10 +0530 Subject: [PATCH 06/47] cmnt --- collectors/layer1_system.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/collectors/layer1_system.py b/collectors/layer1_system.py index 967a8d2..db42ef3 100644 --- a/collectors/layer1_system.py +++ b/collectors/layer1_system.py @@ -3,6 +3,7 @@ from datetime import datetime,timezone from utils.helpers import rate_mb_s +# a dictionary to store previous values _last = { "time": None, "disk_read_bytes": None, @@ -106,7 +107,7 @@ def collect_layer1_metrics(): #b Load Average Metrics try: load_avg1, load_avg5, load_avg15=psutil.getloadavg() - + # except AttributeError: # catches specific error when getloadavg is not support except Exception: load_avg1=load_avg5=load_avg15=None From 2a79a01fb653af7e5597d062bf177c7be706b884 Mon Sep 17 00:00:00 2001 From: Abhishek Reddy Date: Fri, 19 Jun 2026 18:16:13 +0530 Subject: [PATCH 07/47] Started working in layer2 --- cognios_telemetry.db | Bin 0 -> 16384 bytes collectors/layer2_process.py | 84 +++++++++++++++++++++++++++++ db.py | 100 +++++++++++++++++++++++++++++++++++ test.py | 29 ++++++++++ 4 files changed, 213 insertions(+) create mode 100644 cognios_telemetry.db create mode 100644 test.py diff --git a/cognios_telemetry.db b/cognios_telemetry.db new file mode 100644 index 0000000000000000000000000000000000000000..fe6294922f5e425192b42166d2233613fc7d8a92 GIT binary patch literal 16384 zcmeI2PfXKL9LHbVjS*yMiaf55N{fThKsLV`O_5y6Fivweo6P*zF)t6@AH0L z(*AxI`yyF|;9*e`QVKqe4kLy^JjMtij&2sZZ5T^;wjVPa9e-#!bm~@}ojPzeZ;*Y) z?xzhH5C8%|00;m9AOHk_01!A(0@GuhtG%g|jj$kyIv`7K?Pu88N#fh)2*&%2fflWJ;qdqueyqc@HL!Z z4Z3Rh8oD8TcX+}ee6@cxkNR$Ib=*MCXUSgtsd&3k-bhBfnj zU#U4;c(79Y7$N3)>+(Emkc#VV&v^m3b3{_;0S1MX&)1pM=0wL+q4Bt|ol5eTp zvUg}ZvhDd+uoBL9nmGjYT}>sRZ60@HoY2QC$ah~pF@x8n*&sAVX 0: + calc_read_rate = max(0.0, (read_bytes - prev['read_bytes']) / time_delta) + calc_write_rate = max(0.0, (write_bytes - prev['write_bytes']) / time_delta) + + processed_snapshots.append({ + 'pid': pid, + 'name': info['name'] or 'Unknown', + 'cpu_percent': round(cpu_percent, 2), + 'memory_percent': round(mem_percent, 2), + 'rss_memory': mem_info.rss, + 'vms_memory': mem_info.vms, + 'thread_count': info['num_threads'] or 1, + 'read_bytes_sec': round(calc_read_rate, 2), + 'write_bytes_sec': round(calc_write_rate, 2), + 'status': info['status'] or 'unknown' + }) + + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + continue + + # Slice out the two lists independently + top_cpu = sorted(processed_snapshots, key=lambda x: x['cpu_percent'], reverse=True)[:5] + top_mem = sorted(processed_snapshots, key=lambda x: x['rss_memory'], reverse=True)[:5] + + # Return as a structured tuple + return top_cpu, top_mem, current_states + + +if __name__ == '__main__': + from db import init_db, insert_separated_telemetry + + init_db() + baselines = {} + print("CogniOS Separated Telemetry Daemon started...") + + try: + while True: + top_cpu, top_mem, baselines = collect_process_telemetry(baselines) + + # Pass both lists cleanly to our data layer + insert_separated_telemetry(top_cpu, top_mem) + + print(f"Committed Top 5 CPU and Top 5 Memory snapshots.") + time.sleep(5) + except KeyboardInterrupt: + print("\nDaemon safely terminated.") \ No newline at end of file diff --git a/db.py b/db.py index e5c2395..c5f5bdb 100644 --- a/db.py +++ b/db.py @@ -1 +1,101 @@ """Shared database schema and read/write interface.""" +import sqlite3 +import time + +DB_NAME = "cognios_telemetry.db" + +def init_db(): + """Initializes separate structural tables for CPU and Memory metrics.""" + with sqlite3.connect(DB_NAME) as conn: + cursor = conn.cursor() + + # Table 1: Dedicated CPU telemetry + cursor.execute(""" + CREATE TABLE IF NOT EXISTS top_cpu_telemetry ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp REAL NOT NULL, + pid INTEGER NOT NULL, + name TEXT NOT NULL, + cpu_percent REAL NOT NULL, + memory_percent REAL NOT NULL, + rss_memory INTEGER NOT NULL, + vms_memory INTEGER NOT NULL, + thread_count INTEGER NOT NULL, + read_bytes_sec REAL NOT NULL, + write_bytes_sec REAL NOT NULL, + status TEXT NOT NULL + ) + """) + + # Table 2: Dedicated RAM telemetry + cursor.execute(""" + CREATE TABLE IF NOT EXISTS top_ram_telemetry ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp REAL NOT NULL, + pid INTEGER NOT NULL, + name TEXT NOT NULL, + cpu_percent REAL NOT NULL, + memory_percent REAL NOT NULL, + rss_memory INTEGER NOT NULL, + vms_memory INTEGER NOT NULL, + thread_count INTEGER NOT NULL, + read_bytes_sec REAL NOT NULL, + write_bytes_sec REAL NOT NULL, + status TEXT NOT NULL + ) + """) + conn.commit() + + +def _map_to_rows(process_list, current_timestamp): + """Helper function to transform list of dicts to flat tuples for SQLite.""" + return [ + ( + current_timestamp, + p['pid'], + p['name'], + p['cpu_percent'], + p['memory_percent'], + p['rss_memory'], + p['vms_memory'], + p['thread_count'], + p['read_bytes_sec'], + p['write_bytes_sec'], + p['status'] + ) + for p in process_list + ] + + +def insert_separated_telemetry(top_cpu, top_mem): + """ + Inserts data cleanly into their respective tables. + Even if a process exists in both lists, it is safely recorded + in both metrics tables under the same time block window. + """ + current_timestamp = time.time() + + # Map the dictionaries into raw tuple rows + cpu_rows = _map_to_rows(top_cpu, current_timestamp) + ram_rows = _map_to_rows(top_mem, current_timestamp) + + insert_query = """ + INSERT INTO {} ( + timestamp, pid, name, cpu_percent, memory_percent, + rss_memory, vms_memory, thread_count, read_bytes_sec, + write_bytes_sec, status + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """ + + with sqlite3.connect(DB_NAME) as conn: + cursor = conn.cursor() + + # Write to top_cpu_telemetry table if metrics exist + if cpu_rows: + cursor.executemany(insert_query.format("top_cpu_telemetry"), cpu_rows) + + # Write to top_ram_telemetry table if metrics exist + if ram_rows: + cursor.executemany(insert_query.format("top_ram_telemetry"), ram_rows) + + conn.commit() \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..ec4aad2 --- /dev/null +++ b/test.py @@ -0,0 +1,29 @@ +"Testing Document" +import time +from collectors.layer2_process import collect_process_telemetry +from db import init_db, insert_separated_telemetry + +def test_layer2_pipeline(): + print("Initializing CogniOS Test Database...") + init_db() + + baselines = {} + print("Running initial telemetry sweep (Baseline generation)...") + top_cpu, top_mem, baselines = collect_process_telemetry(baselines) + + # Wait 5 seconds to simulate a real monitoring pulse + print("Pacing for 5 seconds to calculate real delta rates...") + time.sleep(5) + + print("Running second telemetry sweep...") + top_cpu, top_mem, baselines = collect_process_telemetry(baselines) + + print(f"Top CPU Process Count: {len(top_cpu)}") + print(f"Top RAM Process Count: {len(top_mem)}") + + print("Writing captured data to separate SQLite tables...") + insert_separated_telemetry(top_cpu, top_mem) + print("Success! Check your root directory for 'cognios_telemetry.db'.") + +if __name__ == "__main__": + test_layer2_pipeline() \ No newline at end of file From c25737f02848703b27110676f182c8116017651f Mon Sep 17 00:00:00 2001 From: Abhishek Reddy Date: Fri, 19 Jun 2026 18:32:16 +0530 Subject: [PATCH 08/47] minor changes made --- cognios_telemetry.db | Bin 16384 -> 16384 bytes collectors/layer2_process.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cognios_telemetry.db b/cognios_telemetry.db index fe6294922f5e425192b42166d2233613fc7d8a92..b3ec5990b052ae36618b688d9e7999ace6c24530 100644 GIT binary patch delta 769 zcmZo@U~Fh$+`uNlQ^vr*g+GR0h;Kh%*=9w76ux?QR$c~nW@&aHVC3MCcD&W=uf}c~ z!0=r42iryu8f3^gtGnLHZa5 zUDJ2YPf1nqNX;ooEmF{M4+zzCU`qo7`-krJ>qM#^dN_m`fsABusQxbnGE*38rUx^~ zOj%4bbzE~&lS_*7^Bj22fWTS%&tS*JIAk1a0Gl+&_{SC?Lj!737!$}OOAM1HH^@uY z%R+s~1To8@Lihm03`?6GK!zC93@=8Y8BEIPX0Xgm&M!(WE=WyH&PZ{PgL>@($ONE| zT`<*jEV3q^(>sGlPfo~itiyA<#cbH>Q zr|jiS z13}gXIn=Z{fOXE%xf=syz=A&<6#UHI7&^=CmF2miG1!<3(K?rxPXee_6ly5|Dw5H6 delta 698 zcmZo@U~Fh$+`uNl)4{;Mi$8;3mG1^$$7Vr=GQN60R$c~nW>a={7FI?M4r#|*y?%vv z9d9!HPH@goNmcMj%_&GNQqTyE0|L^Yk&yyQYs~o_j#3ru`2FhI$4duzvs|85me48WCrp9$;W7;pTN<2!I+9zyvbF62pk!@gN6H zF0~VmV}+TqBk*;r+z!zs2cV}Wzhr=W%7qbV29q+n87woC^NUi83sO^)Gg2JnfIa{M z`wI}7sSik}U-;y(`MW&708cFg|7QLKesR9@e6_%k&*l>k0Eaw=!zSn1N!1f{ksCOe zO*t^4Z8l0UL!-@s5gc1wq8ASUeb%nxz`z3y9d~d5tE2gh@#bV1dr1QpNSI2VbpVE` z7;giEE>u?_I843KZJ)f)UfF;R;(Zy71_s6p=gu8q5Qb{<1_dIsJ%*-idpQY_s)z3D PL}Z+RdJdfvLj*hkTdmSg diff --git a/collectors/layer2_process.py b/collectors/layer2_process.py index eae2a3a..2414894 100644 --- a/collectors/layer2_process.py +++ b/collectors/layer2_process.py @@ -46,8 +46,8 @@ def collect_process_telemetry(prev_states=None): 'name': info['name'] or 'Unknown', 'cpu_percent': round(cpu_percent, 2), 'memory_percent': round(mem_percent, 2), - 'rss_memory': mem_info.rss, - 'vms_memory': mem_info.vms, + 'rss_memory': mem_info.rss / (1024 * 1024), # MB + 'vms_memory': mem_info.vms / (1024 * 1024 * 1024), # GB 'thread_count': info['num_threads'] or 1, 'read_bytes_sec': round(calc_read_rate, 2), 'write_bytes_sec': round(calc_write_rate, 2), From da7471bc2f56531439370c943376beab1614186c Mon Sep 17 00:00:00 2001 From: khyati-1711 Date: Sat, 20 Jun 2026 23:04:41 +0530 Subject: [PATCH 09/47] Added layer 3 metadata collection --- collectors/layer3_metadata.py | 104 ++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/collectors/layer3_metadata.py b/collectors/layer3_metadata.py index 28a5786..cbf5e1c 100644 --- a/collectors/layer3_metadata.py +++ b/collectors/layer3_metadata.py @@ -1 +1,105 @@ """Layer 3 metadata telemetry collection.""" +import sqlite3 +from datetime import datetime +import psutil + +class MetadataCollector: + + def __init__(self, db_path="cognios_telemetry.db"): + + self.conn = sqlite3.connect(db_path, check_same_thread = False) + self.cursor = self.conn.cursor() + + self.cursor.execute(""" + CREATE TABLE IF NOT EXISTS process_metadata( + pid INTEGER, + ppid INTEGER, + process_name TEXT, + create_time TEXT, + exe_path TEXT, + cmdline TEXT, + username TEXT, + nice_priority INTEGER, + first_seen TEXT, + PRIMARY KEY(pid, create_time) + ) + """) + + self.conn.commit() + def metadata_exists(self, pid, create_time): + self.cursor.execute("SELECT 1 FROM process_metadata WHERE pid=? AND create_time=?", (pid, create_time)) + return self.cursor.fetchone() is not None + + def save_metadata(self, pid): + + try: + p = psutil.Process(pid) + + create_time = datetime.fromtimestamp( + p.create_time() + ).isoformat() + + if self.metadata_exists(pid, create_time): + return + + record = ( + + p.pid, #pid + p.ppid(), + p.name(), + + create_time, + p.exe(), #to track the executable path + " ".join(p.cmdline()), #to track the command line arguments + p.username(), + p.nice(), + datetime.now().isoformat() #to track when we first saw this process + ) + + self.cursor.execute(""" + INSERT INTO process_metadata + VALUES(?,?,?,?,?,?,?,?,?) + """, record) + print( + f"[LAYER3] Metadata stored for PID {pid}" +) + + except ( + psutil.NoSuchProcess, + psutil.AccessDenied, + psutil.ZombieProcess + ) as e: + print(f"[LAYER3 ERROR] {e}") + + def commit(self): + self.conn.commit() + + def cleanup(self, days=7): #delete record older than 7 days + + self.cursor.execute(""" + DELETE FROM process_metadata + WHERE julianday('now')-julianday(first_seen)> ? + """, + (days,) + ) + + self.conn.commit() + + def close(self): # + self.conn.close() + + + +def process_top_processes(top_cpu, top_mem): + + + top_pids = set() + for proc in top_cpu: #this will ensure that we only save metadata for unique PIDs, even if they appear in both top CPU and top memory lists + top_pids.add(proc["pid"]) + for proc in top_mem: + top_pids.add(proc["pid"]) + for pid in top_pids: + collector.save_metadata(pid) + + collector.commit() + collector.cleanup() From 60c4a553fb85aee09f7acef0a0411e39b192d9c2 Mon Sep 17 00:00:00 2001 From: ankitkumar25-rk Date: Sun, 21 Jun 2026 09:30:23 +0530 Subject: [PATCH 10/47] fixs some error --- db.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db.py b/db.py index 8f84e63..8eb9dc8 100644 --- a/db.py +++ b/db.py @@ -30,7 +30,7 @@ def create_connection(db_path): memory_buffers INTEGER, swap_percent REAL, swap_sin INTEGER, - swap_sout INTEGER + swap_sout INTEGER, --Disk Metrics disk_usage_percent REAL, @@ -60,7 +60,7 @@ def create_connection(db_path): --Hardware Metrics avg_temp REAL, max_temp REAL, - battery_percent REAL, + battery_percent REAL ) ''') From 48ec208a2c081b92001599177116039a7df52cba Mon Sep 17 00:00:00 2001 From: ankitkumar25-rk Date: Sun, 21 Jun 2026 09:41:55 +0530 Subject: [PATCH 11/47] resolve: collector is not pass in process_top_processes fxn --- collectors/layer3_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collectors/layer3_metadata.py b/collectors/layer3_metadata.py index cbf5e1c..93ab0be 100644 --- a/collectors/layer3_metadata.py +++ b/collectors/layer3_metadata.py @@ -90,7 +90,7 @@ def close(self): # -def process_top_processes(top_cpu, top_mem): +def process_top_processes(collector, top_cpu, top_mem): top_pids = set() From 1e93b3a267f3d3f0599b789b0e5a833d6ffa9209 Mon Sep 17 00:00:00 2001 From: mitsmania Date: Sun, 21 Jun 2026 10:49:14 +0530 Subject: [PATCH 12/47] defined DiagnosticsCollector class along with trigger function --- collectors/layer4_diagnostics.py | 129 +++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/collectors/layer4_diagnostics.py b/collectors/layer4_diagnostics.py index 6c3c5f7..0e804f5 100644 --- a/collectors/layer4_diagnostics.py +++ b/collectors/layer4_diagnostics.py @@ -1 +1,130 @@ """Layer 4 diagnostics telemetry collection.""" +import threading +import sqlite3 +import psutil +from datetime import datetime + +_layer4_running = False +_layer4_lock = threading.Lock() + +class DiagnosticsCollector: + def __init__(self, db_path="cognios_telemetry.db"): + self.conn = sqlite3.connect(db_path, check_same_thread=False) + self.cursor = self.conn.cursor() + self.cursor.execute(""" + CREATE TABLE IF NOT EXISTS process_diagnostics ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pid INTEGER, + process_name TEXT, + timestamp TEXT, + open_files_count INTEGER, + open_files TEXT, + thread_count INTEGER, + thread_details TEXT, + voluntary_ctx_switches INTEGER, + involuntary_ctx_switches INTEGER, + cpu_affinity TEXT, + io_priority INTEGER, + nice_value INTEGER, + trigger_reason TEXT + ) + """) + self.conn.commit() + + def collect_and_save(self, pid, trigger_reason="anomaly"): + try: + p = psutil.Process(pid) + timestamp = datetime.now().isoformat() + + try: + files = p.open_files() + open_files_count = len(files) + open_files = ",".join(f.path for f in files[:20]) + except (psutil.AccessDenied, Exception): + open_files_count = None + open_files = None + + try: + threads = p.threads() + thread_count = len(threads) + thread_details = ",".join( + f"{t.id}(u={round(t.user_time,2)},s={round(t.system_time,2)})" + for t in threads + ) + except (psutil.AccessDenied, Exception): + thread_count = None + thread_details = None + + try: + ctx = p.num_ctx_switches() + vol_ctx = ctx.voluntary + invol_ctx = ctx.involuntary + except (psutil.AccessDenied, Exception): + vol_ctx, invol_ctx = None, None + + try: + affinity = ",".join(str(c) for c in p.cpu_affinity()) + except (psutil.AccessDenied, AttributeError, Exception): + affinity = None + + try: + ioprio = p.ionice().value + except (psutil.AccessDenied, AttributeError, Exception): + ioprio = None + + try: + nice = p.nice() + except (psutil.AccessDenied, Exception): + nice = None + + self.cursor.execute(""" + INSERT INTO process_diagnostics ( + pid, process_name, timestamp, + open_files_count, open_files, + thread_count, thread_details, + voluntary_ctx_switches, involuntary_ctx_switches, + cpu_affinity, io_priority, nice_value, trigger_reason + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) + """, ( + pid, p.name(), timestamp, + open_files_count, open_files, + thread_count, thread_details, + vol_ctx, invol_ctx, + affinity, ioprio, nice, trigger_reason + )) + self.conn.commit() + except (psutil.NoSuchProcess, psutil.ZombieProcess) as e: + print(f"[LAYER4 ERROR] {e}") + + def close(self): + self.conn.close() + + + +def _run_diagnostics(pid_list, trigger_reason): + global _layer4_running + collector = DiagnosticsCollector() + try: + for pid in pid_list: + collector.collect_and_save(pid, trigger_reason) + finally: + collector.close() + with _layer4_lock: + _layer4_running = False + + +def trigger_layer4(pid_list, trigger_reason="anomaly"): + global _layer4_running + with _layer4_lock: + if _layer4_running: + print("LAYER4 Already running, skipping trigger.") + return + _layer4_running = True + + t = threading.Thread( + target=_run_diagnostics, + args=(pid_list, trigger_reason), + daemon=True + ) + t.start() + From e8d1131d1b452ff1dea1d0d6f7d6f2c5e17493c1 Mon Sep 17 00:00:00 2001 From: Nikhil Date: Sun, 21 Jun 2026 14:20:50 +0530 Subject: [PATCH 13/47] Updated README with architecture diagram Added architecture diagram for CogniOS. --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fa47341..ef75438 100644 --- a/README.md +++ b/README.md @@ -1 +1,38 @@ -# CogniOS +# CogniOS - Archietecture diagram +```mermaid +graph TD + subgraph System Environment + proc[/proc Filesystem/] + psutil[psutil wrapper] + end + + subgraph CogniOS Core + DB[(Central SQLite DB)] + TC[Module 1: Telemetry Collector\nBackground Daemon] + + TC -->|Writes real-time data| DB + proc --> TC + psutil --> TC + end + + subgraph Intelligent Modules + OD[Module 2: OS Doctor\nIsolation Forest] + FO[Module 3: FocusOS\nCNN Classifier] + BB[Module 4: BlackBox\nRolling Window / Replay] + RE[Module 5: Research Engine\nRL & Simulators] + end + + DB <-->|Reads Data / Writes Alerts| OD + DB <-->|Reads Heatmap / Writes Configs| FO + DB <-->|Reads Trace / Writes Narrative| BB + DB <-->|Reads Traces for Simulation| RE + + subgraph User Interface + DASH[Streamlit Dashboard\nUnified GUI] + end + + OD --> DASH + FO --> DASH + BB --> DASH + RE --> DASH +``` From f2160d27914157f0b13671800921cc3d69468335 Mon Sep 17 00:00:00 2001 From: ankitkumar25-rk Date: Sun, 21 Jun 2026 17:29:28 +0530 Subject: [PATCH 14/47] added network metrics --- collectors/layer4_diagnostics.py | 62 +++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/collectors/layer4_diagnostics.py b/collectors/layer4_diagnostics.py index 0e804f5..66ddd9c 100644 --- a/collectors/layer4_diagnostics.py +++ b/collectors/layer4_diagnostics.py @@ -26,7 +26,11 @@ def __init__(self, db_path="cognios_telemetry.db"): cpu_affinity TEXT, io_priority INTEGER, nice_value INTEGER, - trigger_reason TEXT + trigger_reason TEXT, + net_connections_count INTEGER, + net_connections TEXT, + net_bytes_sent INTEGER, + net_bytes_recv INTEGER ) """) self.conn.commit() @@ -36,6 +40,8 @@ def collect_and_save(self, pid, trigger_reason="anomaly"): p = psutil.Process(pid) timestamp = datetime.now().isoformat() + #open files + try: files = p.open_files() open_files_count = len(files) @@ -44,6 +50,7 @@ def collect_and_save(self, pid, trigger_reason="anomaly"): open_files_count = None open_files = None + #threads try: threads = p.threads() thread_count = len(threads) @@ -55,6 +62,8 @@ def collect_and_save(self, pid, trigger_reason="anomaly"): thread_count = None thread_details = None + #ctx switches + try: ctx = p.num_ctx_switches() vol_ctx = ctx.voluntary @@ -62,37 +71,82 @@ def collect_and_save(self, pid, trigger_reason="anomaly"): except (psutil.AccessDenied, Exception): vol_ctx, invol_ctx = None, None + #Cpu affinity try: affinity = ",".join(str(c) for c in p.cpu_affinity()) except (psutil.AccessDenied, AttributeError, Exception): affinity = None + #I/O Priority try: ioprio = p.ionice().value except (psutil.AccessDenied, AttributeError, Exception): ioprio = None + # Nice value try: nice = p.nice() except (psutil.AccessDenied, Exception): nice = None + # Network Connections + try: + conns = p.connections(kind="all") + net_connections_count = len(conns)\ + + conn_parts = [] + for c in conns[:30]: + if c.type == 1: + proto = "tcp" + elif c.type == 2: + proto = "udp" + else: + proto = "other" + + local = f"{c.laddr.ip}:{c.laddr.port}" if c.laddr else "-" + remote = f"{c.raddr.ip}:{c.raddr.port}" if c.raddr else "-" + status = c.status if c.status else "-" + + conn_parts.append(f"{proto}|{local}|{remote}|{status}") + + net_connections = ",".join(conn_parts) + + except (psutil.AccessDenied, AttributeError,Exception): + net_connections_count = None + net_connections = None + + # Per-process Network Bytes + try: + io = p.io_counters() + net_bytes_sent = io.write_bytes + net_bytes_recv = io.read_bytes + except (psutil.AccessDenied, AttributeError, Exception): + net_bytes_sent = None + net_bytes_recv = None + + self.cursor.execute(""" INSERT INTO process_diagnostics ( pid, process_name, timestamp, open_files_count, open_files, thread_count, thread_details, voluntary_ctx_switches, involuntary_ctx_switches, - cpu_affinity, io_priority, nice_value, trigger_reason - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) + cpu_affinity, io_priority, nice_value, trigger_reason, + net_connections_count, net_connections, + net_bytes_sent, net_bytes_recv + + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) """, ( pid, p.name(), timestamp, open_files_count, open_files, thread_count, thread_details, vol_ctx, invol_ctx, - affinity, ioprio, nice, trigger_reason + affinity, ioprio, nice, trigger_reason, + net_connections_count, net_connections, + net_bytes_sent, net_bytes_recv )) self.conn.commit() + except (psutil.NoSuchProcess, psutil.ZombieProcess) as e: print(f"[LAYER4 ERROR] {e}") From 93cde3b31a1354985dd9ac79856c4b7d3687ed83 Mon Sep 17 00:00:00 2001 From: ankitkumar25-rk Date: Mon, 22 Jun 2026 09:37:38 +0530 Subject: [PATCH 15/47] using socket lib for network protocols and also enhance path for db file --- collectors/layer4_diagnostics.py | 58 ++++++++++++++++++++++---------- requirements.txt | 2 +- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/collectors/layer4_diagnostics.py b/collectors/layer4_diagnostics.py index 66ddd9c..34e6634 100644 --- a/collectors/layer4_diagnostics.py +++ b/collectors/layer4_diagnostics.py @@ -1,14 +1,21 @@ """Layer 4 diagnostics telemetry collection.""" -import threading +from datetime import datetime +from pathlib import Path +import socket import sqlite3 +import threading + import psutil -from datetime import datetime _layer4_running = False _layer4_lock = threading.Lock() +DEFAULT_DB_PATH = Path(__file__).resolve().parents[1] / "data" / "cognios.db" class DiagnosticsCollector: - def __init__(self, db_path="cognios_telemetry.db"): + def __init__(self, db_path=DEFAULT_DB_PATH): + db_path = Path(db_path) + db_path.parent.mkdir(parents=True, exist_ok=True) + self.conn = sqlite3.connect(db_path, check_same_thread=False) self.cursor = self.conn.cursor() self.cursor.execute(""" @@ -29,12 +36,30 @@ def __init__(self, db_path="cognios_telemetry.db"): trigger_reason TEXT, net_connections_count INTEGER, net_connections TEXT, - net_bytes_sent INTEGER, - net_bytes_recv INTEGER + io_write_bytes INTEGER, + io_read_bytes INTEGER ) """) + self._ensure_columns( + { + "io_write_bytes": "INTEGER", + "io_read_bytes": "INTEGER", + } + ) self.conn.commit() + def _ensure_columns(self, columns): + existing_columns = { + row[1] + for row in self.cursor.execute("PRAGMA table_info(process_diagnostics)") + } + + for column_name, column_type in columns.items(): + if column_name not in existing_columns: + self.cursor.execute( + f"ALTER TABLE process_diagnostics ADD COLUMN {column_name} {column_type}" + ) + def collect_and_save(self, pid, trigger_reason="anomaly"): try: p = psutil.Process(pid) @@ -91,14 +116,14 @@ def collect_and_save(self, pid, trigger_reason="anomaly"): # Network Connections try: - conns = p.connections(kind="all") - net_connections_count = len(conns)\ + conns = p.net_connections(kind="all") + net_connections_count = len(conns) conn_parts = [] for c in conns[:30]: - if c.type == 1: + if c.type == socket.SOCK_STREAM: proto = "tcp" - elif c.type == 2: + elif c.type == socket.SOCK_DGRAM: proto = "udp" else: proto = "other" @@ -115,14 +140,14 @@ def collect_and_save(self, pid, trigger_reason="anomaly"): net_connections_count = None net_connections = None - # Per-process Network Bytes + # Per-process disk I/O bytes. try: io = p.io_counters() - net_bytes_sent = io.write_bytes - net_bytes_recv = io.read_bytes + io_write_bytes = io.write_bytes + io_read_bytes = io.read_bytes except (psutil.AccessDenied, AttributeError, Exception): - net_bytes_sent = None - net_bytes_recv = None + io_write_bytes = None + io_read_bytes = None self.cursor.execute(""" @@ -133,7 +158,7 @@ def collect_and_save(self, pid, trigger_reason="anomaly"): voluntary_ctx_switches, involuntary_ctx_switches, cpu_affinity, io_priority, nice_value, trigger_reason, net_connections_count, net_connections, - net_bytes_sent, net_bytes_recv + io_write_bytes, io_read_bytes ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) """, ( @@ -143,7 +168,7 @@ def collect_and_save(self, pid, trigger_reason="anomaly"): vol_ctx, invol_ctx, affinity, ioprio, nice, trigger_reason, net_connections_count, net_connections, - net_bytes_sent, net_bytes_recv + io_write_bytes, io_read_bytes )) self.conn.commit() @@ -181,4 +206,3 @@ def trigger_layer4(pid_list, trigger_reason="anomaly"): daemon=True ) t.start() - diff --git a/requirements.txt b/requirements.txt index 6d3b0e9..4d04dda 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -# Project dependencies go here. +psutil>=6.0 From bb044f775232f32485653b684809f7cbeda16dc3 Mon Sep 17 00:00:00 2001 From: NikhByte Date: Mon, 22 Jun 2026 20:18:20 +0530 Subject: [PATCH 16/47] fix: resolve ModuleNotFoundError for utils by appending parent dir to sys.path --- collectors/layer1_system.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/collectors/layer1_system.py b/collectors/layer1_system.py index db42ef3..47e5720 100644 --- a/collectors/layer1_system.py +++ b/collectors/layer1_system.py @@ -1,6 +1,11 @@ import psutil import time from datetime import datetime,timezone +import sys +import os +#adding path to locate the utils +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +import utils from utils.helpers import rate_mb_s # a dictionary to store previous values From 1537096172cc1cd0cd2132fa14d9586fc9ee33bb Mon Sep 17 00:00:00 2001 From: Parshvi Garg Date: Tue, 23 Jun 2026 11:58:04 +0530 Subject: [PATCH 17/47] editing code --- db.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/db.py b/db.py index c5f5bdb..afc8a0a 100644 --- a/db.py +++ b/db.py @@ -90,11 +90,10 @@ def insert_separated_telemetry(top_cpu, top_mem): with sqlite3.connect(DB_NAME) as conn: cursor = conn.cursor() - # Write to top_cpu_telemetry table if metrics exist + if cpu_rows: cursor.executemany(insert_query.format("top_cpu_telemetry"), cpu_rows) - - # Write to top_ram_telemetry table if metrics exist + if ram_rows: cursor.executemany(insert_query.format("top_ram_telemetry"), ram_rows) From 8218fe52025ba86ff33beb6e8e262536465d3482 Mon Sep 17 00:00:00 2001 From: Abhishek Reddy Date: Sat, 27 Jun 2026 12:52:56 +0530 Subject: [PATCH 18/47] file structure created for doctorOS --- os_doctor/alerts.py | 1 - os_doctor/featuring.py | 2 ++ os_doctor/{anomaly_model.py => i_forest.py} | 0 os_doctor/{diagnostics.py => llm_layer.py} | 0 os_doctor/streamlit.py | 1 + 5 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 os_doctor/alerts.py create mode 100644 os_doctor/featuring.py rename os_doctor/{anomaly_model.py => i_forest.py} (100%) rename os_doctor/{diagnostics.py => llm_layer.py} (100%) create mode 100644 os_doctor/streamlit.py diff --git a/os_doctor/alerts.py b/os_doctor/alerts.py deleted file mode 100644 index f19881c..0000000 --- a/os_doctor/alerts.py +++ /dev/null @@ -1 +0,0 @@ -"""Alert helpers for OS Doctor.""" diff --git a/os_doctor/featuring.py b/os_doctor/featuring.py new file mode 100644 index 0000000..1034c7c --- /dev/null +++ b/os_doctor/featuring.py @@ -0,0 +1,2 @@ +""" featuring raw data to useful data.""" + diff --git a/os_doctor/anomaly_model.py b/os_doctor/i_forest.py similarity index 100% rename from os_doctor/anomaly_model.py rename to os_doctor/i_forest.py diff --git a/os_doctor/diagnostics.py b/os_doctor/llm_layer.py similarity index 100% rename from os_doctor/diagnostics.py rename to os_doctor/llm_layer.py diff --git a/os_doctor/streamlit.py b/os_doctor/streamlit.py new file mode 100644 index 0000000..9752436 --- /dev/null +++ b/os_doctor/streamlit.py @@ -0,0 +1 @@ +"""Streamlit Dashboard code goes here""" \ No newline at end of file From e379ab34d826092f6f6dd761735a40717bca1ee5 Mon Sep 17 00:00:00 2001 From: Abhishek Reddy Date: Sat, 27 Jun 2026 12:54:31 +0530 Subject: [PATCH 19/47] readme created --- os_doctor/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 os_doctor/README.md diff --git a/os_doctor/README.md b/os_doctor/README.md new file mode 100644 index 0000000..e69de29 From 6ec83f8424d7357ed47f854cfc0e82d896ced57e Mon Sep 17 00:00:00 2001 From: NikhByte Date: Sat, 27 Jun 2026 13:34:21 +0530 Subject: [PATCH 20/47] fixed base-line 0 error --- collectors/layer1_system.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/collectors/layer1_system.py b/collectors/layer1_system.py index 47e5720..3c764cf 100644 --- a/collectors/layer1_system.py +++ b/collectors/layer1_system.py @@ -22,7 +22,27 @@ def collect_layer1_metrics(): now = time.time() timestamp = datetime.now(timezone.utc).isoformat() - + #process + try: + procs = {} + for p in psutil.process_iter(["name", "memory_percent"]): + try: + p.cpu_percent() + procs[p.pid] = p + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + time.sleep(0.1) + process_data = [] + for pid, p in procs.items(): + try: + cpu = round(p.cpu_percent(), 2) + mem = round(p.info.get("memory_percent") or 0.0, 2) + if cpu > 0.5 or mem > 0.5: + process_data.append((p.info.get("name"), cpu, mem)) + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + except Exception: + process_data = [] # CPU Metrics cpu_usage_percent = psutil.cpu_percent(interval=None) cpu_times = psutil.cpu_times() From 673c775dbaf8fa2f1f37bfbd09ccd6faa65eaf09 Mon Sep 17 00:00:00 2001 From: NikhByte Date: Sat, 27 Jun 2026 13:34:39 +0530 Subject: [PATCH 21/47] added process names with individual cpu and memory usage --- collectors/layer1_system.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/collectors/layer1_system.py b/collectors/layer1_system.py index 3c764cf..1760bd0 100644 --- a/collectors/layer1_system.py +++ b/collectors/layer1_system.py @@ -213,7 +213,8 @@ def collect_layer1_metrics(): "zombie_processes":zombie_processes, "avg_temp":temp_avg, "max_temp":temp_max, - "battery_percent":battery_percent + "battery_percent":battery_percent, + "process_data":process_data } if __name__ == "__main__": From c98b23438ca7f91303884ba2f62e22c781753c47 Mon Sep 17 00:00:00 2001 From: Parshvi Garg Date: Sun, 28 Jun 2026 16:26:16 +0530 Subject: [PATCH 22/47] Add DoctorOS documentation --- documentation.md | 146 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 documentation.md diff --git a/documentation.md b/documentation.md new file mode 100644 index 0000000..62018d2 --- /dev/null +++ b/documentation.md @@ -0,0 +1,146 @@ +OSDoctor +Real-Time Intelligent Operating System Monitoring & Anomaly Detection System + +Project Documentation + +Team Members : +Guide : +Institute : +Date : +Version : + +1. Introduction + +OSDoctor is an intelligent observability system that continuously monitors operating system telemetry, detects abnormal system behavior using machine learning, identifies the likely root cause, and explains the issue in natural language. Its objective is to answer the question: “Why is my laptop slow right now?” This aligns with the project objective described in the proposal. + +2. Objectives + +* Collect live OS telemetry +* Store telemetry efficiently +* Engineer useful features +* Detect anomalies +* Explain detected issues +* Display results on dashboard + + +3. System Architecture + +# OSDoctor: Core Pipeline Architecture + +This document describes the linear data flow and core components of the OSDoctor observability engine as designed. + +## 1. System Architecture Diagram + +```mermaid +graph TD + %% Telemetry Collection Layer + Collector[Telemetry Collector] --> System[System Telemetry] + Collector --> Process[Per Process Telemetry] + + %% Database Interaction + System --> DB_Script[db.py] + Process --> DB_Script + DB_Script --> SQLite[(SQL Lite Database)] + + %% Processing Pipeline + SQLite --> FeatureEng[Feature Engineering] + FeatureEng --> IForest[Isolation Forest] + + %% Evaluation Logic + IForest -->|No| Nothing[Nothing] + IForest -->|Yes| AppendAlert[Append to Alerts Table] + + %% Output Generation & UI + AppendAlert --> LLMLayer[LLM Layer] + LLMLayer --> Streamlit[Streamlit UI] + + %% Custom Styling for Visual Clarity + style Collector fill:#f4f4f4,stroke:#333,stroke-width:2px + style IForest fill:#e1f5fe,stroke:#0288d1,stroke-width:2px + style AppendAlert fill:#ffebee,stroke:#c62828,stroke-width:2px + + 2. Pipeline Step Descriptions +Step 1: Telemetry Collector (System & Per-Process) +• The entry point of the pipeline splits data gathering into two concurrent streams: +• System Telemetry: Monitors macro-level hardware usage across the entire operating system. +• Per-Process Telemetry: Tracks individual application metrics to identify localized resource usage. + +Step 2: Database Pipeline (db.py ➔ SQL Lite) +• Both telemetry feeds pass synchronously into the central database management script (db.py). +• The database script handles formatting, opens a transaction, and writes the metric frames safely into persistent storage inside the SQL Lite engine. + +Step 3: Feature Engineering +• This step reads raw performance rows from SQL Lite and transforms them into predictive indicators. +• It computes dynamic trends, smooths out sudden system noise, and prepares structured multi-dimensional state vectors for the machine learning layer. + +Step 4: Isolation Forest Inference +• The engineered metrics enter the Isolation Forest unsupervised learning model. +• The pipeline evaluates the model's output via a binary branch: +• No Anomaly (Output: 1): The execution stops instantly and loops back (Nothing). +• Anomaly Detected (Output: -1): The execution is flagged as Yes and proceeds downstream. + +Step 5: Append to Alerts Table +• The moment an anomaly is confirmed, the system generates an incident log. +• This record, containing key anomaly flags and metrics data, is immediately written to the specialized alerts table in the database. + +Step 6: LLM Layer +• The LLM Layer triggers automatically when a new record appears in the alerts table. +• It processes the structured metric telemetry data and translates it into natural language explanations covering cause, severity, and mitigation paths. + +Step 7: Streamlit UI Presentation +• The final human-readable report and live performance timeline streams are loaded directly into the front-end dashboard. +• The Streamlit user interface displays recommendations and reactive alerts to let end-users know exactly why their machine is slow. + + +4. Project Workflow + +5. File Structure + +[span_0](start_span)[span_1](start_span)[span_2](start_span)[span_3](start_span)This document outlines the standard Python project layout designed to map your pipeline architecture components into distinct modules and files[span_0](end_span)[span_1](end_span)[span_2](end_span)[span_3](end_span). + +## 1. Directory Layout Diagram + +```mermaid +graph TD + Project[os_doctor_project/] --> Src[src/] + Project --> Requirements[requirements.txt] + Project --> README[README.md] + + %% Collectors + Src --> CollDir[collectors/] + CollDir --> SysColl[system_collector.py] + CollDir --> ProcColl[process_collector.py] + CollDir --> DeepColl[deep_diagnostics.py] + + %% Core Processing & ML + Src --> EngineDir[engine/] + EngineDir --> DB[db.py] + EngineDir --> Feature[feature.py] + EngineDir --> IForest[i_forest.py] + EngineDir --> Tagger[tagger.py] + + %% Analysis & Frontend + Src --> WorkersDir[workers/] + WorkersDir --> LLMExec[llm_executor.py] + + Src --> AppUI[app.py] + + %% Styling + style Project fill:#f5f5f5,stroke:#333,stroke-width:2px + style SysColl fill:#e8f5e9,stroke:#2e7d32 + style ProcColl fill:#e8f5e9,stroke:#2e7d32 + style DB fill:#fff3e0,stroke:#ef6c00 + style IForest fill:#e1f5fe,stroke:#0288d1 + style LLMExec fill:#f3e5f5,stroke:#6a1b9a + style AppUI fill:#ffebee,stroke:#c62828 + + +6. Module Description +7. Function Documentation +8. Database Design +9. Feature Engineering +10. Machine Learning Model +11. LLM Explanation Layer +12. Dashboard +13. Installation +14. Future Scope From 809bd1c36c6f0764a6fa00ee0045e6146b9c59d2 Mon Sep 17 00:00:00 2001 From: parshvigarg3 Date: Mon, 29 Jun 2026 18:41:54 +0530 Subject: [PATCH 23/47] documentation created --- documentation.md => os_doctor/documentation.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename documentation.md => os_doctor/documentation.md (100%) diff --git a/documentation.md b/os_doctor/documentation.md similarity index 100% rename from documentation.md rename to os_doctor/documentation.md From a6238b2e03ecf09d20cdad31b0903dc2ee84d184 Mon Sep 17 00:00:00 2001 From: mitsmania Date: Mon, 29 Jun 2026 21:34:55 +0530 Subject: [PATCH 24/47] added required files --- blackbox/anomaly_model.py | 1 + blackbox/correlation.py | 1 + blackbox/feature_engineering.py | 1 + blackbox/heartbeat.py | 1 + blackbox/rule_engine.py | 1 + blackbox/zscore_detector.py | 1 + 6 files changed, 6 insertions(+) create mode 100644 blackbox/anomaly_model.py create mode 100644 blackbox/correlation.py create mode 100644 blackbox/feature_engineering.py create mode 100644 blackbox/heartbeat.py create mode 100644 blackbox/rule_engine.py create mode 100644 blackbox/zscore_detector.py diff --git a/blackbox/anomaly_model.py b/blackbox/anomaly_model.py new file mode 100644 index 0000000..9cd1d4e --- /dev/null +++ b/blackbox/anomaly_model.py @@ -0,0 +1 @@ +#Isolation Forest wrapper (sklearn) \ No newline at end of file diff --git a/blackbox/correlation.py b/blackbox/correlation.py new file mode 100644 index 0000000..40d86a8 --- /dev/null +++ b/blackbox/correlation.py @@ -0,0 +1 @@ +#builds cause-effect event chains \ No newline at end of file diff --git a/blackbox/feature_engineering.py b/blackbox/feature_engineering.py new file mode 100644 index 0000000..6061905 --- /dev/null +++ b/blackbox/feature_engineering.py @@ -0,0 +1 @@ +#converts raw DB rows to statistical feature vector \ No newline at end of file diff --git a/blackbox/heartbeat.py b/blackbox/heartbeat.py new file mode 100644 index 0000000..62464f4 --- /dev/null +++ b/blackbox/heartbeat.py @@ -0,0 +1 @@ +#Crash/freeze detection via heartbeat timestamps \ No newline at end of file diff --git a/blackbox/rule_engine.py b/blackbox/rule_engine.py new file mode 100644 index 0000000..080ae85 --- /dev/null +++ b/blackbox/rule_engine.py @@ -0,0 +1 @@ +#deterministic threshold-based checks (backup) \ No newline at end of file diff --git a/blackbox/zscore_detector.py b/blackbox/zscore_detector.py new file mode 100644 index 0000000..72f3a16 --- /dev/null +++ b/blackbox/zscore_detector.py @@ -0,0 +1 @@ +#z-score and slope(trend) \ No newline at end of file From 8a68edde254612556212e8a6744b09a47e2e05ff Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 29 Jun 2026 21:57:12 +0530 Subject: [PATCH 25/47] updated Readme.md --- README.md | 424 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 423 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fa47341..e1b03dc 100644 --- a/README.md +++ b/README.md @@ -1 +1,423 @@ -# CogniOS +# BlackBox Module — CogniOS + +> **"Airplane ka black box"** — continuously record karta rehta hai, crash/freeze ke baad replay karke batata hai exactly kya hua tha aur kyun. + +--- + +## 1. Module Overview + +BlackBox is CogniOS's **flight recorder and forensic analysis engine**. It answers one core question: + +> _"My system froze/crashed — what happened in the 30 minutes before it?"_ + +### What BlackBox is + +- A real-time telemetry recorder (rolling 30-minute window) +- An anomaly detection engine (Z-score + Isolation Forest) +- A root-cause explanation system (Correlation Engine + LLM) + +### Core pipeline + +``` +Layer 1/2/3/4 Telemetry + ↓ +Rolling Event Store (last 30 min only) + ↓ +Heartbeat System (crash/freeze detection) + ↓ +Feature Engineering (120 rows → 1 feature vector) + ↓ +Z-score Detector (sudden spikes + slow drift) + ↓ +Isolation Forest (multi-metric anomaly confirmation) + ↓ +Rule Engine (deterministic backup checks) + ↓ +Correlation Engine (CAUSE → EFFECT chain) + ↓ +Replay Timeline + ↓ +LLM Explanation (human-readable root cause) + ↓ +Dashboard Alert +``` + +--- + +## 2. File Structure + +``` +blackbox/ +├── recorder.py # Rolling event store — writes + prunes telemetry +├── heartbeat.py # Crash/freeze detection via heartbeat timestamps +├── feature_engineering.py # Converts raw DB rows → statistical feature vector +├── zscore_detector.py # Z-score + slope (trend) based anomaly detector +├── anomaly_model.py # Isolation Forest wrapper (sklearn) +├── rule_engine.py # Deterministic threshold-based checks (backup) +├── correlation.py # Builds CAUSE → EFFECT event chains +├── replay.py # Reconstructs timeline from rolling window +├── nl_query.py # Natural language query interface over crash data +``` + +``` +utils/ +├── config.py # All BlackBox config constants +├── db.py # SQLite connection + schema helpers +└── helpers.py # rate_mb_s() and other shared utilities +``` + +--- + +## 3. Function Definitions + +### `recorder.py` + +```python +def write_telemetry(conn, metrics: dict) -> None +``` + +**Purpose:** Inserts one Layer 1 metrics row into the rolling telemetry store. +Calls `prune_old_records()` after every write to enforce the 30-minute window. + +```python +def rm_old_records(conn) -> None +``` + +**Purpose:** Deletes rows older than `BLACKBOX_WINDOW_SEC` (default 1800s). + +```sql +DELETE FROM telemetry WHERE timestamp < (strftime('%s','now') - 1800) +``` + +--- + +### `heartbeat.py` + +```python +def update_heartbeat(conn) -> None +``` + +**Purpose:** Updates a single-row heartbeat table with `time.time()` every second. +If the daemon crashes, the last saved timestamp persists in SQLite. + +```python +def check_crash_on_startup(conn) -> bool +``` + +**Purpose:** Called once at daemon startup. Reads last heartbeat timestamp. +If `time.time() - last_beat > 10`, returns `True` → crash/freeze was detected. +Triggers `replay.py` to reconstruct the pre-crash timeline. + +--- + +### `feature_engineering.py` + +```python +def extract_feature_vector(rows: list[dict]) -> list[float] +``` + +**Purpose:** Converts the last 120 raw telemetry rows (2 minutes × 1 sample/sec) +into a single statistical feature vector for anomaly detection. + +**Input:** 120 dicts from `layer1_system` table +**Output:** +`[mean_cpu, max_cpu, cpu_growth_rate, cpu_variance, mean_ram, memory_growth_rate, disk_spike_frequency, context_switch_rate]` + +Why statistical summary and not raw rows? + +- Isolation Forest is **not a time-series model** — it takes a single point in feature space +- Z-score also operates on aggregated values +- 8 features instead of 120×8=960 raw values = faster + less noise + +--- + +### `zscore_detector.py` + +```python +class ZScoreDetector: + def __init__(self, + zscore_window=1800, # seconds of rolling baseline + trend_window=600, # seconds for slope calculation + sustained_sec=30, # spike must last this long + z_threshold=2.8, + slope_threshold=0.003, + sustained_ratio=0.6) +``` + +```python + def update(self, val: float) -> None +``` + +**Purpose:** Appends new reading to all internal deques (zscore, trend, sustained). + +```python + def check(self, val: float, metric_name: str, unit: str) -> list[dict] +``` + +**Purpose:** Runs three independent checks and returns list of detected issues. + +**Check 1 — Z-score (sudden spike):** + +``` +Z = (current_value - rolling_mean) / rolling_std +If |Z| > 2.8 AND spike sustained for 30s → ALERT +``` + +Catches: CPU suddenly 35% → 95% + +**Check 2 — Slope/Trend (slow drift):** + +```python +slope = np.polyfit(time_axis, values, 1)[0] # linear regression +If slope > 0.003 %/sec → ALERT (memory leak pattern) +``` + +Catches: Memory slowly 40% → 80% over 2 hours (Z-score misses this) +Bonus: predicts `ETA to critical = (90 - current) / slope / 60` minutes + +**Check 3 — Sustained filter (false positive prevention):** + +``` +If < 60% readings above threshold in last 30s → skip (compilation burst) +If ≥ 60% readings above threshold → real anomaly +``` + +Prevents: Short CPU bursts from `gcc` compilation triggering false alerts + +**Why Z-score alone is not enough:** + +- Rolling window adapts to slow drift → memory leak goes undetected +- Single-metric check → misses multi-metric correlations +- Solution: Z-score + Slope + Isolation Forest together + +--- + +### `anomaly_model.py` + +```python +def train(normal_feature_vectors: np.ndarray) -> IsolationForest +``` + +**Purpose:** Trains Isolation Forest on normal telemetry data only. +`contamination=0.05` means we expect ~5% of data to be anomalous. + +```python +def predict(model: IsolationForest, feature_vector: list) -> tuple[int, float] +``` + +**Purpose:** Returns `(label, score)` where: + +- `label = 1` → normal +- `label = -1` → anomaly +- `score` → negative = more anomalous + +**How Isolation Forest works:** +Isolation Forest is an **unsupervised algorithm that detects anomalies in a feature space** — NOT a time-series model. + +Core intuition: + +- Normal points cluster together → many random splits needed to isolate them +- Anomaly points are sparse → isolated with very few splits +- Points isolated in fewer splits = higher anomaly score + +``` +Normal [cpu=35, mem=55, disk=2]: deep in tree → many splits → normal +Anomaly [cpu=40, mem=40, disk=40, net=40]: shallow in tree → few splits → anomaly +``` + +Key advantage over Z-score: detects **multi-metric combinations** that are individually normal but collectively anomalous. + +Example: `CPU=40%, MEM=40%, DISK=40%, NET=40%` — each metric looks fine individually, but this combination never appears in normal usage . + +--- + +### `rule_engine.py` + +```python +def check_rules(metrics: dict) -> list[dict] +``` + +**Purpose:** Deterministic backup checks — always runs alongside ML models. +Catches obvious anomalies even before warmup period ends. + +```python +RULES = [ + {"condition": lambda m: m["cpu"] > 95, "alert": "cpu_critical"}, + {"condition": lambda m: m["thread_growth_rate"] > 200, "alert": "thread_explosion"}, + {"condition": lambda m: m["zombie_count"] > 10, "alert": "zombie_buildup"}, + {"condition": lambda m: m["memory_growth_continuous"], "alert": "memory_leak"}, +] +``` + +Why rule engine alongside ML? + +- ML models need warmup period (60s) → rules work from second 1 +- Rules are 100% deterministic → never fail unexpectedly +- Double detection = higher confidence alerts + +--- + +### `correlation.py` + +```python +def build_event_chain(events: list[dict]) -> list[dict] +``` + +**Purpose:** Takes raw timestamped events and builds a CAUSE → EFFECT chain +by correlating events that occurred close together in time. + +Example output: + +``` +Chrome opened (10:01) + ↓ +Renderer processes +12 (10:02) + ↓ +Thread count +340 in 60s (10:03) + ↓ +CPU saturation 87% (10:04) + ↓ +Isolation Forest: ANOMALY -1 (10:05) + ↓ +System freeze detected (10:05) +``` + +```python +def telemetry_to_events(rows: list[dict]) -> list[dict] +``` + +**Purpose:** Converts raw telemetry rows into discrete events with type + timestamp. + +```python +{"type": "cpu_spike", "time": "10:03:45", "detail": "cpu jumped to 87%"} +``` + +--- + +### `replay.py` + +```python +def replay(conn, crash_time: float, window_minutes: int = 30) -> list[dict] +``` + +**Purpose:** Fetches all telemetry rows from `crash_time - window_minutes` to `crash_time`. +Returns ordered list for dashboard timeline visualization. + +```python +def generate_narrative(events: list[dict], anomaly_type: str) -> str +``` + +**Purpose:** Formats the event chain into a structured JSON for LLM input. + +--- + +### `nl_query.py` + +```python +def query(conn, question: str) -> str +``` + +**Purpose:** Accepts free-text questions about crash data and returns answers +by querying SQLite + formatting context for LLM. + +Example: + +``` +Input: "Which process caused the freeze at 10:05?" +Output: "Chrome (PID 1823) spawned 340 additional threads between 10:01-10:04, + causing CPU saturation that led to the system freeze." +``` + +--- + +## 4. Useful Telemetry Data + +BlackBox consumes data from all 4 collector layers: + +### From `layer1_system` (Layer 1 — every 1s) + +| Column | Use in BlackBox | +| ------------------- | --------------------------------------- | +| `cpu_usage_percent` | Primary Z-score metric, spike detection | +| `memory_percent` | Memory leak slope detection | +| `disk_read_mb_s` | I/O storm detection | +| `net_rate_mb_s` | Network exfiltration detection | +| `cpu_ctx_switches` | Scheduler congestion (feature vector) | +| `total_processes` | Thread explosion detection | +| `zombie_processes` | Zombie accumulation rule check | +| `load_avg_1` | Scheduler load feature | +| `swap_percent` | Memory pressure feature | + +### From `layer2_top_processes` (Layer 2 — every 5s) + +| Column | Use in BlackBox | +| ------------- | ---------------------------------------------- | +| `pid`, `name` | Identify culprit process in correlation engine | +| `cpu_percent` | Which process caused CPU spike | +| `rss_memory` | Which process is leaking memory | +| `timestamp` | Timeline correlation | + +### From `process_metadata` (Layer 3 — on first seen) + +| Column | Use in BlackBox | +| ------------- | -------------------------------------- | +| `cmdline` | What exact command was running | +| `exe_path` | Where executable came from | +| `username` | Who launched the process | +| `create_time` | When process started relative to crash | + +### From `process_diagnostics` (Layer 4 — on anomaly) + +| Column | Use in BlackBox | +| ------------------ | ---------------------------------------- | +| `open_files_count` | File handle leak detection | +| `thread_details` | Which threads were consuming CPU | +| `net_connections` | Active network connections at crash time | +| `trigger_reason` | Why Layer 4 was triggered | + +### Rolling window SQL + +```sql +-- Fetch last 2 minutes for feature extraction +SELECT cpu_usage_percent, memory_percent, + disk_read_mb_s, net_rate_mb_s, + total_processes, cpu_ctx_switches +FROM layer1_system +ORDER BY timestamp DESC +LIMIT 120; + +--Trim to 30-minute window +DELETE FROM telemetry +WHERE timestamp < (strftime('%s', 'now') - 1800); +``` + +--- + +## 5. Additional Info + +### Config values (from `utils/config.py`) + +```python +# BlackBox — all tunable from one place (mentor requirement) +BLACKBOX_WINDOW_SEC = 1800 # rolling window duration +BLACKBOX_WARMUP_SEC = 60 # seconds before detection starts +BLACKBOX_Z_THRESHOLD = 2.8 # standard deviations +BLACKBOX_SLOPE_THRESHOLD = 0.003 # %/sec rising = suspicious +BLACKBOX_SUSTAINED_SEC = 30 # spike must last this long +BLACKBOX_SUSTAINED_RATIO = 0.6 # 60% readings must be above threshold +BLACKBOX_TREND_WINDOW = 600 # 10 min for slope calculation +``` + +### Key design decisions + +**Why SQLite over other storage?** +Lightweight, local, crash-resistant (data survives daemon crash), queryable via SQL, replay-friendly. + +**Why rolling 30-minute window?** +30 minutes of pre-crash context is sufficient for root-cause analysis. Longer window = more disk usage with diminishing returns. + +**Why Isolation Forest and not supervised model?** +No labeled anomaly dataset exists initially. IF is unsupervised — trained on normal data only, no labels needed. + +**Why Z-score threshold 2.8 and not 3.0?** +3.0 is the classic threshold but at 3.0, a developer's laptop (higher baseline CPU) barely crossed the threshold for genuine spikes. 2.8 gives slightly better sensitivity while keeping false positives low with the sustained filter. From b49381c77c7f0056c5190e2205517cf16a701690 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 29 Jun 2026 23:38:45 +0530 Subject: [PATCH 26/47] updated path of readme file for blackbox --- README.md | 424 +-------------------------------------------- blackbox/README.md | 423 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 424 insertions(+), 423 deletions(-) create mode 100644 blackbox/README.md diff --git a/README.md b/README.md index e1b03dc..fa47341 100644 --- a/README.md +++ b/README.md @@ -1,423 +1 @@ -# BlackBox Module — CogniOS - -> **"Airplane ka black box"** — continuously record karta rehta hai, crash/freeze ke baad replay karke batata hai exactly kya hua tha aur kyun. - ---- - -## 1. Module Overview - -BlackBox is CogniOS's **flight recorder and forensic analysis engine**. It answers one core question: - -> _"My system froze/crashed — what happened in the 30 minutes before it?"_ - -### What BlackBox is - -- A real-time telemetry recorder (rolling 30-minute window) -- An anomaly detection engine (Z-score + Isolation Forest) -- A root-cause explanation system (Correlation Engine + LLM) - -### Core pipeline - -``` -Layer 1/2/3/4 Telemetry - ↓ -Rolling Event Store (last 30 min only) - ↓ -Heartbeat System (crash/freeze detection) - ↓ -Feature Engineering (120 rows → 1 feature vector) - ↓ -Z-score Detector (sudden spikes + slow drift) - ↓ -Isolation Forest (multi-metric anomaly confirmation) - ↓ -Rule Engine (deterministic backup checks) - ↓ -Correlation Engine (CAUSE → EFFECT chain) - ↓ -Replay Timeline - ↓ -LLM Explanation (human-readable root cause) - ↓ -Dashboard Alert -``` - ---- - -## 2. File Structure - -``` -blackbox/ -├── recorder.py # Rolling event store — writes + prunes telemetry -├── heartbeat.py # Crash/freeze detection via heartbeat timestamps -├── feature_engineering.py # Converts raw DB rows → statistical feature vector -├── zscore_detector.py # Z-score + slope (trend) based anomaly detector -├── anomaly_model.py # Isolation Forest wrapper (sklearn) -├── rule_engine.py # Deterministic threshold-based checks (backup) -├── correlation.py # Builds CAUSE → EFFECT event chains -├── replay.py # Reconstructs timeline from rolling window -├── nl_query.py # Natural language query interface over crash data -``` - -``` -utils/ -├── config.py # All BlackBox config constants -├── db.py # SQLite connection + schema helpers -└── helpers.py # rate_mb_s() and other shared utilities -``` - ---- - -## 3. Function Definitions - -### `recorder.py` - -```python -def write_telemetry(conn, metrics: dict) -> None -``` - -**Purpose:** Inserts one Layer 1 metrics row into the rolling telemetry store. -Calls `prune_old_records()` after every write to enforce the 30-minute window. - -```python -def rm_old_records(conn) -> None -``` - -**Purpose:** Deletes rows older than `BLACKBOX_WINDOW_SEC` (default 1800s). - -```sql -DELETE FROM telemetry WHERE timestamp < (strftime('%s','now') - 1800) -``` - ---- - -### `heartbeat.py` - -```python -def update_heartbeat(conn) -> None -``` - -**Purpose:** Updates a single-row heartbeat table with `time.time()` every second. -If the daemon crashes, the last saved timestamp persists in SQLite. - -```python -def check_crash_on_startup(conn) -> bool -``` - -**Purpose:** Called once at daemon startup. Reads last heartbeat timestamp. -If `time.time() - last_beat > 10`, returns `True` → crash/freeze was detected. -Triggers `replay.py` to reconstruct the pre-crash timeline. - ---- - -### `feature_engineering.py` - -```python -def extract_feature_vector(rows: list[dict]) -> list[float] -``` - -**Purpose:** Converts the last 120 raw telemetry rows (2 minutes × 1 sample/sec) -into a single statistical feature vector for anomaly detection. - -**Input:** 120 dicts from `layer1_system` table -**Output:** -`[mean_cpu, max_cpu, cpu_growth_rate, cpu_variance, mean_ram, memory_growth_rate, disk_spike_frequency, context_switch_rate]` - -Why statistical summary and not raw rows? - -- Isolation Forest is **not a time-series model** — it takes a single point in feature space -- Z-score also operates on aggregated values -- 8 features instead of 120×8=960 raw values = faster + less noise - ---- - -### `zscore_detector.py` - -```python -class ZScoreDetector: - def __init__(self, - zscore_window=1800, # seconds of rolling baseline - trend_window=600, # seconds for slope calculation - sustained_sec=30, # spike must last this long - z_threshold=2.8, - slope_threshold=0.003, - sustained_ratio=0.6) -``` - -```python - def update(self, val: float) -> None -``` - -**Purpose:** Appends new reading to all internal deques (zscore, trend, sustained). - -```python - def check(self, val: float, metric_name: str, unit: str) -> list[dict] -``` - -**Purpose:** Runs three independent checks and returns list of detected issues. - -**Check 1 — Z-score (sudden spike):** - -``` -Z = (current_value - rolling_mean) / rolling_std -If |Z| > 2.8 AND spike sustained for 30s → ALERT -``` - -Catches: CPU suddenly 35% → 95% - -**Check 2 — Slope/Trend (slow drift):** - -```python -slope = np.polyfit(time_axis, values, 1)[0] # linear regression -If slope > 0.003 %/sec → ALERT (memory leak pattern) -``` - -Catches: Memory slowly 40% → 80% over 2 hours (Z-score misses this) -Bonus: predicts `ETA to critical = (90 - current) / slope / 60` minutes - -**Check 3 — Sustained filter (false positive prevention):** - -``` -If < 60% readings above threshold in last 30s → skip (compilation burst) -If ≥ 60% readings above threshold → real anomaly -``` - -Prevents: Short CPU bursts from `gcc` compilation triggering false alerts - -**Why Z-score alone is not enough:** - -- Rolling window adapts to slow drift → memory leak goes undetected -- Single-metric check → misses multi-metric correlations -- Solution: Z-score + Slope + Isolation Forest together - ---- - -### `anomaly_model.py` - -```python -def train(normal_feature_vectors: np.ndarray) -> IsolationForest -``` - -**Purpose:** Trains Isolation Forest on normal telemetry data only. -`contamination=0.05` means we expect ~5% of data to be anomalous. - -```python -def predict(model: IsolationForest, feature_vector: list) -> tuple[int, float] -``` - -**Purpose:** Returns `(label, score)` where: - -- `label = 1` → normal -- `label = -1` → anomaly -- `score` → negative = more anomalous - -**How Isolation Forest works:** -Isolation Forest is an **unsupervised algorithm that detects anomalies in a feature space** — NOT a time-series model. - -Core intuition: - -- Normal points cluster together → many random splits needed to isolate them -- Anomaly points are sparse → isolated with very few splits -- Points isolated in fewer splits = higher anomaly score - -``` -Normal [cpu=35, mem=55, disk=2]: deep in tree → many splits → normal -Anomaly [cpu=40, mem=40, disk=40, net=40]: shallow in tree → few splits → anomaly -``` - -Key advantage over Z-score: detects **multi-metric combinations** that are individually normal but collectively anomalous. - -Example: `CPU=40%, MEM=40%, DISK=40%, NET=40%` — each metric looks fine individually, but this combination never appears in normal usage . - ---- - -### `rule_engine.py` - -```python -def check_rules(metrics: dict) -> list[dict] -``` - -**Purpose:** Deterministic backup checks — always runs alongside ML models. -Catches obvious anomalies even before warmup period ends. - -```python -RULES = [ - {"condition": lambda m: m["cpu"] > 95, "alert": "cpu_critical"}, - {"condition": lambda m: m["thread_growth_rate"] > 200, "alert": "thread_explosion"}, - {"condition": lambda m: m["zombie_count"] > 10, "alert": "zombie_buildup"}, - {"condition": lambda m: m["memory_growth_continuous"], "alert": "memory_leak"}, -] -``` - -Why rule engine alongside ML? - -- ML models need warmup period (60s) → rules work from second 1 -- Rules are 100% deterministic → never fail unexpectedly -- Double detection = higher confidence alerts - ---- - -### `correlation.py` - -```python -def build_event_chain(events: list[dict]) -> list[dict] -``` - -**Purpose:** Takes raw timestamped events and builds a CAUSE → EFFECT chain -by correlating events that occurred close together in time. - -Example output: - -``` -Chrome opened (10:01) - ↓ -Renderer processes +12 (10:02) - ↓ -Thread count +340 in 60s (10:03) - ↓ -CPU saturation 87% (10:04) - ↓ -Isolation Forest: ANOMALY -1 (10:05) - ↓ -System freeze detected (10:05) -``` - -```python -def telemetry_to_events(rows: list[dict]) -> list[dict] -``` - -**Purpose:** Converts raw telemetry rows into discrete events with type + timestamp. - -```python -{"type": "cpu_spike", "time": "10:03:45", "detail": "cpu jumped to 87%"} -``` - ---- - -### `replay.py` - -```python -def replay(conn, crash_time: float, window_minutes: int = 30) -> list[dict] -``` - -**Purpose:** Fetches all telemetry rows from `crash_time - window_minutes` to `crash_time`. -Returns ordered list for dashboard timeline visualization. - -```python -def generate_narrative(events: list[dict], anomaly_type: str) -> str -``` - -**Purpose:** Formats the event chain into a structured JSON for LLM input. - ---- - -### `nl_query.py` - -```python -def query(conn, question: str) -> str -``` - -**Purpose:** Accepts free-text questions about crash data and returns answers -by querying SQLite + formatting context for LLM. - -Example: - -``` -Input: "Which process caused the freeze at 10:05?" -Output: "Chrome (PID 1823) spawned 340 additional threads between 10:01-10:04, - causing CPU saturation that led to the system freeze." -``` - ---- - -## 4. Useful Telemetry Data - -BlackBox consumes data from all 4 collector layers: - -### From `layer1_system` (Layer 1 — every 1s) - -| Column | Use in BlackBox | -| ------------------- | --------------------------------------- | -| `cpu_usage_percent` | Primary Z-score metric, spike detection | -| `memory_percent` | Memory leak slope detection | -| `disk_read_mb_s` | I/O storm detection | -| `net_rate_mb_s` | Network exfiltration detection | -| `cpu_ctx_switches` | Scheduler congestion (feature vector) | -| `total_processes` | Thread explosion detection | -| `zombie_processes` | Zombie accumulation rule check | -| `load_avg_1` | Scheduler load feature | -| `swap_percent` | Memory pressure feature | - -### From `layer2_top_processes` (Layer 2 — every 5s) - -| Column | Use in BlackBox | -| ------------- | ---------------------------------------------- | -| `pid`, `name` | Identify culprit process in correlation engine | -| `cpu_percent` | Which process caused CPU spike | -| `rss_memory` | Which process is leaking memory | -| `timestamp` | Timeline correlation | - -### From `process_metadata` (Layer 3 — on first seen) - -| Column | Use in BlackBox | -| ------------- | -------------------------------------- | -| `cmdline` | What exact command was running | -| `exe_path` | Where executable came from | -| `username` | Who launched the process | -| `create_time` | When process started relative to crash | - -### From `process_diagnostics` (Layer 4 — on anomaly) - -| Column | Use in BlackBox | -| ------------------ | ---------------------------------------- | -| `open_files_count` | File handle leak detection | -| `thread_details` | Which threads were consuming CPU | -| `net_connections` | Active network connections at crash time | -| `trigger_reason` | Why Layer 4 was triggered | - -### Rolling window SQL - -```sql --- Fetch last 2 minutes for feature extraction -SELECT cpu_usage_percent, memory_percent, - disk_read_mb_s, net_rate_mb_s, - total_processes, cpu_ctx_switches -FROM layer1_system -ORDER BY timestamp DESC -LIMIT 120; - ---Trim to 30-minute window -DELETE FROM telemetry -WHERE timestamp < (strftime('%s', 'now') - 1800); -``` - ---- - -## 5. Additional Info - -### Config values (from `utils/config.py`) - -```python -# BlackBox — all tunable from one place (mentor requirement) -BLACKBOX_WINDOW_SEC = 1800 # rolling window duration -BLACKBOX_WARMUP_SEC = 60 # seconds before detection starts -BLACKBOX_Z_THRESHOLD = 2.8 # standard deviations -BLACKBOX_SLOPE_THRESHOLD = 0.003 # %/sec rising = suspicious -BLACKBOX_SUSTAINED_SEC = 30 # spike must last this long -BLACKBOX_SUSTAINED_RATIO = 0.6 # 60% readings must be above threshold -BLACKBOX_TREND_WINDOW = 600 # 10 min for slope calculation -``` - -### Key design decisions - -**Why SQLite over other storage?** -Lightweight, local, crash-resistant (data survives daemon crash), queryable via SQL, replay-friendly. - -**Why rolling 30-minute window?** -30 minutes of pre-crash context is sufficient for root-cause analysis. Longer window = more disk usage with diminishing returns. - -**Why Isolation Forest and not supervised model?** -No labeled anomaly dataset exists initially. IF is unsupervised — trained on normal data only, no labels needed. - -**Why Z-score threshold 2.8 and not 3.0?** -3.0 is the classic threshold but at 3.0, a developer's laptop (higher baseline CPU) barely crossed the threshold for genuine spikes. 2.8 gives slightly better sensitivity while keeping false positives low with the sustained filter. +# CogniOS diff --git a/blackbox/README.md b/blackbox/README.md new file mode 100644 index 0000000..e1b03dc --- /dev/null +++ b/blackbox/README.md @@ -0,0 +1,423 @@ +# BlackBox Module — CogniOS + +> **"Airplane ka black box"** — continuously record karta rehta hai, crash/freeze ke baad replay karke batata hai exactly kya hua tha aur kyun. + +--- + +## 1. Module Overview + +BlackBox is CogniOS's **flight recorder and forensic analysis engine**. It answers one core question: + +> _"My system froze/crashed — what happened in the 30 minutes before it?"_ + +### What BlackBox is + +- A real-time telemetry recorder (rolling 30-minute window) +- An anomaly detection engine (Z-score + Isolation Forest) +- A root-cause explanation system (Correlation Engine + LLM) + +### Core pipeline + +``` +Layer 1/2/3/4 Telemetry + ↓ +Rolling Event Store (last 30 min only) + ↓ +Heartbeat System (crash/freeze detection) + ↓ +Feature Engineering (120 rows → 1 feature vector) + ↓ +Z-score Detector (sudden spikes + slow drift) + ↓ +Isolation Forest (multi-metric anomaly confirmation) + ↓ +Rule Engine (deterministic backup checks) + ↓ +Correlation Engine (CAUSE → EFFECT chain) + ↓ +Replay Timeline + ↓ +LLM Explanation (human-readable root cause) + ↓ +Dashboard Alert +``` + +--- + +## 2. File Structure + +``` +blackbox/ +├── recorder.py # Rolling event store — writes + prunes telemetry +├── heartbeat.py # Crash/freeze detection via heartbeat timestamps +├── feature_engineering.py # Converts raw DB rows → statistical feature vector +├── zscore_detector.py # Z-score + slope (trend) based anomaly detector +├── anomaly_model.py # Isolation Forest wrapper (sklearn) +├── rule_engine.py # Deterministic threshold-based checks (backup) +├── correlation.py # Builds CAUSE → EFFECT event chains +├── replay.py # Reconstructs timeline from rolling window +├── nl_query.py # Natural language query interface over crash data +``` + +``` +utils/ +├── config.py # All BlackBox config constants +├── db.py # SQLite connection + schema helpers +└── helpers.py # rate_mb_s() and other shared utilities +``` + +--- + +## 3. Function Definitions + +### `recorder.py` + +```python +def write_telemetry(conn, metrics: dict) -> None +``` + +**Purpose:** Inserts one Layer 1 metrics row into the rolling telemetry store. +Calls `prune_old_records()` after every write to enforce the 30-minute window. + +```python +def rm_old_records(conn) -> None +``` + +**Purpose:** Deletes rows older than `BLACKBOX_WINDOW_SEC` (default 1800s). + +```sql +DELETE FROM telemetry WHERE timestamp < (strftime('%s','now') - 1800) +``` + +--- + +### `heartbeat.py` + +```python +def update_heartbeat(conn) -> None +``` + +**Purpose:** Updates a single-row heartbeat table with `time.time()` every second. +If the daemon crashes, the last saved timestamp persists in SQLite. + +```python +def check_crash_on_startup(conn) -> bool +``` + +**Purpose:** Called once at daemon startup. Reads last heartbeat timestamp. +If `time.time() - last_beat > 10`, returns `True` → crash/freeze was detected. +Triggers `replay.py` to reconstruct the pre-crash timeline. + +--- + +### `feature_engineering.py` + +```python +def extract_feature_vector(rows: list[dict]) -> list[float] +``` + +**Purpose:** Converts the last 120 raw telemetry rows (2 minutes × 1 sample/sec) +into a single statistical feature vector for anomaly detection. + +**Input:** 120 dicts from `layer1_system` table +**Output:** +`[mean_cpu, max_cpu, cpu_growth_rate, cpu_variance, mean_ram, memory_growth_rate, disk_spike_frequency, context_switch_rate]` + +Why statistical summary and not raw rows? + +- Isolation Forest is **not a time-series model** — it takes a single point in feature space +- Z-score also operates on aggregated values +- 8 features instead of 120×8=960 raw values = faster + less noise + +--- + +### `zscore_detector.py` + +```python +class ZScoreDetector: + def __init__(self, + zscore_window=1800, # seconds of rolling baseline + trend_window=600, # seconds for slope calculation + sustained_sec=30, # spike must last this long + z_threshold=2.8, + slope_threshold=0.003, + sustained_ratio=0.6) +``` + +```python + def update(self, val: float) -> None +``` + +**Purpose:** Appends new reading to all internal deques (zscore, trend, sustained). + +```python + def check(self, val: float, metric_name: str, unit: str) -> list[dict] +``` + +**Purpose:** Runs three independent checks and returns list of detected issues. + +**Check 1 — Z-score (sudden spike):** + +``` +Z = (current_value - rolling_mean) / rolling_std +If |Z| > 2.8 AND spike sustained for 30s → ALERT +``` + +Catches: CPU suddenly 35% → 95% + +**Check 2 — Slope/Trend (slow drift):** + +```python +slope = np.polyfit(time_axis, values, 1)[0] # linear regression +If slope > 0.003 %/sec → ALERT (memory leak pattern) +``` + +Catches: Memory slowly 40% → 80% over 2 hours (Z-score misses this) +Bonus: predicts `ETA to critical = (90 - current) / slope / 60` minutes + +**Check 3 — Sustained filter (false positive prevention):** + +``` +If < 60% readings above threshold in last 30s → skip (compilation burst) +If ≥ 60% readings above threshold → real anomaly +``` + +Prevents: Short CPU bursts from `gcc` compilation triggering false alerts + +**Why Z-score alone is not enough:** + +- Rolling window adapts to slow drift → memory leak goes undetected +- Single-metric check → misses multi-metric correlations +- Solution: Z-score + Slope + Isolation Forest together + +--- + +### `anomaly_model.py` + +```python +def train(normal_feature_vectors: np.ndarray) -> IsolationForest +``` + +**Purpose:** Trains Isolation Forest on normal telemetry data only. +`contamination=0.05` means we expect ~5% of data to be anomalous. + +```python +def predict(model: IsolationForest, feature_vector: list) -> tuple[int, float] +``` + +**Purpose:** Returns `(label, score)` where: + +- `label = 1` → normal +- `label = -1` → anomaly +- `score` → negative = more anomalous + +**How Isolation Forest works:** +Isolation Forest is an **unsupervised algorithm that detects anomalies in a feature space** — NOT a time-series model. + +Core intuition: + +- Normal points cluster together → many random splits needed to isolate them +- Anomaly points are sparse → isolated with very few splits +- Points isolated in fewer splits = higher anomaly score + +``` +Normal [cpu=35, mem=55, disk=2]: deep in tree → many splits → normal +Anomaly [cpu=40, mem=40, disk=40, net=40]: shallow in tree → few splits → anomaly +``` + +Key advantage over Z-score: detects **multi-metric combinations** that are individually normal but collectively anomalous. + +Example: `CPU=40%, MEM=40%, DISK=40%, NET=40%` — each metric looks fine individually, but this combination never appears in normal usage . + +--- + +### `rule_engine.py` + +```python +def check_rules(metrics: dict) -> list[dict] +``` + +**Purpose:** Deterministic backup checks — always runs alongside ML models. +Catches obvious anomalies even before warmup period ends. + +```python +RULES = [ + {"condition": lambda m: m["cpu"] > 95, "alert": "cpu_critical"}, + {"condition": lambda m: m["thread_growth_rate"] > 200, "alert": "thread_explosion"}, + {"condition": lambda m: m["zombie_count"] > 10, "alert": "zombie_buildup"}, + {"condition": lambda m: m["memory_growth_continuous"], "alert": "memory_leak"}, +] +``` + +Why rule engine alongside ML? + +- ML models need warmup period (60s) → rules work from second 1 +- Rules are 100% deterministic → never fail unexpectedly +- Double detection = higher confidence alerts + +--- + +### `correlation.py` + +```python +def build_event_chain(events: list[dict]) -> list[dict] +``` + +**Purpose:** Takes raw timestamped events and builds a CAUSE → EFFECT chain +by correlating events that occurred close together in time. + +Example output: + +``` +Chrome opened (10:01) + ↓ +Renderer processes +12 (10:02) + ↓ +Thread count +340 in 60s (10:03) + ↓ +CPU saturation 87% (10:04) + ↓ +Isolation Forest: ANOMALY -1 (10:05) + ↓ +System freeze detected (10:05) +``` + +```python +def telemetry_to_events(rows: list[dict]) -> list[dict] +``` + +**Purpose:** Converts raw telemetry rows into discrete events with type + timestamp. + +```python +{"type": "cpu_spike", "time": "10:03:45", "detail": "cpu jumped to 87%"} +``` + +--- + +### `replay.py` + +```python +def replay(conn, crash_time: float, window_minutes: int = 30) -> list[dict] +``` + +**Purpose:** Fetches all telemetry rows from `crash_time - window_minutes` to `crash_time`. +Returns ordered list for dashboard timeline visualization. + +```python +def generate_narrative(events: list[dict], anomaly_type: str) -> str +``` + +**Purpose:** Formats the event chain into a structured JSON for LLM input. + +--- + +### `nl_query.py` + +```python +def query(conn, question: str) -> str +``` + +**Purpose:** Accepts free-text questions about crash data and returns answers +by querying SQLite + formatting context for LLM. + +Example: + +``` +Input: "Which process caused the freeze at 10:05?" +Output: "Chrome (PID 1823) spawned 340 additional threads between 10:01-10:04, + causing CPU saturation that led to the system freeze." +``` + +--- + +## 4. Useful Telemetry Data + +BlackBox consumes data from all 4 collector layers: + +### From `layer1_system` (Layer 1 — every 1s) + +| Column | Use in BlackBox | +| ------------------- | --------------------------------------- | +| `cpu_usage_percent` | Primary Z-score metric, spike detection | +| `memory_percent` | Memory leak slope detection | +| `disk_read_mb_s` | I/O storm detection | +| `net_rate_mb_s` | Network exfiltration detection | +| `cpu_ctx_switches` | Scheduler congestion (feature vector) | +| `total_processes` | Thread explosion detection | +| `zombie_processes` | Zombie accumulation rule check | +| `load_avg_1` | Scheduler load feature | +| `swap_percent` | Memory pressure feature | + +### From `layer2_top_processes` (Layer 2 — every 5s) + +| Column | Use in BlackBox | +| ------------- | ---------------------------------------------- | +| `pid`, `name` | Identify culprit process in correlation engine | +| `cpu_percent` | Which process caused CPU spike | +| `rss_memory` | Which process is leaking memory | +| `timestamp` | Timeline correlation | + +### From `process_metadata` (Layer 3 — on first seen) + +| Column | Use in BlackBox | +| ------------- | -------------------------------------- | +| `cmdline` | What exact command was running | +| `exe_path` | Where executable came from | +| `username` | Who launched the process | +| `create_time` | When process started relative to crash | + +### From `process_diagnostics` (Layer 4 — on anomaly) + +| Column | Use in BlackBox | +| ------------------ | ---------------------------------------- | +| `open_files_count` | File handle leak detection | +| `thread_details` | Which threads were consuming CPU | +| `net_connections` | Active network connections at crash time | +| `trigger_reason` | Why Layer 4 was triggered | + +### Rolling window SQL + +```sql +-- Fetch last 2 minutes for feature extraction +SELECT cpu_usage_percent, memory_percent, + disk_read_mb_s, net_rate_mb_s, + total_processes, cpu_ctx_switches +FROM layer1_system +ORDER BY timestamp DESC +LIMIT 120; + +--Trim to 30-minute window +DELETE FROM telemetry +WHERE timestamp < (strftime('%s', 'now') - 1800); +``` + +--- + +## 5. Additional Info + +### Config values (from `utils/config.py`) + +```python +# BlackBox — all tunable from one place (mentor requirement) +BLACKBOX_WINDOW_SEC = 1800 # rolling window duration +BLACKBOX_WARMUP_SEC = 60 # seconds before detection starts +BLACKBOX_Z_THRESHOLD = 2.8 # standard deviations +BLACKBOX_SLOPE_THRESHOLD = 0.003 # %/sec rising = suspicious +BLACKBOX_SUSTAINED_SEC = 30 # spike must last this long +BLACKBOX_SUSTAINED_RATIO = 0.6 # 60% readings must be above threshold +BLACKBOX_TREND_WINDOW = 600 # 10 min for slope calculation +``` + +### Key design decisions + +**Why SQLite over other storage?** +Lightweight, local, crash-resistant (data survives daemon crash), queryable via SQL, replay-friendly. + +**Why rolling 30-minute window?** +30 minutes of pre-crash context is sufficient for root-cause analysis. Longer window = more disk usage with diminishing returns. + +**Why Isolation Forest and not supervised model?** +No labeled anomaly dataset exists initially. IF is unsupervised — trained on normal data only, no labels needed. + +**Why Z-score threshold 2.8 and not 3.0?** +3.0 is the classic threshold but at 3.0, a developer's laptop (higher baseline CPU) barely crossed the threshold for genuine spikes. 2.8 gives slightly better sensitivity while keeping false positives low with the sustained filter. From 37f1abd61e448337b00e13b270161f194623d617 Mon Sep 17 00:00:00 2001 From: parshvigarg3 Date: Tue, 30 Jun 2026 01:36:32 +0530 Subject: [PATCH 27/47] updates in objectives --- .DS_Store | Bin 0 -> 8196 bytes os_doctor/documentation.md | 20 +++++++------------- 2 files changed, 7 insertions(+), 13 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..029c4baf92be81f9e8a8f9a6dcbb419de723259a GIT binary patch literal 8196 zcmeHM!EVz)5S>j^ViN&56x0inC9YLz3#F>KgwP&1aA^=602#YBsa4l5vC|Y)m2!rE z;1{^^Bk(Vr;LX^LY{x~M5K4EY-M6;q?Rwwtjx!q~QoWJ4PP9x!1~Ox(jiM&;bIvoV zk>9xkRNxcslY@LfKG`$Y`i53OE1(t73TOqi0{?>o_-1o5F8S_fPj#sk&+LmlHS3y#GF(&LtSbGv;wmVaO~bjv~JTeeJsWA_OmeFcf;6)1#hY+$g963 zp~go7E`<{BAEFl1X~q6)#eNB`$26db0$_q39VtDaPe-^H(JH@1xgB-4(#~~hCq&;q zYAJX_N-CV6f5UOd$#FiuZ|-pzB!YrcxMz9X<=Ss%+LsWV`r50llA`x>p4-eqCrK=7W{*QwOj8Yco!5EGFe6V z7f)0b4^h#S{X>z_hUE$T$xzNN9WZM=k?fHo%cMQp#roc%=fG9GW8EGpGCJtNrw^Fv zKx#+0ir~W%uv0pvwEUjDcht?o-nk{L1T5Z>e9~%53j@){uduPsO6*1So5C`jSz}PJ zWC?k6ntKHnvJozG{J(qo`TyMOpa-ZGxXKEs=1za72L&#EW>&@ESldE=hs=rXCK(C^ tg@ofk3CDrcKMYZ~V9K0sj*|>=2kD<51R$1C`ukss?Uerh%UZlj#cxGN_?!R$ literal 0 HcmV?d00001 diff --git a/os_doctor/documentation.md b/os_doctor/documentation.md index 62018d2..c4c4741 100644 --- a/os_doctor/documentation.md +++ b/os_doctor/documentation.md @@ -3,24 +3,18 @@ Real-Time Intelligent Operating System Monitoring & Anomaly Detection System Project Documentation -Team Members : -Guide : -Institute : -Date : -Version : - 1. Introduction -OSDoctor is an intelligent observability system that continuously monitors operating system telemetry, detects abnormal system behavior using machine learning, identifies the likely root cause, and explains the issue in natural language. Its objective is to answer the question: “Why is my laptop slow right now?” This aligns with the project objective described in the proposal. +OSDoctor is an intelligent observability system that continuously monitors operating system telemetry, detects abnormal system behavior using machine learning, identifies the likely root cause, and explains the issue in natural language. Its objective is to answer the question: “Why is my laptop slow right now?” 2. Objectives -* Collect live OS telemetry -* Store telemetry efficiently -* Engineer useful features -* Detect anomalies -* Explain detected issues -* Display results on dashboard +- Monitor real-time system telemetry. +- Detect system anomalies automatically. +- Identify causes of performance issues. +- Detect CPU, memory, disk, and process-related faults. +- Explain issues in simple, human-readable language. +- Provide actionable alerts and recommendations. 3. System Architecture From 659a17e2ae62cc54c27ad41969fa32fc10b934a1 Mon Sep 17 00:00:00 2001 From: parshvigarg3 Date: Tue, 30 Jun 2026 12:06:27 +0530 Subject: [PATCH 28/47] remove .DS_Store --- .DS_Store | Bin 8196 -> 0 bytes .gitignore | 1 + 2 files changed, 1 insertion(+) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 029c4baf92be81f9e8a8f9a6dcbb419de723259a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHM!EVz)5S>j^ViN&56x0inC9YLz3#F>KgwP&1aA^=602#YBsa4l5vC|Y)m2!rE z;1{^^Bk(Vr;LX^LY{x~M5K4EY-M6;q?Rwwtjx!q~QoWJ4PP9x!1~Ox(jiM&;bIvoV zk>9xkRNxcslY@LfKG`$Y`i53OE1(t73TOqi0{?>o_-1o5F8S_fPj#sk&+LmlHS3y#GF(&LtSbGv;wmVaO~bjv~JTeeJsWA_OmeFcf;6)1#hY+$g963 zp~go7E`<{BAEFl1X~q6)#eNB`$26db0$_q39VtDaPe-^H(JH@1xgB-4(#~~hCq&;q zYAJX_N-CV6f5UOd$#FiuZ|-pzB!YrcxMz9X<=Ss%+LsWV`r50llA`x>p4-eqCrK=7W{*QwOj8Yco!5EGFe6V z7f)0b4^h#S{X>z_hUE$T$xzNN9WZM=k?fHo%cMQp#roc%=fG9GW8EGpGCJtNrw^Fv zKx#+0ir~W%uv0pvwEUjDcht?o-nk{L1T5Z>e9~%53j@){uduPsO6*1So5C`jSz}PJ zWC?k6ntKHnvJozG{J(qo`TyMOpa-ZGxXKEs=1za72L&#EW>&@ESldE=hs=rXCK(C^ tg@ofk3CDrcKMYZ~V9K0sj*|>=2kD<51R$1C`ukss?Uerh%UZlj#cxGN_?!R$ diff --git a/.gitignore b/.gitignore index 6b08a83..210860c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ data/traces/* # Keep data directories present in git if marker files are added. !data/models/.gitkeep !data/traces/.gitkeep +.DS_Store From 57bfcc0da677a048fc86a69d39920f77712fb4f5 Mon Sep 17 00:00:00 2001 From: parshvigarg3 Date: Tue, 30 Jun 2026 13:43:24 +0530 Subject: [PATCH 29/47] editions --- os_doctor/documentation.md | 173 ++++++++++++------------------------- 1 file changed, 54 insertions(+), 119 deletions(-) diff --git a/os_doctor/documentation.md b/os_doctor/documentation.md index c4c4741..1d050e7 100644 --- a/os_doctor/documentation.md +++ b/os_doctor/documentation.md @@ -19,122 +19,57 @@ OSDoctor is an intelligent observability system that continuously monitors opera 3. System Architecture -# OSDoctor: Core Pipeline Architecture - -This document describes the linear data flow and core components of the OSDoctor observability engine as designed. - -## 1. System Architecture Diagram - -```mermaid -graph TD - %% Telemetry Collection Layer - Collector[Telemetry Collector] --> System[System Telemetry] - Collector --> Process[Per Process Telemetry] - - %% Database Interaction - System --> DB_Script[db.py] - Process --> DB_Script - DB_Script --> SQLite[(SQL Lite Database)] - - %% Processing Pipeline - SQLite --> FeatureEng[Feature Engineering] - FeatureEng --> IForest[Isolation Forest] - - %% Evaluation Logic - IForest -->|No| Nothing[Nothing] - IForest -->|Yes| AppendAlert[Append to Alerts Table] - - %% Output Generation & UI - AppendAlert --> LLMLayer[LLM Layer] - LLMLayer --> Streamlit[Streamlit UI] - - %% Custom Styling for Visual Clarity - style Collector fill:#f4f4f4,stroke:#333,stroke-width:2px - style IForest fill:#e1f5fe,stroke:#0288d1,stroke-width:2px - style AppendAlert fill:#ffebee,stroke:#c62828,stroke-width:2px - - 2. Pipeline Step Descriptions -Step 1: Telemetry Collector (System & Per-Process) -• The entry point of the pipeline splits data gathering into two concurrent streams: -• System Telemetry: Monitors macro-level hardware usage across the entire operating system. -• Per-Process Telemetry: Tracks individual application metrics to identify localized resource usage. - -Step 2: Database Pipeline (db.py ➔ SQL Lite) -• Both telemetry feeds pass synchronously into the central database management script (db.py). -• The database script handles formatting, opens a transaction, and writes the metric frames safely into persistent storage inside the SQL Lite engine. - -Step 3: Feature Engineering -• This step reads raw performance rows from SQL Lite and transforms them into predictive indicators. -• It computes dynamic trends, smooths out sudden system noise, and prepares structured multi-dimensional state vectors for the machine learning layer. - -Step 4: Isolation Forest Inference -• The engineered metrics enter the Isolation Forest unsupervised learning model. -• The pipeline evaluates the model's output via a binary branch: -• No Anomaly (Output: 1): The execution stops instantly and loops back (Nothing). -• Anomaly Detected (Output: -1): The execution is flagged as Yes and proceeds downstream. - -Step 5: Append to Alerts Table -• The moment an anomaly is confirmed, the system generates an incident log. -• This record, containing key anomaly flags and metrics data, is immediately written to the specialized alerts table in the database. - -Step 6: LLM Layer -• The LLM Layer triggers automatically when a new record appears in the alerts table. -• It processes the structured metric telemetry data and translates it into natural language explanations covering cause, severity, and mitigation paths. - -Step 7: Streamlit UI Presentation -• The final human-readable report and live performance timeline streams are loaded directly into the front-end dashboard. -• The Streamlit user interface displays recommendations and reactive alerts to let end-users know exactly why their machine is slow. - - -4. Project Workflow - -5. File Structure - -[span_0](start_span)[span_1](start_span)[span_2](start_span)[span_3](start_span)This document outlines the standard Python project layout designed to map your pipeline architecture components into distinct modules and files[span_0](end_span)[span_1](end_span)[span_2](end_span)[span_3](end_span). - -## 1. Directory Layout Diagram - -```mermaid -graph TD - Project[os_doctor_project/] --> Src[src/] - Project --> Requirements[requirements.txt] - Project --> README[README.md] - - %% Collectors - Src --> CollDir[collectors/] - CollDir --> SysColl[system_collector.py] - CollDir --> ProcColl[process_collector.py] - CollDir --> DeepColl[deep_diagnostics.py] - - %% Core Processing & ML - Src --> EngineDir[engine/] - EngineDir --> DB[db.py] - EngineDir --> Feature[feature.py] - EngineDir --> IForest[i_forest.py] - EngineDir --> Tagger[tagger.py] - - %% Analysis & Frontend - Src --> WorkersDir[workers/] - WorkersDir --> LLMExec[llm_executor.py] - - Src --> AppUI[app.py] - - %% Styling - style Project fill:#f5f5f5,stroke:#333,stroke-width:2px - style SysColl fill:#e8f5e9,stroke:#2e7d32 - style ProcColl fill:#e8f5e9,stroke:#2e7d32 - style DB fill:#fff3e0,stroke:#ef6c00 - style IForest fill:#e1f5fe,stroke:#0288d1 - style LLMExec fill:#f3e5f5,stroke:#6a1b9a - style AppUI fill:#ffebee,stroke:#c62828 - - -6. Module Description -7. Function Documentation -8. Database Design -9. Feature Engineering -10. Machine Learning Model -11. LLM Explanation Layer -12. Dashboard -13. Installation -14. Future Scope +```text +CogniOS Telemetry Collector + │ + ▼ +OSDoctor (Data Extraction) + │ + ▼ +System Metrics + Process Metrics + │ + ▼ +SQLite Database + │ + ▼ +Feature Engineering + │ + ▼ +Isolation Forest + │ + ├── No Anomaly + │ │ + │ ▼ + │ Continue Monitoring + │ + └── Anomaly Detected + │ + ▼ + Alerts Table + │ + ▼ + LLM Explanation Layer + │ + ▼ + Streamlit Dashboard +``` + +4. File Structure + +os_doctor/ +│── _init_.py # Marks the directory as a Python package +│── featuring.py # Feature engineering and preprocessing +│── i_forest.py # Isolation Forest model implementation +│── llm_layer.py # LLM integration and response generation +│── streamlit.py # Streamlit web application +│── README.md # Project overview and setup guide + + + +5. Function Documentation +6. Feature Engineering +7. Machine Learning Model +8. LLM Explanation Layer +9. Dashboard +10. Installation +11. Future Scope From c4f7003b3ef6776391afb0a5a49af66ccb8c8e74 Mon Sep 17 00:00:00 2001 From: parshvigarg3 Date: Tue, 30 Jun 2026 13:52:24 +0530 Subject: [PATCH 30/47] update --- os_doctor/documentation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/os_doctor/documentation.md b/os_doctor/documentation.md index 1d050e7..e749e36 100644 --- a/os_doctor/documentation.md +++ b/os_doctor/documentation.md @@ -56,6 +56,7 @@ Isolation Forest 4. File Structure +```text os_doctor/ │── _init_.py # Marks the directory as a Python package │── featuring.py # Feature engineering and preprocessing @@ -63,7 +64,7 @@ os_doctor/ │── llm_layer.py # LLM integration and response generation │── streamlit.py # Streamlit web application │── README.md # Project overview and setup guide - +``` 5. Function Documentation From 0471550d257e866e304751b5b07ac6ecc0c5a89b Mon Sep 17 00:00:00 2001 From: parshvigarg3 Date: Tue, 30 Jun 2026 14:00:22 +0530 Subject: [PATCH 31/47] Update README --- os_doctor/README.md | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/os_doctor/README.md b/os_doctor/README.md index e69de29..55b9e17 100644 --- a/os_doctor/README.md +++ b/os_doctor/README.md @@ -0,0 +1,76 @@ +OSDoctor +Real-Time Intelligent Operating System Monitoring & Anomaly Detection System + +Project Documentation + +1. Introduction + +OSDoctor is an intelligent observability system that continuously monitors operating system telemetry, detects abnormal system behavior using machine learning, identifies the likely root cause, and explains the issue in natural language. Its objective is to answer the question: “Why is my laptop slow right now?” + +2. Objectives + +- Monitor real-time system telemetry. +- Detect system anomalies automatically. +- Identify causes of performance issues. +- Detect CPU, memory, disk, and process-related faults. +- Explain issues in simple, human-readable language. +- Provide actionable alerts and recommendations. + + +3. System Architecture + +```text +CogniOS Telemetry Collector + │ + ▼ +OSDoctor (Data Extraction) + │ + ▼ +System Metrics + Process Metrics + │ + ▼ +SQLite Database + │ + ▼ +Feature Engineering + │ + ▼ +Isolation Forest + │ + ├── No Anomaly + │ │ + │ ▼ + │ Continue Monitoring + │ + └── Anomaly Detected + │ + ▼ + Alerts Table + │ + ▼ + LLM Explanation Layer + │ + ▼ + Streamlit Dashboard +``` + +4. File Structure + +```text +os_doctor/ +│── _init_.py # Marks the directory as a Python package +│── featuring.py # Feature engineering and preprocessing +│── i_forest.py # Isolation Forest model implementation +│── llm_layer.py # LLM integration and response generation +│── streamlit.py # Streamlit web application +│── README.md # Project overview and setup guide +``` + + +5. Function Documentation +6. Feature Engineering +7. Machine Learning Model +8. LLM Explanation Layer +9. Dashboard +10. Installation +11. Future Scope \ No newline at end of file From 56ac5ade425f3f5a132446a0ebbe52a54d47712a Mon Sep 17 00:00:00 2001 From: Abhishek Reddy Date: Tue, 30 Jun 2026 17:54:06 +0530 Subject: [PATCH 32/47] made changes to layer2 collector, db.py, test.py these changes are made to accommodate appending json object --- collectors/layer2_process.py | 146 ++++++++++++++++++++++++++--------- db.py | 120 +++++++--------------------- test.py | 38 ++++----- 3 files changed, 154 insertions(+), 150 deletions(-) diff --git a/collectors/layer2_process.py b/collectors/layer2_process.py index 2414894..58ea9ee 100644 --- a/collectors/layer2_process.py +++ b/collectors/layer2_process.py @@ -2,84 +2,158 @@ import time import psutil + def collect_process_telemetry(prev_states=None): if prev_states is None: prev_states = {} + # --- Phase 0: CPU prime pass (t=0) --- + # First cpu_percent call per Process object always returns 0.0 or a stale + # cumulative rate. Discarding it gives the 5 sampling passes a clean 1-second + # baseline each. + for proc in psutil.process_iter(['pid']): + try: + proc.cpu_percent(interval=None) + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + continue + + # --- Phase 1: 5 × 1-second CPU + RAM sampling --- + cpu_samples = {} # pid -> [cpu_percent, ...] + ram_samples = {} # pid -> [rss_mb, ...] + for _ in range(5): + time.sleep(1.0) + for proc in psutil.process_iter(['pid']): + try: + pid = proc.pid + cpu_samples.setdefault(pid, []).append(proc.cpu_percent(interval=None)) + try: + ram_samples.setdefault(pid, []).append( + round(proc.memory_info().rss / (1024 * 1024), 3) + ) + except (psutil.AccessDenied, AttributeError): + pass + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + continue + + # --- Phase 2: Full metadata pass + aggregation --- + current_time = time.time() current_states = {} processed_snapshots = [] - current_time = time.time() - for proc in psutil.process_iter(['pid', 'name', 'num_threads', 'status']): + for proc in psutil.process_iter(['pid', 'name', 'num_threads', 'status', 'create_time']): try: info = proc.info pid = info['pid'] - - cpu_percent = proc.cpu_percent(interval=None) + + if pid not in cpu_samples: + continue + mem_info = proc.memory_info() mem_percent = proc.memory_percent() - + try: - io_counters = proc.io_counters() - read_bytes = io_counters.read_bytes - write_bytes = io_counters.write_bytes + io = proc.io_counters() + read_bytes, write_bytes = io.read_bytes, io.write_bytes except (psutil.AccessDenied, AttributeError): read_bytes, write_bytes = 0, 0 - current_states[pid] = { - 'read_bytes': read_bytes, - 'write_bytes': write_bytes, - 'timestamp': current_time - } + try: + ctx = proc.num_ctx_switches() + ctx_vol = ctx.voluntary + ctx_invol = ctx.involuntary + except (psutil.AccessDenied, AttributeError): + ctx_vol, ctx_invol = 0, 0 + + try: + open_fds = proc.num_fds() + except (psutil.AccessDenied, AttributeError): + open_fds = 0 + + try: + net_conn_count = len(proc.net_connections()) + except (psutil.AccessDenied, AttributeError): + net_conn_count = 0 calc_read_rate = 0.0 calc_write_rate = 0.0 + calc_ctx_vol_sec = 0.0 + calc_ctx_invol_sec = 0.0 if pid in prev_states: prev = prev_states[pid] - time_delta = current_time - prev['timestamp'] - if time_delta > 0: - calc_read_rate = max(0.0, (read_bytes - prev['read_bytes']) / time_delta) - calc_write_rate = max(0.0, (write_bytes - prev['write_bytes']) / time_delta) + dt = current_time - prev['timestamp'] + if dt > 0: + calc_read_rate = max(0.0, (read_bytes - prev['read_bytes']) / dt) + calc_write_rate = max(0.0, (write_bytes - prev['write_bytes']) / dt) + calc_ctx_vol_sec = max(0.0, (ctx_vol - prev['ctx_vol']) / dt) + calc_ctx_invol_sec = max(0.0, (ctx_invol - prev['ctx_invol']) / dt) + + current_states[pid] = { + 'read_bytes': read_bytes, + 'write_bytes': write_bytes, + 'ctx_vol': ctx_vol, + 'ctx_invol': ctx_invol, + 'timestamp': current_time, + } + + cpu_s = cpu_samples[pid] + cpu_avg = round(sum(cpu_s) / len(cpu_s), 2) + cpu_peak = round(max(cpu_s), 2) + cpu_score = round(0.6 * cpu_peak + 0.4 * cpu_avg, 2) + + ram_s = ram_samples.get(pid, []) + if ram_s: + ram_avg = round(sum(ram_s) / len(ram_s), 3) + ram_peak = round(max(ram_s), 3) + ram_score = round(0.6 * ram_peak + 0.4 * ram_avg, 3) + rss_mb_now = ram_s[-1] + else: + rss_mb_now = round(mem_info.rss / (1024 * 1024), 3) + ram_avg = ram_peak = ram_score = rss_mb_now processed_snapshots.append({ 'pid': pid, 'name': info['name'] or 'Unknown', - 'cpu_percent': round(cpu_percent, 2), + 'cpu_percent': round(cpu_s[-1], 2), + 'cpu_avg': cpu_avg, + 'cpu_peak': cpu_peak, + 'cpu_score': cpu_score, 'memory_percent': round(mem_percent, 2), - 'rss_memory': mem_info.rss / (1024 * 1024), # MB - 'vms_memory': mem_info.vms / (1024 * 1024 * 1024), # GB + 'rss_mb': rss_mb_now, + 'ram_avg': ram_avg, + 'ram_peak': ram_peak, + 'ram_score': ram_score, + 'vms_gb': round(mem_info.vms / (1024 * 1024 * 1024), 4), 'thread_count': info['num_threads'] or 1, 'read_bytes_sec': round(calc_read_rate, 2), 'write_bytes_sec': round(calc_write_rate, 2), - 'status': info['status'] or 'unknown' + 'status': info['status'] or 'unknown', + 'age_sec': round(current_time - (info['create_time'] or current_time), 1), + 'open_fds': open_fds, + 'ctx_vol_sec': round(calc_ctx_vol_sec, 2), + 'ctx_invol_sec': round(calc_ctx_invol_sec, 2), + 'net_conn_count': net_conn_count, }) except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): continue - # Slice out the two lists independently - top_cpu = sorted(processed_snapshots, key=lambda x: x['cpu_percent'], reverse=True)[:5] - top_mem = sorted(processed_snapshots, key=lambda x: x['rss_memory'], reverse=True)[:5] + top_cpu = sorted(processed_snapshots, key=lambda x: x['cpu_score'], reverse=True)[:5] + top_mem = sorted(processed_snapshots, key=lambda x: x['ram_score'], reverse=True)[:5] - # Return as a structured tuple return top_cpu, top_mem, current_states if __name__ == '__main__': - from db import init_db, insert_separated_telemetry - + from db import init_db, insert_process_snapshot + init_db() baselines = {} - print("CogniOS Separated Telemetry Daemon started...") - + print("CogniOS Telemetry Daemon started. Press Ctrl+C to stop.") + try: while True: top_cpu, top_mem, baselines = collect_process_telemetry(baselines) - - # Pass both lists cleanly to our data layer - insert_separated_telemetry(top_cpu, top_mem) - - print(f"Committed Top 5 CPU and Top 5 Memory snapshots.") - time.sleep(5) + insert_process_snapshot(top_cpu, top_mem) + print(f"Snapshot committed at t={time.time():.0f} | top_cpu={top_cpu[0]['name']} score={top_cpu[0]['cpu_score']}") except KeyboardInterrupt: - print("\nDaemon safely terminated.") \ No newline at end of file + print("\nDaemon safely terminated.") diff --git a/db.py b/db.py index 369f6bc..e6c392a 100644 --- a/db.py +++ b/db.py @@ -1,107 +1,45 @@ """Shared database schema and read/write interface.""" -import sqlite3 # for both layers -import time # for layer 2 -from config import DB_PATH # for layer 1 +import json +import sqlite3 +import time +from config import DB_PATH -# layer 2 db code starts here +# layer 2 db code starts here -DB_NAME = "cognios_telemetry.db" def init_db(): - """Initializes separate structural tables for CPU and Memory metrics.""" - with sqlite3.connect(DB_NAME) as conn: - cursor = conn.cursor() - - # Table 1: Dedicated CPU telemetry - cursor.execute(""" - CREATE TABLE IF NOT EXISTS top_cpu_telemetry ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp REAL NOT NULL, - pid INTEGER NOT NULL, - name TEXT NOT NULL, - cpu_percent REAL NOT NULL, - memory_percent REAL NOT NULL, - rss_memory INTEGER NOT NULL, - vms_memory INTEGER NOT NULL, - thread_count INTEGER NOT NULL, - read_bytes_sec REAL NOT NULL, - write_bytes_sec REAL NOT NULL, - status TEXT NOT NULL + """Initializes the unified process_snapshot table for Layer 2 telemetry.""" + with sqlite3.connect(DB_PATH) as conn: + conn.execute(""" + CREATE TABLE IF NOT EXISTS process_snapshot ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp REAL NOT NULL, + top_cpu_processes TEXT NOT NULL, + top_ram_processes TEXT NOT NULL ) """) - - # Table 2: Dedicated RAM telemetry - cursor.execute(""" - CREATE TABLE IF NOT EXISTS top_ram_telemetry ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp REAL NOT NULL, - pid INTEGER NOT NULL, - name TEXT NOT NULL, - cpu_percent REAL NOT NULL, - memory_percent REAL NOT NULL, - rss_memory INTEGER NOT NULL, - vms_memory INTEGER NOT NULL, - thread_count INTEGER NOT NULL, - read_bytes_sec REAL NOT NULL, - write_bytes_sec REAL NOT NULL, - status TEXT NOT NULL - ) + conn.execute(""" + CREATE INDEX IF NOT EXISTS idx_snapshot_ts + ON process_snapshot (timestamp) """) conn.commit() -def _map_to_rows(process_list, current_timestamp): - """Helper function to transform list of dicts to flat tuples for SQLite.""" - return [ - ( - current_timestamp, - p['pid'], - p['name'], - p['cpu_percent'], - p['memory_percent'], - p['rss_memory'], - p['vms_memory'], - p['thread_count'], - p['read_bytes_sec'], - p['write_bytes_sec'], - p['status'] +def insert_process_snapshot(top_cpu, top_ram): + """Writes one unified row per 5-second poll — timestamp + two compact JSON arrays.""" + row = ( + time.time(), + json.dumps(top_cpu, separators=(',', ':')), + json.dumps(top_ram, separators=(',', ':')) + ) + with sqlite3.connect(DB_PATH) as conn: + conn.execute( + "INSERT INTO process_snapshot (timestamp, top_cpu_processes, top_ram_processes) VALUES (?, ?, ?)", + row ) - for p in process_list - ] - - -def insert_separated_telemetry(top_cpu, top_mem): - """ - Inserts data cleanly into their respective tables. - Even if a process exists in both lists, it is safely recorded - in both metrics tables under the same time block window. - """ - current_timestamp = time.time() - - # Map the dictionaries into raw tuple rows - cpu_rows = _map_to_rows(top_cpu, current_timestamp) - ram_rows = _map_to_rows(top_mem, current_timestamp) - - insert_query = """ - INSERT INTO {} ( - timestamp, pid, name, cpu_percent, memory_percent, - rss_memory, vms_memory, thread_count, read_bytes_sec, - write_bytes_sec, status - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """ - - with sqlite3.connect(DB_NAME) as conn: - cursor = conn.cursor() - - - if cpu_rows: - cursor.executemany(insert_query.format("top_cpu_telemetry"), cpu_rows) - - if ram_rows: - cursor.executemany(insert_query.format("top_ram_telemetry"), ram_rows) - conn.commit() -# layer 2 db code ends here + +# layer 2 db code ends here # layer 1 db code starts here diff --git a/test.py b/test.py index ec4aad2..716377d 100644 --- a/test.py +++ b/test.py @@ -1,29 +1,21 @@ -"Testing Document" +"""CogniOS Layer 2 telemetry daemon entry point.""" import time from collectors.layer2_process import collect_process_telemetry -from db import init_db, insert_separated_telemetry +from db import init_db, insert_process_snapshot -def test_layer2_pipeline(): - print("Initializing CogniOS Test Database...") +if __name__ == "__main__": init_db() - baselines = {} - print("Running initial telemetry sweep (Baseline generation)...") - top_cpu, top_mem, baselines = collect_process_telemetry(baselines) - - # Wait 5 seconds to simulate a real monitoring pulse - print("Pacing for 5 seconds to calculate real delta rates...") - time.sleep(5) - - print("Running second telemetry sweep...") - top_cpu, top_mem, baselines = collect_process_telemetry(baselines) - - print(f"Top CPU Process Count: {len(top_cpu)}") - print(f"Top RAM Process Count: {len(top_mem)}") - - print("Writing captured data to separate SQLite tables...") - insert_separated_telemetry(top_cpu, top_mem) - print("Success! Check your root directory for 'cognios_telemetry.db'.") + print("CogniOS Telemetry Daemon started. Press Ctrl+C to stop.") -if __name__ == "__main__": - test_layer2_pipeline() \ No newline at end of file + try: + while True: + top_cpu, top_mem, baselines = collect_process_telemetry(baselines) + insert_process_snapshot(top_cpu, top_mem) + print( + f"Snapshot committed at t={time.time():.0f} | " + f"top_cpu={top_cpu[0]['name']} score={top_cpu[0]['cpu_score']} | " + f"top_ram={top_mem[0]['name']} score={top_mem[0]['ram_score']}" + ) + except KeyboardInterrupt: + print("\nDaemon safely terminated.") \ No newline at end of file From 61e8e57c380d307755210570bfefc7fdd9f288e6 Mon Sep 17 00:00:00 2001 From: Abhishek Reddy Date: Tue, 30 Jun 2026 18:24:53 +0530 Subject: [PATCH 33/47] Updated README.md for doctor-os Added function descriptions for feature engineering file and overview of what the file does --- os_doctor/README.md | 66 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/os_doctor/README.md b/os_doctor/README.md index 55b9e17..abd2aeb 100644 --- a/os_doctor/README.md +++ b/os_doctor/README.md @@ -69,6 +69,72 @@ os_doctor/ 5. Function Documentation 6. Feature Engineering + +## Module Overview +The `feature.py` script serves as the core mathematical transformation layer for the **OS Doctor Anomaly Engine**. Its primary responsibility is to bridge the gap between low-frequency/high-frequency transactional data stored in the local SQLite database and the high-dimensional, uniform matrices required by the **Isolation Forest** machine learning pipeline. + +The module continuously processes a rolling **2-minute sliding window** of system and process performance telemetry, cleans missing data, applies advanced statistical transformations (rolling averages, gradients, and rates of change), and generates a unified vector payload for real-time anomaly detection. + +--- + +## Libraries Used + +### 1 pandas +We use pandas for faster vector calculations leveraging it's built in functions like rolling(), shift() and many more. + +### 2 sqlite3 +We use sqlite3 library to handle the telemetry database created prior. + +### 3 json +We use json library to parse the json object created in layer2. + +## Core Telemetry Pipeline Alignment Matrix + +Because our telemetry collection infrastructure drops metrics down at asynchronous cadences, this script enforces chronological structure across distinct dimensional boundaries: + +| Data Layer | Source Table | DB CADENCE | Target Window Scope | Base Matrix Shape | +| :--- | :--- | :--- | :--- | :--- | +| **Layer 1: System-Wide** | `system_telemetry` | Every 1 Second | Last 120 Seconds | $120 \times \text{metrics}$ | +| **Layer 2: Top Processes**| `process_telemetry`| Every 5 Seconds | Last 120 Seconds | $24 \times \text{metrics}$ | + +--- + +## Detailed Function Breakdown + +The feature engineering layer executes its operations deterministically through four functions: + +### 1. `extract_and_engineer_system(db_path)` +* **Intent:** Pulls the high-frequency global system indicators (Layer 1) and translates flat numbers into directional trends. +* **Mechanism:** * Queries the last **120 rows** from the `system_telemetry` table ordered by timestamp, reversing them in memory to form a clean chronological left-to-right timeline. + * Utilizes `pandas` to calculate moving windows (e.g., 30-second rolling averages for CPU consumption) to smooth out short-term spikes. + * Calculates **I/O Storm Rates** and **Scheduler Context Switch Acceleration** by taking the difference between the most current metric entry and past rows (`df['metric'] - df['metric'].shift(1)`). +* **Output Shape:** A flat, single-row pandas DataFrame: `[1 × num_system_features]`. + +### 2. `extract_and_engineer_processes(db_path)` +* **Intent:** Unpacks the denormalized JSON arrays for the Top 5 CPU and Top 5 RAM consumers, correcting the shape mismatch and handling the "cold start" baseline issue. +* **Mechanism:** + * Queries the last **24 rows** (representing 120 seconds of 5-second steps) from the `process_telemetry` table. + * **The Chronological Upsampling Transform:** Because the database contains 24 rows but the final matrix requires a 1-second grid alignment, it upsamples the process rows into a 120-second array using forward-filling (`.ffill()`). This ensures data points match at every single second ticker. + * **Zero-Imputation (Cold Start Fix):** If a rogue process bursts into the Top 5 list halfway through the window, its missing history blocks are auto-populated with `0.0` instead of letting `NaN` values break the mathematical tracking loops. + * Computes process acceleration curves and resource gradients ($\Delta \text{Metric} / \Delta t$). +* **Output Shape:** Two independent flat, single-row arrays: `[1 × num_cpu_features]` and `[1 × num_ram_features]`. + +### 3. `build_unified_vector(sys_vec, cpu_vec, ram_vec)` +* **Intent:** Combines separate feature spaces into a single high-dimensional coordinate vector. +* **Mechanism:** + * Takes the horizontal outputs generated by the system and process computation layers. + * Executes an optimized columnar concatenation step (`pandas.concat(axis=1)`) to stitch the arrays together. + * Enforces a rigid, static schema definition so that column indexes never drift, ensuring the input dimensions exactly match what the downstream Isolation Forest expects. +* **Output Shape:** A single, high-dimensional flat vector row: `[1 × total_combined_features]`. + +### 4. `get_inference_payload(db_path)` +* **Intent:** Serves as the centralized public orchestrator function called directly by the core daemon script loop. +* **Mechanism:** + * Acts as a non-blocking execution gatekeeper. + * First runs a safety health check on the database capacity. If the background collectors haven't yet logged the baseline buffer of 120 system records, this function gracefully exits returning `None`, preventing the ML models from calculating false positives on partial windows. + * Executes functions 1, 2, and 3 sequentially completely in-memory, then passes the finalized ready vector row directly down to the machine learning execution loop (`i_forest.py`). +* **Output:** An inference-ready `pandas.DataFrame` row or `None`. + 7. Machine Learning Model 8. LLM Explanation Layer 9. Dashboard From 45f7c346868e3606059f98b96a70679c2924bdbe Mon Sep 17 00:00:00 2001 From: NikhByte Date: Tue, 30 Jun 2026 19:04:34 +0530 Subject: [PATCH 34/47] feat: setup continuous layer 1 metrics collection and db storage - Fixed a bug in db.py by adding conn.commit() to ensure data persists. - Expanded db.py schema and insert query to support network metrics and top process lists. - Refactored collectors/layer1_system.py to correctly format process_data and network metrics. - Implemented a continuous loop in cognios_as_daemon.py to collect and save telemetry every 1 second in the background. --- cognios_as_daemon.py | 79 +++++++++++++++++++++++++++++++++++++ collectors/layer1_system.py | 59 +++++++++++++++++++++++++-- db.py | 20 +++++++--- 3 files changed, 150 insertions(+), 8 deletions(-) diff --git a/cognios_as_daemon.py b/cognios_as_daemon.py index 03d43a7..cbf2700 100644 --- a/cognios_as_daemon.py +++ b/cognios_as_daemon.py @@ -1 +1,80 @@ """Daemon entry point for CogniOS.""" +import time +import json +import logging +from db import create_connection, write_layer1 +from collectors.layer1_system import collect_layer1_metrics +from config import DB_PATH + +# Set up basic logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +def run_daemon(): + logging.info(f"Starting CogniOS Daemon. Saving metrics to '{DB_PATH}' every 1 second.") + + # Initialize database connection + conn = create_connection(DB_PATH) + + try: + while True: + try: + metrics = collect_layer1_metrics() + + write_layer1( + conn, + metrics['timestamp'], + metrics['cpu_usage_percent'], + metrics['cpu_current_freq'], + metrics['cpu_user_time'], + metrics['cpu_system_time'], + metrics['cpu_idle_time'], + metrics['cpu_iowait_time'], + metrics['cpu_busy_time'], + metrics['cpu_ctx_switches'], + metrics['memory_percent'], + metrics['memory_used'], + metrics['memory_available'], + metrics['memory_cached'], + metrics['memory_buffers'], + metrics['swap_percent'], + metrics['swap_sin'], + metrics['swap_sout'], + metrics['disk_usage_percent'], + metrics['disk_read'], + metrics['disk_write'], + metrics['disk_read_time'], + metrics['disk_write_time'], + metrics['load_avg1'], + metrics['load_avg5'], + metrics['load_avg15'], + metrics['total_processes'], + metrics['running_processes'], + metrics['sleeping_processes'], + metrics['zombie_processes'], + metrics['avg_temp'], + metrics['max_temp'], + metrics['battery_percent'], + metrics['net_rate_mb_s'], + metrics['net_bytes_sent'], + metrics['net_bytes_received'], + metrics['net_packets_sent'], + metrics['net_packets_received'], + metrics['net_errs'], + metrics['net_drops'], + json.dumps(metrics['process_data']) + ) + logging.info(f"Successfully saved metrics for timestamp: {metrics['timestamp']}") + + except Exception as e: + logging.error(f"Error collecting or writing metrics: {e}") + + # Wait for 1 second before the next collection + time.sleep(1) + + except KeyboardInterrupt: + logging.info("Stopping CogniOS Daemon. Shutting down gracefully...") + finally: + conn.close() + +if __name__ == "__main__": + run_daemon() diff --git a/collectors/layer1_system.py b/collectors/layer1_system.py index 1760bd0..8086307 100644 --- a/collectors/layer1_system.py +++ b/collectors/layer1_system.py @@ -16,8 +16,6 @@ "net_bytes_sent": None, "net_bytes_recv": None, } - - # collects metriics def collect_layer1_metrics(): now = time.time() @@ -197,6 +195,7 @@ def collect_layer1_metrics(): "disk_read_time":disk_read_time, "disk_write_time":disk_write_time, + "net_rate_mb_s":net_rate_mb_s, "net_bytes_sent":net_bytes_sent, "net_bytes_received":net_bytes_received, "net_packets_sent":net_packets_sent, @@ -219,5 +218,59 @@ def collect_layer1_metrics(): if __name__ == "__main__": import json + from db import create_connection, write_layer1 + from config import DB_PATH + metrics = collect_layer1_metrics() - print(json.dumps(metrics, indent=4)) \ No newline at end of file + print("Gathered metrics:") + print(json.dumps(metrics, indent=4)) + + print("\nSaving metrics to database...") + try: + conn = create_connection(DB_PATH) + write_layer1( + conn, + metrics['timestamp'], + metrics['cpu_usage_percent'], + metrics['cpu_current_freq'], + metrics['cpu_user_time'], + metrics['cpu_system_time'], + metrics['cpu_idle_time'], + metrics['cpu_iowait_time'], + metrics['cpu_busy_time'], + metrics['cpu_ctx_switches'], + metrics['memory_percent'], + metrics['memory_used'], + metrics['memory_available'], + metrics['memory_cached'], + metrics['memory_buffers'], + metrics['swap_percent'], + metrics['swap_sin'], + metrics['swap_sout'], + metrics['disk_usage_percent'], + metrics['disk_read'], + metrics['disk_write'], + metrics['disk_read_time'], + metrics['disk_write_time'], + metrics['load_avg1'], + metrics['load_avg5'], + metrics['load_avg15'], + metrics['total_processes'], + metrics['running_processes'], + metrics['sleeping_processes'], + metrics['zombie_processes'], + metrics['avg_temp'], + metrics['max_temp'], + metrics['battery_percent'], + metrics['net_rate_mb_s'], + metrics['net_bytes_sent'], + metrics['net_bytes_received'], + metrics['net_packets_sent'], + metrics['net_packets_received'], + metrics['net_errs'], + metrics['net_drops'], + json.dumps(metrics['process_data']) + ) + print(f"Successfully saved Layer 1 metrics to database table 'layer1_sys' in {DB_PATH}!") + except Exception as e: + print(f"Error saving to database: {e}") \ No newline at end of file diff --git a/db.py b/db.py index 8eb9dc8..d1b862d 100644 --- a/db.py +++ b/db.py @@ -60,7 +60,8 @@ def create_connection(db_path): --Hardware Metrics avg_temp REAL, max_temp REAL, - battery_percent REAL + battery_percent REAL, + process_data TEXT ) ''') @@ -69,11 +70,11 @@ def create_connection(db_path): # Function to write the collected metrics into the database -def write_layer1(conn, timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cpu_system_time, cpu_idle_time, cpu_iowait_time, cpu_busy_time, cpu_ctx_switches, memory_percent, memory_used, memory_available, memory_cached, memory_buffers, swap_percent, swap_sin, swap_sout, disk_usage_percent, disk_read_mb_s, disk_write_mb_s, disk_read_time, disk_write_time, load_avg_1, load_avg_5, load_avg_15, total_processes, running_processes, sleeping_processes, zombie_processes, avg_temp, max_temp, battery_percent): +def write_layer1(conn, timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cpu_system_time, cpu_idle_time, cpu_iowait_time, cpu_busy_time, cpu_ctx_switches, memory_percent, memory_used, memory_available, memory_cached, memory_buffers, swap_percent, swap_sin, swap_sout, disk_usage_percent, disk_read_mb_s, disk_write_mb_s, disk_read_time, disk_write_time, load_avg_1, load_avg_5, load_avg_15, total_processes, running_processes, sleeping_processes, zombie_processes, avg_temp, max_temp, battery_percent, net_rate_mb_s, net_bytes_sent, net_bytes_recv, net_packets_sent, net_packets_recv, net_errs, net_drops, process_data): cursor = conn.cursor() cursor.execute(''' - INSERT INTO layer1_sys (timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cpu_system_time, cpu_idle_time, cpu_iowait_time, cpu_busy_time, cpu_ctx_switches, memory_percent, memory_used, memory_available, memory_cached, memory_buffers, swap_percent, swap_sin, swap_sout, disk_usage_percent, disk_read_mb_s, disk_write_mb_s, disk_read_time, disk_write_time, load_avg_1, load_avg_5, load_avg_15, total_processes, running_processes, sleeping_processes, zombie_processes, avg_temp, max_temp, battery_percent) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO layer1_sys (timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cpu_system_time, cpu_idle_time, cpu_iowait_time, cpu_busy_time, cpu_ctx_switches, memory_percent, memory_used, memory_available, memory_cached, memory_buffers, swap_percent, swap_sin, swap_sout, disk_usage_percent, disk_read_mb_s, disk_write_mb_s, disk_read_time, disk_write_time, net_rate_mb_s, net_bytes_sent, net_bytes_recv, net_packets_sent, net_packets_recv, net_errs, net_drops, load_avg_1, load_avg_5, load_avg_15, total_processes, running_processes, sleeping_processes, zombie_processes, avg_temp, max_temp, battery_percent, process_data) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, @@ -95,6 +96,13 @@ def write_layer1(conn, timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cp disk_write_mb_s, disk_read_time, disk_write_time, + net_rate_mb_s, + net_bytes_sent, + net_bytes_recv, + net_packets_sent, + net_packets_recv, + net_errs, + net_drops, load_avg_1, load_avg_5, load_avg_15, @@ -104,4 +112,6 @@ def write_layer1(conn, timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cp zombie_processes, avg_temp, max_temp, - battery_percent)) + battery_percent, + process_data)) + conn.commit() From b59efbd4bebe5a171761be590afdeca92674caa9 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 1 Jul 2026 08:06:26 +0530 Subject: [PATCH 35/47] fix: bugs and also remove hardcoded path of .db file , write_layer1 fxn also --- .gitignore | 1 + cognios_telemetry.db | Bin 16384 -> 16384 bytes collectors/layer3_metadata.py | 4 +- collectors/layer4_diagnostics.py | 7 +- db.py | 192 +++++++++++++------------------ 5 files changed, 88 insertions(+), 116 deletions(-) diff --git a/.gitignore b/.gitignore index 6b08a83..97bdc7d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ data/traces/* # Keep data directories present in git if marker files are added. !data/models/.gitkeep !data/traces/.gitkeep +cognios_telemetry.db \ No newline at end of file diff --git a/cognios_telemetry.db b/cognios_telemetry.db index b3ec5990b052ae36618b688d9e7999ace6c24530..3e2edc6e40ed94d5758019d410c9c45686cd7344 100644 GIT binary patch literal 16384 zcmeI3Urbw79LMjyZAXD#E?_$#%kVxZficzxNZcaA>7-1w>qhP3x|rOQ_H1}Q%8mSvDAG7RG(FE@EvgCi&9zq8hb ze>r-X1Lqcb65wk1mf>IHL*xJh2mk>f00e*l5C8%|00`Vw0vFD@>e_-qcG0CJC$d<} z7bGPqOB&8%8EeXH)f?_dW8qLdEXG5JBVn=Xve?}qIz}cfjz#0)BjK2MJT`VT6ibLt zgcD-uMEuxTl&BpIN8|geW@#B2t6EYnh~$0|F?uX6Mo&Z{)q#aF{jH;NNg0dr@JXVw zrBbR;lnPi$;heVJO=T?SmD!y}E2=6PGi(xDui~712f^B;f|F?}l`oPtTa>rf)`KTz zHLOZ1PHnfz8HIe^k#-zTvZWQ(O2`#y?K#fvYU}M~6MCts(^)F7;^`vJrLf~yS4EjQ zE_Tyw?k6*QE}UhZ3mZk{}cZv{}eCyZunjxmtg<_AOHk_01yBIKmZ5;0U+>yCNL`SOdZ!zM+WQl z?hW01<$h1*ot2jU)TEM^F=F4K;}U8f|82hWS`3~4`elj>ExongNsjlaS&R#r+?mm) znmSFNnL6$->G%q@U(I`#bS19Vrzx6M`fO~!Kn}asz40+xSnAewtXzfohkhjduiBLA z%JhsHqIx#%HYe?6LvP7Jdt_HlN@ni&nJO)}j=bxC_7*CQPa3;ho%n&A*tzfZ5EVA) zC8OT!4bY0_ZodDZXKE&|OkpJ$>r-cQDQRXplg{FOZW`R@40dk@E2*M%hCIm2;sF

3pkk4l&Do&mj6|tm{=aWelTX4EKI%}_U7B^))A4^+tgDi=w-{U2fPnK<~ ztFx+x<@E5{6f00e*l5C8%| z00;m9AaIWm2ym_-Th+rP1X%O!irx_N5Rdr(jT?-xCX|H30%yAaKNI&q!M70i|2bbj zxeNmc00AHX1b_e#00KY&2mpb5mB66x-3O>;Snl0-+}6GFR^1|RKqd7Tx!$5vXzpUOu^Q9cMvDpRGXG((sE&MV3LVWxA$~Frsr0~_dv+^>q zGfT4r0V4;8wBxN_e>HZ~0EX|M`}(%YLSA5dqAkB16vvx*gtfyUnf%a(8D3j z2xKIKL-l_tkeR|zGd-9=X3AokspFcHnp{$ppXb1H1_aL9e+D})#v$Wa1K6ZF#y_?I z85&TN!k9oNSz?$}|2rPVc|oapDXB%NMVbz>P;WCqEO4k0J^(S;(q;#cAqF+rixFrr zlQO!&EHjhyi&BdVQd5&NQXJ%<{=EP)0q9MaItX3)h0A6FkjV!!Z1Z3F*Zf+nqKwS? zj3xO6@yP|H@g=D_skx~oMU}un+zy{=s=ANtOv)dJqOT0)a0uQ zO}_Aihlp9WK#&`Q9BSGez-G?Txf=syz+yBU6r;@E=teTKl%Y5gVz3S{Nh3_=hNhXu RT!_hYdHE!OCW}H{3jqGY1~UKv diff --git a/collectors/layer3_metadata.py b/collectors/layer3_metadata.py index 93ab0be..34c5fad 100644 --- a/collectors/layer3_metadata.py +++ b/collectors/layer3_metadata.py @@ -2,10 +2,12 @@ import sqlite3 from datetime import datetime import psutil +from config import DB_PATH +db_path = DB_PATH class MetadataCollector: - def __init__(self, db_path="cognios_telemetry.db"): + def __init__(self, db_path): self.conn = sqlite3.connect(db_path, check_same_thread = False) self.cursor = self.conn.cursor() diff --git a/collectors/layer4_diagnostics.py b/collectors/layer4_diagnostics.py index 34e6634..6c5bb7e 100644 --- a/collectors/layer4_diagnostics.py +++ b/collectors/layer4_diagnostics.py @@ -4,15 +4,16 @@ import socket import sqlite3 import threading - import psutil +from config import DB_PATH +db_path = DB_PATH + _layer4_running = False _layer4_lock = threading.Lock() -DEFAULT_DB_PATH = Path(__file__).resolve().parents[1] / "data" / "cognios.db" class DiagnosticsCollector: - def __init__(self, db_path=DEFAULT_DB_PATH): + def __init__(self, db_path): db_path = Path(db_path) db_path.parent.mkdir(parents=True, exist_ok=True) diff --git a/db.py b/db.py index 369f6bc..55a198a 100644 --- a/db.py +++ b/db.py @@ -1,15 +1,90 @@ """Shared database schema and read/write interface.""" import sqlite3 # for both layers import time # for layer 2 -from config import DB_PATH # for layer 1 +from config import DB_PATH + +# layer 1 db code starts here +db_path = DB_PATH + + +# Creating table for all the metrics collected from the system + +def create_connection(db_path): + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + cursor.execute('''CREATE TABLE IF NOT EXISTS layer1_sys ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL, + + cpu_usage_percent REAL, + cpu_current_freq REAL, + cpu_user_time REAL, + cpu_system_time REAL, + cpu_idle_time REAL, + cpu_iowait_time REAL, + cpu_busy_time REAL, + cpu_ctx_switches REAL, + + memory_percent REAL, + memory_used INTEGER, + memory_available INTEGER, + memory_cached INTEGER, + memory_buffers INTEGER, + swap_percent REAL, + swap_sin INTEGER, + swap_sout INTEGER, + + disk_usage_percent REAL, + disk_read REAL, + disk_write REAL, + disk_read_time INTEGER, + disk_write_time INTEGER, + + net_bytes_sent INTEGER, + net_bytes_received INTEGER, + net_packets_sent INTEGER, + net_packets_received INTEGER, + net_errs INTEGER, + net_drops INTEGER, + net_rate_mb_s REAL, + + load_avg1 REAL, + load_avg5 REAL, + load_avg15 REAL, + total_processes INTEGER, + running_processes INTEGER, + sleeping_processes INTEGER, + zombie_processes INTEGER, + + avg_temp REAL, + max_temp REAL, + battery_percent REAL + ) + ''') + + conn.commit() + return conn + +# Function to write the collected metrics into the database +# removed hardcoded keys and added a skip_keys set to avoid storing unnecessary data in the database +def write_layer1(conn, data: dict): + skip_keys = {'process_data'} # DB mein store nahi karna + data_to_store = {k: v for k, v in data.items() if k not in skip_keys} + + cols = ", ".join(data_to_store.keys()) + placeholders = ", ".join("?" for _ in data_to_store) + conn.execute( + f"INSERT INTO layer1_sys ({cols}) VALUES ({placeholders})", + list(data_to_store.values()) + ) + conn.commit() # layer 2 db code starts here -DB_NAME = "cognios_telemetry.db" def init_db(): """Initializes separate structural tables for CPU and Memory metrics.""" - with sqlite3.connect(DB_NAME) as conn: + with sqlite3.connect(DB_PATH) as conn: cursor = conn.cursor() # Table 1: Dedicated CPU telemetry @@ -90,10 +165,9 @@ def insert_separated_telemetry(top_cpu, top_mem): ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """ - with sqlite3.connect(DB_NAME) as conn: + with sqlite3.connect(db_path) as conn: cursor = conn.cursor() - if cpu_rows: cursor.executemany(insert_query.format("top_cpu_telemetry"), cpu_rows) @@ -101,112 +175,6 @@ def insert_separated_telemetry(top_cpu, top_mem): cursor.executemany(insert_query.format("top_ram_telemetry"), ram_rows) conn.commit() + # layer 2 db code ends here -# layer 1 db code starts here - - -db_path = DB_PATH - -# Creating table for all the metrics collected from the system - -def create_connection(db_path): - conn = sqlite3.connect(db_path) - cursor = conn.cursor() - cursor.execute('''CREATE TABLE IF NOT EXISTS layer1_sys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL, - - --CPU Metrics - cpu_usage_percent REAL, - cpu_freq REAL, - cpu_user_time REAL, - cpu_system_time REAL, - cpu_idle_time REAL, - cpu_iowait_time REAL, - cpu_busy_time REAL, - cpu_ctx_switches REAL, - - --Memory Metrics - memory_percent REAL, - memory_used INTEGER, - memory_available INTEGER, - memory_cached INTEGER, - memory_buffers INTEGER, - swap_percent REAL, - swap_sin INTEGER, - swap_sout INTEGER, - - --Disk Metrics - disk_usage_percent REAL, - disk_read_mb_s REAL, - disk_write_mb_s REAL, - disk_read_time INTEGER, - disk_write_time INTEGER, - - --Network Metrics - net_rate_mb_s REAL, - net_bytes_sent INTEGER, - net_bytes_recv INTEGER, - net_packets_sent INTEGER, - net_packets_recv INTEGER, - net_errs INTEGER, - net_drops INTEGER, - - --System Metrics - load_avg_1 REAL, - load_avg_5 REAL, - load_avg_15 REAL, - total_processes INTEGER, - running_processes INTEGER, - sleeping_processes INTEGER, - zombie_processes INTEGER, - - --Hardware Metrics - avg_temp REAL, - max_temp REAL, - battery_percent REAL - ) - ''') - - conn.commit() - return conn - -# Function to write the collected metrics into the database - -def write_layer1(conn, timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cpu_system_time, cpu_idle_time, cpu_iowait_time, cpu_busy_time, cpu_ctx_switches, memory_percent, memory_used, memory_available, memory_cached, memory_buffers, swap_percent, swap_sin, swap_sout, disk_usage_percent, disk_read_mb_s, disk_write_mb_s, disk_read_time, disk_write_time, load_avg_1, load_avg_5, load_avg_15, total_processes, running_processes, sleeping_processes, zombie_processes, avg_temp, max_temp, battery_percent): - cursor = conn.cursor() - cursor.execute(''' - INSERT INTO layer1_sys (timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cpu_system_time, cpu_idle_time, cpu_iowait_time, cpu_busy_time, cpu_ctx_switches, memory_percent, memory_used, memory_available, memory_cached, memory_buffers, swap_percent, swap_sin, swap_sout, disk_usage_percent, disk_read_mb_s, disk_write_mb_s, disk_read_time, disk_write_time, load_avg_1, load_avg_5, load_avg_15, total_processes, running_processes, sleeping_processes, zombie_processes, avg_temp, max_temp, battery_percent) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', (timestamp, - cpu_usage_percent, - cpu_freq, cpu_user_time, - cpu_system_time, - cpu_idle_time, - cpu_iowait_time, - cpu_busy_time, - cpu_ctx_switches, - memory_percent, - memory_used, - memory_available, - memory_cached, - memory_buffers, - swap_percent, - swap_sin, - swap_sout, - disk_usage_percent, - disk_read_mb_s, - disk_write_mb_s, - disk_read_time, - disk_write_time, - load_avg_1, - load_avg_5, - load_avg_15, - total_processes, - running_processes, - sleeping_processes, - zombie_processes, - avg_temp, - max_temp, - battery_percent)) From 76fc247106c25315e172d41845f8c9659d9ba09d Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 1 Jul 2026 08:13:13 +0530 Subject: [PATCH 36/47] removed .db file --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 97bdc7d..df525fa 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,8 @@ data/traces/* # Keep data directories present in git if marker files are added. !data/models/.gitkeep !data/traces/.gitkeep -cognios_telemetry.db \ No newline at end of file + +# Ignore SQLite database files +*.db +*.sqlite3 +*.sqlite From 6325ccd5831b01fff2280f2891ade4560ca53d55 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 1 Jul 2026 08:15:24 +0530 Subject: [PATCH 37/47] chore: stop tracking telemetry database file --- cognios_telemetry.db | Bin 16384 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 cognios_telemetry.db diff --git a/cognios_telemetry.db b/cognios_telemetry.db deleted file mode 100644 index 3e2edc6e40ed94d5758019d410c9c45686cd7344..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI3Urbw79LMjyZAXD#E?_$#%kVxZficzxNZcaA>7-1w>qhP3x|rOQ_H1}Q%8mSvDAG7RG(FE@EvgCi&9zq8hb ze>r-X1Lqcb65wk1mf>IHL*xJh2mk>f00e*l5C8%|00`Vw0vFD@>e_-qcG0CJC$d<} z7bGPqOB&8%8EeXH)f?_dW8qLdEXG5JBVn=Xve?}qIz}cfjz#0)BjK2MJT`VT6ibLt zgcD-uMEuxTl&BpIN8|geW@#B2t6EYnh~$0|F?uX6Mo&Z{)q#aF{jH;NNg0dr@JXVw zrBbR;lnPi$;heVJO=T?SmD!y}E2=6PGi(xDui~712f^B;f|F?}l`oPtTa>rf)`KTz zHLOZ1PHnfz8HIe^k#-zTvZWQ(O2`#y?K#fvYU}M~6MCts(^)F7;^`vJrLf~yS4EjQ zE_Tyw?k6*QE}UhZ3mZk{}cZv{}eCyZunjxmtg<_AOHk_01yBIKmZ5;0U+>yCNL`SOdZ!zM+WQl z?hW01<$h1*ot2jU)TEM^F=F4K;}U8f|82hWS`3~4`elj>ExongNsjlaS&R#r+?mm) znmSFNnL6$->G%q@U(I`#bS19Vrzx6M`fO~!Kn}asz40+xSnAewtXzfohkhjduiBLA z%JhsHqIx#%HYe?6LvP7Jdt_HlN@ni&nJO)}j=bxC_7*CQPa3;ho%n&A*tzfZ5EVA) zC8OT!4bY0_ZodDZXKE&|OkpJ$>r-cQDQRXplg{FOZW`R@40dk@E2*M%hCIm2;sF

3pkk4l&Do&mj6|tm{=aWelTX4EKI%}_U7B^))A4^+tgDi=w-{U2fPnK<~ ztFx+x<@E5{6f00e*l5C8%| z00;m9AaIWm2ym_-Th+rP1X%O!irx_N5Rdr(jT?-xCX|H30%yAaKNI&q!M70i|2bbj zxeNmc00AHX1b_e#00KY&2mpb5mB66x-3O>;Snl0-+}6GFR^1|RKqd7Tx!$5vXzpUOu^Q9cMvDpRGXG((s Date: Thu, 2 Jul 2026 08:16:29 +0530 Subject: [PATCH 38/47] add const for blackbox module and also fix the folder strct --- collectors/layer3_metadata.py | 2 +- collectors/layer4_diagnostics.py | 2 +- config.py | 1 - db.py | 2 +- utils/config.py | 2 ++ 5 files changed, 5 insertions(+), 4 deletions(-) delete mode 100644 config.py create mode 100644 utils/config.py diff --git a/collectors/layer3_metadata.py b/collectors/layer3_metadata.py index 34c5fad..9a749ae 100644 --- a/collectors/layer3_metadata.py +++ b/collectors/layer3_metadata.py @@ -2,7 +2,7 @@ import sqlite3 from datetime import datetime import psutil -from config import DB_PATH +from CogniOS.utils.config import DB_PATH db_path = DB_PATH class MetadataCollector: diff --git a/collectors/layer4_diagnostics.py b/collectors/layer4_diagnostics.py index 6c5bb7e..3ec3185 100644 --- a/collectors/layer4_diagnostics.py +++ b/collectors/layer4_diagnostics.py @@ -6,7 +6,7 @@ import threading import psutil -from config import DB_PATH +from CogniOS.utils.config import DB_PATH db_path = DB_PATH _layer4_running = False diff --git a/config.py b/config.py deleted file mode 100644 index c4c8d21..0000000 --- a/config.py +++ /dev/null @@ -1 +0,0 @@ -DB_PATH = "cognios_telemetry.db" \ No newline at end of file diff --git a/db.py b/db.py index 55a198a..dfedcdd 100644 --- a/db.py +++ b/db.py @@ -1,7 +1,7 @@ """Shared database schema and read/write interface.""" import sqlite3 # for both layers import time # for layer 2 -from config import DB_PATH +from CogniOS.utils.config import DB_PATH # layer 1 db code starts here db_path = DB_PATH diff --git a/utils/config.py b/utils/config.py new file mode 100644 index 0000000..becbcbb --- /dev/null +++ b/utils/config.py @@ -0,0 +1,2 @@ +DB_PATH = "cognios_telemetry.db" +BLACKBOX_WINDOW_SEC = 1800 # 30 minutes \ No newline at end of file From 25375dc8964469454b3897c20e0e8a986e3499d0 Mon Sep 17 00:00:00 2001 From: mitsmania Date: Fri, 3 Jul 2026 13:14:17 +0530 Subject: [PATCH 39/47] added blackbox constants --- utils/config.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/utils/config.py b/utils/config.py index becbcbb..bc59225 100644 --- a/utils/config.py +++ b/utils/config.py @@ -1,2 +1,8 @@ DB_PATH = "cognios_telemetry.db" -BLACKBOX_WINDOW_SEC = 1800 # 30 minutes \ No newline at end of file +BLACKBOX_WINDOW_SEC = 1800 # 30 minutes +BLACKBOX_WARMUP_SEC = 60 # wait before detection starts +BLACKBOX_Z_THRESHOLD = 2.8 # std deviations for spike +BLACKBOX_SLOPE_THRESHOLD = 0.003 # %/sec rise = suspicious +BLACKBOX_SUSTAINED_SEC = 30 # spike must last this long +BLACKBOX_SUSTAINED_RATIO = 0.6 # 60% readings above threshold +BLACKBOX_TREND_WINDOW = 600 # 10 min for slope calculation \ No newline at end of file From b1706b802e40e4a27b1c1d705942d0626c4d0892 Mon Sep 17 00:00:00 2001 From: mitsmania Date: Fri, 3 Jul 2026 15:54:58 +0530 Subject: [PATCH 40/47] defined necessary functions for recorder --- blackbox/recorder.py | 70 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/blackbox/recorder.py b/blackbox/recorder.py index 5a772aa..953acd8 100644 --- a/blackbox/recorder.py +++ b/blackbox/recorder.py @@ -1 +1,71 @@ """Rolling 30-minute telemetry recorder.""" +import time +import sqlite3 +from utils.config import DB_PATH, BLACKBOX_WINDOW_SEC + + +def create_blackbox_table(conn): + conn.execute(""" + CREATE TABLE IF NOT EXISTS blackbox_telemetry ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp REAL NOT NULL, + cpu_usage_percent REAL, + memory_percent REAL, + disk_read REAL, + disk_write REAL, + net_rate_mb_s REAL, + cpu_ctx_switches REAL, + total_processes INTEGER, + zombie_processes INTEGER, + swap_percent REAL, + load_avg1 REAL + ) + """) + conn.commit() + + +def write_telemetry(conn, metrics: dict) -> None: + conn.execute(""" + INSERT INTO blackbox_telemetry ( + timestamp, cpu_usage_percent, memory_percent, + disk_read, disk_write, net_rate_mb_s, + cpu_ctx_switches, total_processes, + zombie_processes, swap_percent, load_avg1 + ) VALUES (?,?,?,?,?,?,?,?,?,?,?) + """, ( + time.time(), + metrics.get("cpu_usage_percent"), + metrics.get("memory_percent"), + metrics.get("disk_read"), + metrics.get("disk_write"), + metrics.get("net_rate_mb_s"), + metrics.get("cpu_ctx_switches"), + metrics.get("total_processes"), + metrics.get("zombie_processes"), + metrics.get("swap_percent"), + metrics.get("load_avg1"), + )) + conn.commit() + prune_old_records(conn) + + +def prune_old_records(conn) -> None: + cutoff = time.time() - BLACKBOX_WINDOW_SEC + conn.execute("DELETE FROM blackbox_telemetry WHERE timestamp < ?", (cutoff,)) + conn.commit() + + +def get_recent_rows(conn, n: int = 120) -> list[dict]: + cursor = conn.execute(""" + SELECT * FROM blackbox_telemetry + ORDER BY timestamp DESC + LIMIT ? + """, (n,)) + cols = [d[0] for d in cursor.description] + return [dict(zip(cols, row)) for row in cursor.fetchall()] + + +if __name__ == "__main__": + conn = sqlite3.connect(DB_PATH) + create_blackbox_table(conn) + \ No newline at end of file From 87fc5209f7ffad6fa540c98bb0962f4dc35eca73 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Fri, 3 Jul 2026 22:34:14 +0530 Subject: [PATCH 41/47] added fxns to recorder.py & heartbeat.py but thinking about gap.. --- blackbox/heartbeat.py | 43 ++++++++++++++- blackbox/recorder.py | 124 +++++++++++++++++++++++++++++------------- 2 files changed, 127 insertions(+), 40 deletions(-) diff --git a/blackbox/heartbeat.py b/blackbox/heartbeat.py index 62464f4..1fb60d7 100644 --- a/blackbox/heartbeat.py +++ b/blackbox/heartbeat.py @@ -1 +1,42 @@ -#Crash/freeze detection via heartbeat timestamps \ No newline at end of file +#Crash/freeze detection via heartbeat timestamps + +import sqlite3 +import time +from blackbox.recorder import get_blackbox_conn + +def create_heartbeat_table(conn: sqlite3.Connection) -> None: + + # Create a table to store the last heartbeat timestamp + conn.execute(""" + CREATE TABLE IF NOT EXISTS blackbox_heartbeat ( + id INTEGER PRIMARY KEY CHECK (id = 1), + last_beat REAL NOT NULL + ) + """) + # Insert an initial heartbeat record if it doesn't exist + conn.execute(""" + INSERT OR IGNORE INTO blackbox_heartbeat (id, last_beat) + VALUES (1, ?) + """, (time.time(),)) + conn.commit() + + +def update_heartbeat(conn: sqlite3.Connection) -> None: + # Update the last heartbeat timestamp + conn.execute(""" + UPDATE blackbox_heartbeat + SET last_beat = ? + WHERE id = 1 + """, (time.time(),)) + conn.commit() + +# def check_heartbeat(conn: sqlite3.Connection, threshold_seconds: float = 10.0) -> tuple[bool, float]: +# # Check if the last heartbeat is within the threshold +# cursor = conn.execute(""" +# SELECT last_beat FROM blackbox_heartbeat WHERE id = 1 +# """) +# row = cursor.fetchone() +# if row is None: +# return False, 0.0 # No heartbeat record found +# gap = time.time() - row[0] +# return gap > 10.0, round(gap, 1) \ No newline at end of file diff --git a/blackbox/recorder.py b/blackbox/recorder.py index 953acd8..47f640f 100644 --- a/blackbox/recorder.py +++ b/blackbox/recorder.py @@ -1,7 +1,18 @@ """Rolling 30-minute telemetry recorder.""" import time import sqlite3 -from utils.config import DB_PATH, BLACKBOX_WINDOW_SEC +from utils.config import BLACKBOX_DB_PATH, BLACKBOX_WINDOW_SEC +from pathlib import Path + + +def get_blackbox_conn() -> sqlite3.Connection: + Path(BLACKBOX_DB_PATH).parent.mkdir(parents=True, exist_ok=True) + conn = sqlite3.connect(BLACKBOX_DB_PATH, check_same_thread=False) + + # WAL mode: crash-safe writes — data survive karta hai daemon crash pe bhi + conn.execute("PRAGMA journal_mode=WAL") + conn.execute("PRAGMA synchronous=NORMAL") + return conn def create_blackbox_table(conn): @@ -9,63 +20,98 @@ def create_blackbox_table(conn): CREATE TABLE IF NOT EXISTS blackbox_telemetry ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp REAL NOT NULL, - cpu_usage_percent REAL, - memory_percent REAL, - disk_read REAL, - disk_write REAL, - net_rate_mb_s REAL, - cpu_ctx_switches REAL, - total_processes INTEGER, - zombie_processes INTEGER, - swap_percent REAL, - load_avg1 REAL + + -- CPU Metrics + cpu_usage_percent REAL, + cpu_ctx_switches REAL, + cpu_busy_time REAL, + cpu_iowait_time REAL, + + -- Disk Metrics + disk_read REAL, + disk_write REAL, + + -- Network Metrics + net_rate_mb_s REAL, + net_bytes_sent INTEGER, + net_bytes_received INTEGER, + + -- Process Metrics + total_processes INTEGER, + running_processes INTEGER, + zombie_processes INTEGER, + + -- System load + load_avg1 REAL, + load_avg5 REAL, + + -- Hardware + avg_temp REAL ) """) conn.commit() +BLACKBOX_METRICS_KEYS = { + 'timestamp', 'cpu_usage_percent', 'cpu_ctx_switches', + 'cpu_busy_time', 'cpu_iowait_time', 'memory_percent', + 'memory_used', 'swap_percent', 'disk_read', 'disk_write', + 'net_rate_mb_s', 'net_bytes_sent', 'net_bytes_received', + 'total_processes', 'running_processes', 'zombie_processes', + 'load_avg1', 'load_avg5', 'avg_temp', +} -def write_telemetry(conn, metrics: dict) -> None: - conn.execute(""" - INSERT INTO blackbox_telemetry ( - timestamp, cpu_usage_percent, memory_percent, - disk_read, disk_write, net_rate_mb_s, - cpu_ctx_switches, total_processes, - zombie_processes, swap_percent, load_avg1 - ) VALUES (?,?,?,?,?,?,?,?,?,?,?) - """, ( - time.time(), - metrics.get("cpu_usage_percent"), - metrics.get("memory_percent"), - metrics.get("disk_read"), - metrics.get("disk_write"), - metrics.get("net_rate_mb_s"), - metrics.get("cpu_ctx_switches"), - metrics.get("total_processes"), - metrics.get("zombie_processes"), - metrics.get("swap_percent"), - metrics.get("load_avg1"), - )) +def write_telemetry(conn: sqlite3.Connection, metrics: dict) -> None: + + data = {k: v for k, v in metrics.items() if k in BLACKBOX_METRICS_KEYS} + # Ensure numeric timestamp + data['timestamp'] = time.time() + + cols = ", ".join(data.keys()) + placeholders = ", ".join("?" for _ in data) + conn.execute( + f"INSERT INTO blackbox_telemetry ({cols}) VALUES ({placeholders})", + list(data.values()) + ) conn.commit() prune_old_records(conn) -def prune_old_records(conn) -> None: +def prune_old_records(conn: sqlite3.Connection) -> None: cutoff = time.time() - BLACKBOX_WINDOW_SEC - conn.execute("DELETE FROM blackbox_telemetry WHERE timestamp < ?", (cutoff,)) + conn.execute( + "DELETE FROM blackbox_telemetry WHERE timestamp < ?", + (cutoff,) + ) conn.commit() -def get_recent_rows(conn, n: int = 120) -> list[dict]: +def get_recent_rows(conn: sqlite3.Connection, n: int = 120) -> list[dict]: + + """ useful for feture engineering or correlation analysis of telemetry data """ cursor = conn.execute(""" SELECT * FROM blackbox_telemetry ORDER BY timestamp DESC LIMIT ? """, (n,)) cols = [d[0] for d in cursor.description] + rows = [dict(zip(cols, row)) for row in cursor.fetchall()] + return list(reversed(rows)) # Return in chronological order + + +def get_window_rows(conn: sqlite3.Connection, + start_time: float, + end_time: float) -> list[dict]: + """ fetch specific window of telemetry data for analysis """ + cursor = conn.execute(""" + SELECT * FROM blackbox_telemetry + WHERE timestamp BETWEEN ? AND ? + ORDER BY timestamp ASC + """, (start_time, end_time)) + cols = [d[0] for d in cursor.description] return [dict(zip(cols, row)) for row in cursor.fetchall()] -if __name__ == "__main__": - conn = sqlite3.connect(DB_PATH) - create_blackbox_table(conn) - \ No newline at end of file +def row_count(conn: sqlite3.Connection) -> int: + return conn.execute( + "SELECT COUNT(*) FROM blackbox_telemetry" + ).fetchone()[0] \ No newline at end of file From 1a34dca26e2fb02593a4786e45b622f52cadb6bd Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Sat, 4 Jul 2026 08:58:44 +0530 Subject: [PATCH 42/47] updated crash-detection logic + missing memory schema columns --- blackbox/heartbeat.py | 52 ++++++++++++++++++++++++++++++------------- blackbox/recorder.py | 5 +++++ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/blackbox/heartbeat.py b/blackbox/heartbeat.py index 1fb60d7..8690ce6 100644 --- a/blackbox/heartbeat.py +++ b/blackbox/heartbeat.py @@ -2,27 +2,28 @@ import sqlite3 import time -from blackbox.recorder import get_blackbox_conn +from utils.config import BLACKBOX_CRASH_GAP_SEC def create_heartbeat_table(conn: sqlite3.Connection) -> None: - # Create a table to store the last heartbeat timestamp + # single row table to store the last heartbeat timestamp and graceful shutdown flag conn.execute(""" CREATE TABLE IF NOT EXISTS blackbox_heartbeat ( id INTEGER PRIMARY KEY CHECK (id = 1), - last_beat REAL NOT NULL + last_beat REAL NOT NULL, + graceful_shutdown INTEGER NOT NULL DEFAULT 0 ) """) # Insert an initial heartbeat record if it doesn't exist conn.execute(""" - INSERT OR IGNORE INTO blackbox_heartbeat (id, last_beat) - VALUES (1, ?) + INSERT OR IGNORE INTO blackbox_heartbeat (id, last_beat, graceful_shutdown) + VALUES (1, ?, 0) """, (time.time(),)) conn.commit() def update_heartbeat(conn: sqlite3.Connection) -> None: - # Update the last heartbeat timestamp + # crash indicator conn.execute(""" UPDATE blackbox_heartbeat SET last_beat = ? @@ -30,13 +31,32 @@ def update_heartbeat(conn: sqlite3.Connection) -> None: """, (time.time(),)) conn.commit() -# def check_heartbeat(conn: sqlite3.Connection, threshold_seconds: float = 10.0) -> tuple[bool, float]: -# # Check if the last heartbeat is within the threshold -# cursor = conn.execute(""" -# SELECT last_beat FROM blackbox_heartbeat WHERE id = 1 -# """) -# row = cursor.fetchone() -# if row is None: -# return False, 0.0 # No heartbeat record found -# gap = time.time() - row[0] -# return gap > 10.0, round(gap, 1) \ No newline at end of file +def mark_graceful_shutdown(conn: sqlite3.Connection) -> None: + # Mark the graceful shutdown flag in the heartbeat table + conn.execute( + "UPDATE blackbox_heartbeat SET graceful_shutdown = 1 WHERE id = 1" + ) + conn.commit() + +def check_crash_on_startup(conn: sqlite3.Connection) -> tuple[bool, float]: + + # check if the last run ended gracefully or if there was a crash based on the heartbeat timestamp and graceful shutdown flag. + row = conn.execute( + "SELECT last_beat, graceful_shutdown FROM blackbox_heartbeat WHERE id = 1" + ).fetchone() + + if row is None: + return False, 0.0 + + last_beat, graceful = row + gap = round(time.time() - last_beat, 1) + + conn.execute( + "UPDATE blackbox_heartbeat SET graceful_shutdown = 0 WHERE id = 1" + ) + conn.commit() + + if graceful: + return False, gap + + return gap > BLACKBOX_CRASH_GAP_SEC, gap \ No newline at end of file diff --git a/blackbox/recorder.py b/blackbox/recorder.py index 47f640f..a8238f2 100644 --- a/blackbox/recorder.py +++ b/blackbox/recorder.py @@ -26,6 +26,11 @@ def create_blackbox_table(conn): cpu_ctx_switches REAL, cpu_busy_time REAL, cpu_iowait_time REAL, + + -- Memory Metrics + memory_percent REAL, + memory_used INTEGER, + swap_percent REAL, -- Disk Metrics disk_read REAL, From 48d1840587d731d7d7124a151570ae59cbe6e760 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Sun, 5 Jul 2026 17:16:00 +0530 Subject: [PATCH 43/47] added missing functions and arch diag for doc --- blackbox/README.md | 291 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 288 insertions(+), 3 deletions(-) diff --git a/blackbox/README.md b/blackbox/README.md index e1b03dc..e292a82 100644 --- a/blackbox/README.md +++ b/blackbox/README.md @@ -80,7 +80,7 @@ def write_telemetry(conn, metrics: dict) -> None Calls `prune_old_records()` after every write to enforce the 30-minute window. ```python -def rm_old_records(conn) -> None +def prune_old_records(conn) -> None ``` **Purpose:** Deletes rows older than `BLACKBOX_WINDOW_SEC` (default 1800s). @@ -101,11 +101,13 @@ def update_heartbeat(conn) -> None If the daemon crashes, the last saved timestamp persists in SQLite. ```python -def check_crash_on_startup(conn) -> bool +def check_crash_on_startup(conn) -> tuple[bool, float] ``` **Purpose:** Called once at daemon startup. Reads last heartbeat timestamp. -If `time.time() - last_beat > 10`, returns `True` → crash/freeze was detected. +Returns `(crash_detected: bool, gap_seconds: float)`. +If `graceful_shutdown = 0` AND `gap > BLACKBOX_CRASH_GAP_SEC` → crash was detected. +Note: Uses graceful shutdown flag approach (not hardcoded gap > 10s — that was arbitrary). Triggers `replay.py` to reconstruct the pre-crash timeline. --- @@ -421,3 +423,286 @@ No labeled anomaly dataset exists initially. IF is unsupervised — trained on n **Why Z-score threshold 2.8 and not 3.0?** 3.0 is the classic threshold but at 3.0, a developer's laptop (higher baseline CPU) barely crossed the threshold for genuine spikes. 2.8 gives slightly better sensitivity while keeping false positives low with the sustained filter. + +--- + +## 6. Architecture Diagram + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Linux System (laptop) │ +└─────────────────────┬───────────────────────────────────────┘ + │ psutil + /proc + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Telemetry Collector — collectors/layer1_system.py │ +│ every 1 second │ +└──────────────┬──────────────────────┬───────────────────────┘ + │ │ + ▼ ▼ +┌──────────────────────┐ ┌──────────────────────────────────┐ +│ cognios_telemetry │ │ blackbox/blackbox.db │ +│ .db (permanent) │ │ (rolling 30-min window only) │ +│ │ │ │ +│ layer1_sys │ │ blackbox_telemetry │ +│ top_cpu_telemetry │ │ blackbox_heartbeat │ +│ top_ram_telemetry │ └──────────────┬─────────────────── ┘ +│ process_metadata │ │ +│ process_diagnostics │ ▼ +│ │ ┌──────────────────────────────────┐ +│ OS Doctor ──────────┤ │ BlackBox Module │ +│ FocusOS ────────────┤ │ │ +│ Research Engine ────┘ │ recorder.py │ +│ │ heartbeat.py │ +└──────────────────────┘ │ feature_engineering.py │ + │ zscore_detector.py │ + │ rule_engine.py │ + │ anomaly_model.py │ + │ correlation.py │ + │ replay.py │ + └──────────────┬─────────────────── ┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ Streamlit Dashboard │ + │ Timeline · Alerts · NL Query │ + └──────────────────────────────────┘ +``` + +### Why two separate databases? + +| `cognios_telemetry.db` | `blackbox/blackbox.db` | +| ------------------------------------------- | ------------------------------------- | +| Permanent — never pruned | Rolling window — last 30 min only | +| All Layer 1/2/3/4 data | Only BlackBox-relevant metrics | +| Read by OS Doctor, FocusOS, Research Engine | Read only by BlackBox | +| Grows indefinitely | Max ~1800 rows (1 per second × 1800s) | + +--- + +## 7. Missing + Updated Function Definitions + +### `recorder.py` — additional functions + +```python +def get_blackbox_conn() -> sqlite3.Connection +``` + +**Purpose:** Opens and returns a connection to `blackbox/blackbox.db` with WAL mode for crash-safe writes. +**Input:** None +**Output:** `sqlite3.Connection` + +```python +def get_recent_rows(conn, n: int = 120) -> list[dict] +``` + +**Purpose:** Returns last `n` rows from `blackbox_telemetry`, ordered oldest-first. +**Input:** `conn`, `n` (default 120 = last 2 minutes) +**Output:** `list[dict]` — each dict has column names as keys +**Used by:** `feature_engineering.py`, `correlation.py` + +```python +def get_window_rows(conn, start_time: float, end_time: float) -> list[dict] +``` + +**Purpose:** Returns all rows between two Unix timestamps for crash replay. +**Input:** `conn`, `start_time` (Unix float), `end_time` (Unix float) +**Output:** `list[dict]` ordered by timestamp ASC +**Used by:** `replay.py` + +```python +def row_count(conn) -> int +``` + +**Purpose:** Returns total number of rows currently in `blackbox_telemetry`. +**Input:** `conn` +**Output:** `int` + +--- + +### `heartbeat.py` — updated with graceful shutdown flag + +> **Mentor feedback:** "Why should we have gap only 10 seconds?" +> +> Hardcoded `gap > 10s` was arbitrary — HDD systems take 20+ seconds to reboot causing false positives. We use a **graceful shutdown flag** instead. Gap is only a fallback for SIGKILL/power cuts. + +```python +def mark_graceful_shutdown(conn) -> None +``` + +**Purpose:** Sets `graceful_shutdown = 1` flag. Must be called in daemon's SIGTERM/SIGINT handler. +**Input:** `conn` +**Output:** None +**Note:** If NOT called (crash/SIGKILL/power cut), flag stays 0 → crash detected on next startup. + +```python +def check_crash_on_startup(conn) -> tuple[bool, float] +``` + +**Purpose:** Called once on daemon startup to detect whether previous session ended in a crash. +**Input:** `conn` +**Output:** `(crash_detected: bool, gap_seconds: float)` + +**Detection logic (priority order):** + +1. `graceful_shutdown = 1` → normal shutdown, no crash +2. `graceful_shutdown = 0` AND `gap > BLACKBOX_CRASH_GAP_SEC` → crash detected +3. Always resets flag to 0 for the next session + +**In daemon signal handler:** + +```python +def handle_signal(sig, frame): + global running + running = False + mark_graceful_shutdown(bb_conn) # ← required for correct crash detection + print("[CogniOS] Graceful shutdown.") +``` + +--- + +### `anomaly_model.py` — additional functions + +```python +def anomaly_severity(score: float) -> int +``` + +**Purpose:** Converts raw Isolation Forest `decision_function` score to a 0–100 severity integer for dashboard. +**Input:** `score` (float — more negative = more anomalous) +**Output:** `int` between 0 and 100 +**Example:** score=-0.3 → severity=80 + +```python +def save_model(model: IsolationForest, path: str = "blackbox/if_model.pkl") -> None +``` + +**Purpose:** Saves trained model to disk using pickle. +**Input:** trained `IsolationForest`, file path +**Output:** None + +```python +def load_model(path: str = "blackbox/if_model.pkl") -> IsolationForest +``` + +**Purpose:** Loads a previously saved model from disk. +**Input:** file path +**Output:** `IsolationForest` + +--- + +## 8. How to Run + +### Prerequisites + +```bash +pip install psutil numpy scikit-learn +``` + +### First time setup + +```bash +cd CogniOS + +python3 -c " +import sys +sys.path.insert(0, '.') +from blackbox.recorder import get_blackbox_conn, create_blackbox_table +from blackbox.heartbeat import create_heartbeat_table + +conn = get_blackbox_conn() +create_blackbox_table(conn) +create_heartbeat_table(conn) +print('BlackBox DB initialised at blackbox/blackbox.db') +" +``` + +### Run full daemon + +```bash +python3 cognios_as_daemon.py +``` + +### Run BlackBox integration test only + +```bash +python3 -c " +import sys, time +sys.path.insert(0, '.') +from collectors.layer1_system import collect_layer1_metrics +from blackbox.recorder import get_blackbox_conn, create_blackbox_table, write_telemetry, row_count +from blackbox.heartbeat import create_heartbeat_table, update_heartbeat +from blackbox.zscore_detector import ZScoreDetector +from blackbox.rule_engine import check_rules + +conn = get_blackbox_conn() +create_blackbox_table(conn) +create_heartbeat_table(conn) +detector = ZScoreDetector() + +for i in range(40): + m = collect_layer1_metrics() + write_telemetry(conn, m) + update_heartbeat(conn) + detector.update(m.get('cpu_usage_percent', 0)) + check_rules(m) + time.sleep(0.1) + +print('Rows in blackbox.db:', row_count(conn)) +print('Integration test PASSED ✅') +" +``` + +### Generate Isolation Forest training data + +```bash +sudo apt install stress-ng +python3 cognios_as_daemon.py & + +stress-ng --cpu 8 --timeout 60s # cpu_overload +sleep 30 +stress-ng --vm 4 --vm-bytes 80% --timeout 60s # memory_pressure +sleep 30 +stress-ng --hdd 4 --timeout 60s # disk_io_stress +sleep 30 +stress-ng --pthread 100 --timeout 60s # thread_explosion +``` + +--- + +## 9. Improvements & Future Work + +### High priority (low effort) + +**WAL mode for crash-safe writes** (2 lines of code): + +```python +conn.execute("PRAGMA journal_mode=WAL") +conn.execute("PRAGMA synchronous=NORMAL") +``` + +Default SQLite write mode can lose the last few seconds of telemetry if daemon crashes mid-write. WAL mode writes to a separate file first — crash during write means no data loss. + +**Continuous anomaly severity score:** +Replace binary `-1/1` output with 0–100 score using `decision_function()`. Dashboard can show gradual escalation (40→60→80→100) instead of a sudden binary alert. + +### Medium priority + +**Per-metric Isolation Forest models:** +One combined model learns a confused boundary across all metrics. Separate models per metric with different `contamination` rates give better detection accuracy. + +**Exponential Moving Average (EMA) baseline:** +Replace simple rolling mean with EMA for faster adaptation to regime changes, reducing false positives when user starts a new heavy workload. + +### Low priority (research phase) + +**LSTM Autoencoder hybrid:** +LSTM captures temporal dependencies that Isolation Forest cannot — sequential patterns like memory growing over 2 hours. A hybrid approach (LSTM reconstruction error fed into Isolation Forest) significantly improves detection. Requires TensorFlow and more training data. + +### Known limitations + +| Limitation | Impact | Workaround | +| -------------------------------------- | ------------------------------------------ | --------------------------------------- | +| Z-score slow drift blind spot | Memory leaks over 2+ hours may not trigger | Slope detector partially covers this | +| IF needs warmup data | No anomaly detection for first 60s | Rule Engine covers warmup period | +| `stress-ng` training data is synthetic | Real anomalies may differ | Collect real anomaly data and retrain | +| SIGKILL bypasses graceful flag | Gap fallback may miss very fast restarts | `BLACKBOX_CRASH_GAP_SEC = 30` as buffer | From bded119f4813dc207cb3c3a58acd1f96dfff71d7 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Sun, 5 Jul 2026 17:54:17 +0530 Subject: [PATCH 44/47] resolved conflicts --- blackbox/heartbeat.py | 2 +- blackbox/recorder.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blackbox/heartbeat.py b/blackbox/heartbeat.py index 8690ce6..888d98c 100644 --- a/blackbox/heartbeat.py +++ b/blackbox/heartbeat.py @@ -2,7 +2,7 @@ import sqlite3 import time -from utils.config import BLACKBOX_CRASH_GAP_SEC +from config import BLACKBOX_CRASH_GAP_SEC def create_heartbeat_table(conn: sqlite3.Connection) -> None: diff --git a/blackbox/recorder.py b/blackbox/recorder.py index a8238f2..42a6da4 100644 --- a/blackbox/recorder.py +++ b/blackbox/recorder.py @@ -1,7 +1,7 @@ """Rolling 30-minute telemetry recorder.""" import time import sqlite3 -from utils.config import BLACKBOX_DB_PATH, BLACKBOX_WINDOW_SEC +from config import BLACKBOX_DB_PATH, BLACKBOX_WINDOW_SEC from pathlib import Path From 5050a313bde996868ce2ba61c683bfea67cb6e7b Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Sun, 5 Jul 2026 17:58:02 +0530 Subject: [PATCH 45/47] resolved --- .github/CODEOWNERS | 0 .github/ISSUE_TEMPLATE/bug_report.md | 89 ++++++ .github/ISSUE_TEMPLATE/config.yml | 10 + .github/ISSUE_TEMPLATE/development_task.md | 81 +++++ .github/ISSUE_TEMPLATE/documentation.md | 88 ++++++ .github/ISSUE_TEMPLATE/feature_request.md | 70 +++++ .github/PULL_REQUEST_TEMPLATE.md | 0 CONTRIBUTING.md | 214 +++++++++++++ README.md | 339 ++++++++++++++++++--- utils/config.py => config.py | 5 +- db.py | 2 +- 11 files changed, 859 insertions(+), 39 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/development_task.md create mode 100644 .github/ISSUE_TEMPLATE/documentation.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CONTRIBUTING.md rename utils/config.py => config.py (70%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e69de29 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..57ee575 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,89 @@ +--- + +name: Bug Report +about: Report an issue or unexpected behavior in the project. +title: "[Bug]: " +labels: bug +assignees: "" +--- + +# Bug Report + +## Summary + +Provide a brief description of the bug. + +--- + +## Module + +Specify the module where the issue occurs. + +Example: + +* Telemetry +* OSDoctor +* FocusOS +* Blackbox +* Research Engine +* Dashboard + +--- + +## Steps to Reproduce + +Describe the steps required to reproduce the issue. + +1. +2. +3. + +--- + +## Expected Behavior + +Describe what should happen. + +--- + +## Actual Behavior + +Describe what actually happens. + +--- + +## Logs / Error Messages + +Paste any relevant logs, stack traces, or terminal output. + +```text +``` + +--- + +## Screenshots + +If applicable, attach screenshots or screen recordings. + +--- + +## Environment + +Please provide the following information: + +* Operating System: +* Programming Language / Version: +* Browser (if applicable): +* Other relevant details: + +--- + +## Possible Solution (Optional) + +If you have an idea of what might be causing the issue or how it could be fixed, describe it here. + +--- + +## Additional Notes + +Add any other information that may help reproduce or resolve the issue. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..2e1c553 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,10 @@ +blank_issues_enabled: false + +contact_links: +- name: 💬 Questions & Discussions + about: Ask questions or discuss ideas before creating a new issue. + url: https://github.com/devlup-labs/CogniOS/discussions + +- name: 📖 Project Documentation + about: Read the project documentation before opening an issue. + url: https://github.com/devlup-labs/CogniOS#readme diff --git a/.github/ISSUE_TEMPLATE/development_task.md b/.github/ISSUE_TEMPLATE/development_task.md new file mode 100644 index 0000000..5b1f58f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/development_task.md @@ -0,0 +1,81 @@ +--- + +name: Development Task +about: Create a new feature implementation task. +title: "[Feature]: " +labels: enhancement +assignees: "" +--- + +# Development Task + +## Module + +Specify the module this feature belongs to. + +Example: + +* Telemetry +* OSDoctor +* FocusOS +* Blackbox +* Research Engine +* Dashboard + +--- + +## Objective + +Clearly describe the feature that needs to be implemented. + +--- + +## Problem Statement + +What problem does this feature solve? + +--- + +## Proposed Solution + +Describe how you plan to implement this feature. + +--- + +## Tasks + +Break the implementation into smaller tasks. + +* [ ] +* [ ] +* [ ] +* [ ] + +--- + +## Acceptance Criteria + +The feature will be considered complete when: + +* [ ] +* [ ] +* [ ] +* [ ] + +--- + +## Related Documentation + +Mention any documentation that should be referred to before implementation. + +Examples: + +* Module README +* API Documentation +* Architecture Documentation + +--- + +## Additional Notes + +Add any diagrams, references, or implementation notes that may help contributors. diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 0000000..48456e2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,88 @@ +--- + +name: Documentation +about: Suggest improvements or updates to the project documentation. +title: "[Docs]: " +labels: documentation +assignees: "" +--- + +# Documentation Update + +## Summary + +Provide a brief description of the documentation change. + +--- + +## Type of Documentation + +Select all that apply by replacing the space inside the brackets with an **`x`**. + +Example: + +```text +- [x] Module Documentation +- [ ] API Documentation +``` + +* [ ] README +* [ ] Module Documentation +* [ ] API Documentation +* [ ] Architecture Documentation +* [ ] Code Comments +* [ ] Setup Guide +* [ ] Other + +--- + +## Files Affected + +List the documentation files that need to be updated. + +Example: + +```text +docs/modules/backend.md +README.md +backend/api/README.md +``` + +--- + +## Description + +Describe what needs to be added, updated, or corrected. + +--- + +## Reason for the Update + +Explain why this documentation change is needed. + +Examples: + +* New feature added +* Existing documentation is outdated +* Missing documentation +* Incorrect information +* Improve clarity for contributors + +--- + +## Related Issue(s) + +Mention any related GitHub issue(s). + +Example: + +```text +Closes #12 +Related to #8 +``` + +--- + +## Additional Notes + +Include any references, screenshots, diagrams, or links that may help reviewers. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..68abd34 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,70 @@ +--- + +name: Feature Request +about: Propose a new feature or enhancement for the project. +title: "[Feature]: " +labels: enhancement +assignees: "" +--- + +# Feature Request + +## Summary + +Provide a brief description of the requested feature. + +--- + +## Module + +Specify the module this request is related to. + +Example: + +* Telemetry +* OSDoctor +* FocusOS +* Blackbox +* Research Engine +* Dashboard + +--- + +## Problem Statement + +Describe the current limitation or pain point. + +--- + +## Proposed Solution + +Describe your proposed feature and how it should work. + +--- + +## Alternatives Considered + +Describe any alternative approaches you have considered. + +--- + +## Expected Outcome + +Describe what success looks like after this feature is implemented. + +--- + +## Acceptance Criteria + +The feature will be considered complete when: + +* [ ] +* [ ] +* [ ] + +--- + +## Additional Context + +Add mockups, references, links, or any extra details that may help. + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e69de29 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5d095d6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,214 @@ +# Contributing to CogniOS + +First off, thank you for taking the time to contribute to **CogniOS**! 🎉 + +Whether you're fixing a bug, implementing a new feature, improving documentation, or optimizing existing code, your contributions are appreciated. + +Please read this guide before making your first contribution. + +--- + +# Development Workflow + +All development follows the GitHub Issue → Branch → Pull Request workflow. + +``` +Issue + ↓ +Assign Yourself + ↓ +Create a Branch + ↓ +Implement Changes + ↓ +Commit & Push + ↓ +Open Pull Request + ↓ +Code Review + ↓ +Merge +``` + +Direct pushes to the `main` branch are **not allowed**. + +--- + +# Before You Start + +Before working on a feature: + +1. Check the existing GitHub Issues. +2. Assign the issue to yourself. +3. If the required issue does not exist, create one describing the proposed feature or bug. +4. Wait for approval (if required) before beginning implementation. + +This helps prevent duplicate work and keeps development organized. + +--- + +# Branch Naming Convention + +Create a new branch from the latest `main` branch. + +Use the following naming convention: + +``` +feature/ +fix/ +docs/ +refactor/ +test/ +``` + +Examples: + +``` +feature/process-monitor +feature/anomaly-detector +fix/cpu-parser +docs/backend-api +refactor/cache-layer +``` + +--- + +# Coding Standards + +Please ensure your code follows these guidelines: + +* Write clean and readable code. +* Use meaningful variable and function names. +* Follow the coding style used within the module. +* Keep functions small and focused. +* Avoid unnecessary complexity. +* Remove unused code before submitting a Pull Request. +* Add comments only where they improve understanding. + +--- + +# Documentation + +Every contribution should include documentation updates whenever applicable. + +Documentation may include: + +* Function descriptions +* API endpoint updates +* Architecture changes +* README updates +* Module documentation + +If your implementation changes the behavior of a module, its documentation should also be updated. + +--- + +# Commit Message Guidelines + +Write clear and descriptive commit messages. + +Recommended format: + +``` +feat: implement process telemetry collector +fix: resolve memory parsing issue +docs: update backend documentation +refactor: simplify scheduler logic +test: add telemetry unit tests +``` + +Avoid vague commit messages such as: + +``` +update +changes +fixed stuff +final +temp +``` + +--- + +# Pull Request Guidelines + +Before opening a Pull Request: + +* Ensure your branch is up to date. +* Resolve merge conflicts. +* Verify the project builds successfully. +* Update relevant documentation. +* Ensure code follows project conventions. + +Each Pull Request should focus on **one feature or one bug fix**. + +Large unrelated changes should be split into multiple Pull Requests. + +--- + +# Code Review + +Every Pull Request will be reviewed before merging. + +Reviewers may request: + +* Code improvements +* Refactoring +* Additional documentation +* Bug fixes +* Performance improvements + +Please address review comments before requesting another review. + +--- + +# Testing + +Before submitting a Pull Request, verify that: + +* The project builds successfully. +* Existing functionality remains unaffected. +* New functionality works as intended. +* Any applicable tests pass successfully. + +Do not submit code that does not compile. + +--- + +# Reporting Bugs + +When reporting a bug, include: + +* Description of the issue +* Steps to reproduce +* Expected behavior +* Actual behavior +* Relevant logs or screenshots (if available) + +--- + +# Feature Requests + +When proposing a new feature, include: + +* Problem being solved +* Proposed solution +* Module(s) affected +* Expected outcome + +Discuss major architectural changes before beginning implementation. + +--- + +# Code of Conduct + +Please be respectful and collaborative. + +Constructive discussions and code reviews help improve the project for everyone. + +--- + +# Questions + +If you have questions regarding implementation, architecture, or project structure, please open a GitHub Discussion or contact the maintainers before starting work. + +Happy coding! 🚀 diff --git a/README.md b/README.md index ef75438..440d385 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,303 @@ -# CogniOS - Archietecture diagram -```mermaid -graph TD - subgraph System Environment - proc[/proc Filesystem/] - psutil[psutil wrapper] - end - - subgraph CogniOS Core - DB[(Central SQLite DB)] - TC[Module 1: Telemetry Collector\nBackground Daemon] - - TC -->|Writes real-time data| DB - proc --> TC - psutil --> TC - end - - subgraph Intelligent Modules - OD[Module 2: OS Doctor\nIsolation Forest] - FO[Module 3: FocusOS\nCNN Classifier] - BB[Module 4: BlackBox\nRolling Window / Replay] - RE[Module 5: Research Engine\nRL & Simulators] - end - - DB <-->|Reads Data / Writes Alerts| OD - DB <-->|Reads Heatmap / Writes Configs| FO - DB <-->|Reads Trace / Writes Narrative| BB - DB <-->|Reads Traces for Simulation| RE - - subgraph User Interface - DASH[Streamlit Dashboard\nUnified GUI] - end - - OD --> DASH - FO --> DASH - BB --> DASH - RE --> DASH +# CogniOS + +### Intelligent OS Observability & Adaptive Workload Optimization Platform + +> **CogniOS** is an AI-assisted Linux system observability and workload optimization platform that combines operating systems, machine learning, and systems engineering to provide intelligent performance monitoring, anomaly detection, workload-aware optimization, crash analysis, and scheduling research—all without modifying the Linux kernel. + +--- + +## Overview + +Modern operating systems expose a large amount of telemetry, but existing monitoring tools often present only raw statistics, leaving users to determine the root cause of performance issues themselves. + +CogniOS bridges this gap by collecting real-time system telemetry, analyzing workload behavior, detecting anomalies, recommending optimizations, and enabling post-crash analysis through an integrated and modular architecture. + +The project is designed as both: + +- A practical system observability platform for Linux +- A research framework for experimenting with AI-assisted operating system scheduling + +--- + +## Key Features + +- 📊 Real-time Linux system telemetry collection +- 🩺 Intelligent anomaly detection for performance degradation +- 🎯 AI-based workload classification and optimization +- 📦 Rolling telemetry recording for crash replay +- 🔬 Scheduling algorithm benchmarking and research +- 📈 Interactive Streamlit dashboard +- 🗄️ Centralized SQLite telemetry database +- 🧩 Modular architecture for independent development and testing + +--- + +# Architecture + +``` + Linux System + │ + ▼ + Telemetry Collectors + (/proc, psutil, process APIs) + │ + ▼ + SQLite Database + │ + ┌───────────────┼────────────────┐ + │ │ │ + ▼ ▼ ▼ + OS Doctor FocusOS BlackBox + │ │ │ + └───────────────┼────────────────┘ + ▼ + Research Engine + │ + ▼ + Streamlit Dashboard +``` + +--- + +# Core Modules + +## 🩺 OS Doctor + +OS Doctor continuously monitors the system for abnormal behavior using machine learning–based anomaly detection techniques. + +### Objective + +- Detect abnormal CPU, memory and I/O behavior +- Explain possible reasons for system slowdowns +- Identify unusual workload patterns +- Provide interpretable diagnostics from live telemetry + +--- + +## 🎯 FocusOS + +FocusOS is responsible for intelligent workload classification and adaptive system optimization. + +### Objective + +- Classify the current workload using machine learning +- Optimize process priorities +- Modify Linux scheduling parameters +- Improve overall system responsiveness + +Future versions may also support dynamic CPU affinity optimization and workload-aware scheduling. + +--- + +## 📦 BlackBox + +BlackBox acts as the system's flight recorder. + +It continuously stores recent telemetry in a rolling buffer, allowing developers to replay system activity after crashes or freezes. + +### Objective + +- Record recent system telemetry +- Preserve crash history +- Replay workload traces +- Assist in post-mortem debugging + +--- + +## 🔬 Research Engine + +The Research Engine provides an experimentation platform for scheduling algorithms. + +### Objective + +- Compare classical scheduling algorithms +- Evaluate AI-based schedulers +- Replay collected workloads +- Benchmark scheduling performance +- Support reinforcement learning experiments + +Supported scheduling algorithms include: + +- FCFS +- Shortest Job First +- Round Robin +- Priority Scheduling + +Future versions may include reinforcement learning schedulers. + +--- + +## 📊 Dashboard + +The Streamlit dashboard serves as the unified visualization layer of CogniOS. + +### Objective + +- Visualize system telemetry +- Display anomaly alerts +- Monitor workload classifications +- Compare scheduling results +- Provide a centralized monitoring interface + +--- + +## 📡 Collectors + +Collectors gather telemetry directly from Linux using lightweight system interfaces. + +### Objective + +- CPU monitoring +- Memory monitoring +- Disk monitoring +- Process monitoring +- Network statistics +- I/O statistics + +These collectors act as the data source for every other module. + +--- + +## 🗄️ Data Layer + +The data layer stores all telemetry generated by the collectors. + +### Responsibilities + +- Store real-time telemetry +- Maintain workload traces +- Persist crash recordings +- Provide data for ML training +- Support benchmarking experiments + +SQLite is used as the centralized storage backend. + +--- + +# Repository Structure + +``` +CogniOS/ +│ +├── blackbox/ # Rolling telemetry recorder and crash replay engine +├── collectors/ # System telemetry collectors using /proc and psutil +├── dashboard/ # Streamlit-based monitoring dashboard +├── data/ +│ ├── datasets/ # Labeled workload datasets for ML training +│ ├── telemetry/ # Raw telemetry snapshots +│ └── sqlite/ # SQLite database files +├── focusos/ # AI workload classifier and adaptive optimization engine +├── os_doctor/ # Real-time anomaly detection module +├── research_engine/ # Scheduling simulator and benchmarking framework +├── utils/ # Shared helper utilities used across modules +│ +├── cognios_as_daemon.py # Runs CogniOS as a background monitoring daemon +├── config.py # Global project configuration +├── db.py # SQLite database interface and helper functions +├── main.py # Main application entry point +├── overhead.py # Measures runtime overhead introduced by monitoring +├── requirements.txt # Python dependencies +└── README.md +``` + +--- + +# Technology Stack + +| Category | Technology | +| ---------------------- | ------------------------------- | +| Language | Python | +| System Monitoring | psutil, Linux `/proc` | +| Database | SQLite | +| Machine Learning | PyTorch | +| Data Processing | NumPy, Pandas | +| Anomaly Detection | Isolation Forest (scikit-learn) | +| Reinforcement Learning | Stable-Baselines3 | +| Dashboard | Streamlit | + +--- + +# Getting Started + +## Clone the Repository + +```bash +git clone https://github.com//CogniOS.git +cd CogniOS +``` + +## Install Dependencies + +```bash +pip install -r requirements.txt +``` + +## Launch CogniOS + +```bash +python main.py ``` + +To run CogniOS as a background service: + +```bash +python cognios_as_daemon.py +``` + +--- + +# Development Philosophy + +Each major subsystem is designed as an independent module with its own documentation, enabling contributors to work on individual components without affecting the rest of the project. + +Every module contains its own dedicated `README.md` describing: + +- Module architecture +- Directory structure +- Components +- APIs +- Data flow +- Usage +- Future work + +--- + +# Roadmap + +- Real-time Linux telemetry collection +- Intelligent anomaly detection +- AI-assisted workload optimization +- Crash replay and forensic analysis +- Scheduling benchmark suite +- Reinforcement learning scheduler +- GPU telemetry support +- Windows and macOS support + +--- + +# Contributing + +We welcome contributions from developers interested in: + +- Operating Systems +- Machine Learning +- Systems Programming +- Linux Internals +- Performance Engineering +- Data Engineering + +Before contributing, please read the documentation for the module you wish to work on. + +--- + +# License + +This project is released under the MIT License. + +--- + +# Acknowledgements + +CogniOS is developed as an educational and research-focused project exploring the intersection of **Operating Systems**, **Artificial Intelligence**, and **Systems Engineering**. + +The project aims to provide a modular platform for learning, experimentation, and innovation in intelligent system observability and workload optimization. diff --git a/utils/config.py b/config.py similarity index 70% rename from utils/config.py rename to config.py index bc59225..baa7aaa 100644 --- a/utils/config.py +++ b/config.py @@ -1,6 +1,9 @@ DB_PATH = "cognios_telemetry.db" -BLACKBOX_WINDOW_SEC = 1800 # 30 minutes +BLACKBOX_DB_PATH = "blackbox/blackbox.db" +BLACKBOX_WINDOW_SEC = 1800 # 30 minutes BLACKBOX_WARMUP_SEC = 60 # wait before detection starts +BLACKBOX_CRASH_GAP_SEC = 30 # SIGKILL-only fallback, not the primary check + BLACKBOX_Z_THRESHOLD = 2.8 # std deviations for spike BLACKBOX_SLOPE_THRESHOLD = 0.003 # %/sec rise = suspicious BLACKBOX_SUSTAINED_SEC = 30 # spike must last this long diff --git a/db.py b/db.py index dfedcdd..55a198a 100644 --- a/db.py +++ b/db.py @@ -1,7 +1,7 @@ """Shared database schema and read/write interface.""" import sqlite3 # for both layers import time # for layer 2 -from CogniOS.utils.config import DB_PATH +from config import DB_PATH # layer 1 db code starts here db_path = DB_PATH From 5aa0315b1a8e7dc432f86f1bd3fd071200ff26ed Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 7 Jul 2026 09:15:02 +0530 Subject: [PATCH 46/47] updated README diagram and remove os_doctor changes from module/Blackbox branch --- README.md | 23 +--- assets/image.png | Bin 0 -> 78340 bytes os_doctor/README.md | 142 -------------------- os_doctor/alerts.py | 1 + os_doctor/{i_forest.py => anomaly_model.py} | 0 os_doctor/{llm_layer.py => diagnostics.py} | 0 os_doctor/documentation.md | 76 ----------- os_doctor/featuring.py | 2 - os_doctor/streamlit.py | 1 - 9 files changed, 2 insertions(+), 243 deletions(-) create mode 100644 assets/image.png delete mode 100644 os_doctor/README.md create mode 100644 os_doctor/alerts.py rename os_doctor/{i_forest.py => anomaly_model.py} (100%) rename os_doctor/{llm_layer.py => diagnostics.py} (100%) delete mode 100644 os_doctor/documentation.md delete mode 100644 os_doctor/featuring.py delete mode 100644 os_doctor/streamlit.py diff --git a/README.md b/README.md index 440d385..2624089 100644 --- a/README.md +++ b/README.md @@ -34,28 +34,7 @@ The project is designed as both: # Architecture -``` - Linux System - │ - ▼ - Telemetry Collectors - (/proc, psutil, process APIs) - │ - ▼ - SQLite Database - │ - ┌───────────────┼────────────────┐ - │ │ │ - ▼ ▼ ▼ - OS Doctor FocusOS BlackBox - │ │ │ - └───────────────┼────────────────┘ - ▼ - Research Engine - │ - ▼ - Streamlit Dashboard -``` +![alt text](./assets/image.png) --- diff --git a/assets/image.png b/assets/image.png new file mode 100644 index 0000000000000000000000000000000000000000..1662e1524bbcc6c7ca678a2f194df06685e391f8 GIT binary patch literal 78340 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 literal 0 HcmV?d00001 diff --git a/os_doctor/README.md b/os_doctor/README.md deleted file mode 100644 index abd2aeb..0000000 --- a/os_doctor/README.md +++ /dev/null @@ -1,142 +0,0 @@ -OSDoctor -Real-Time Intelligent Operating System Monitoring & Anomaly Detection System - -Project Documentation - -1. Introduction - -OSDoctor is an intelligent observability system that continuously monitors operating system telemetry, detects abnormal system behavior using machine learning, identifies the likely root cause, and explains the issue in natural language. Its objective is to answer the question: “Why is my laptop slow right now?” - -2. Objectives - -- Monitor real-time system telemetry. -- Detect system anomalies automatically. -- Identify causes of performance issues. -- Detect CPU, memory, disk, and process-related faults. -- Explain issues in simple, human-readable language. -- Provide actionable alerts and recommendations. - - -3. System Architecture - -```text -CogniOS Telemetry Collector - │ - ▼ -OSDoctor (Data Extraction) - │ - ▼ -System Metrics + Process Metrics - │ - ▼ -SQLite Database - │ - ▼ -Feature Engineering - │ - ▼ -Isolation Forest - │ - ├── No Anomaly - │ │ - │ ▼ - │ Continue Monitoring - │ - └── Anomaly Detected - │ - ▼ - Alerts Table - │ - ▼ - LLM Explanation Layer - │ - ▼ - Streamlit Dashboard -``` - -4. File Structure - -```text -os_doctor/ -│── _init_.py # Marks the directory as a Python package -│── featuring.py # Feature engineering and preprocessing -│── i_forest.py # Isolation Forest model implementation -│── llm_layer.py # LLM integration and response generation -│── streamlit.py # Streamlit web application -│── README.md # Project overview and setup guide -``` - - -5. Function Documentation -6. Feature Engineering - -## Module Overview -The `feature.py` script serves as the core mathematical transformation layer for the **OS Doctor Anomaly Engine**. Its primary responsibility is to bridge the gap between low-frequency/high-frequency transactional data stored in the local SQLite database and the high-dimensional, uniform matrices required by the **Isolation Forest** machine learning pipeline. - -The module continuously processes a rolling **2-minute sliding window** of system and process performance telemetry, cleans missing data, applies advanced statistical transformations (rolling averages, gradients, and rates of change), and generates a unified vector payload for real-time anomaly detection. - ---- - -## Libraries Used - -### 1 pandas -We use pandas for faster vector calculations leveraging it's built in functions like rolling(), shift() and many more. - -### 2 sqlite3 -We use sqlite3 library to handle the telemetry database created prior. - -### 3 json -We use json library to parse the json object created in layer2. - -## Core Telemetry Pipeline Alignment Matrix - -Because our telemetry collection infrastructure drops metrics down at asynchronous cadences, this script enforces chronological structure across distinct dimensional boundaries: - -| Data Layer | Source Table | DB CADENCE | Target Window Scope | Base Matrix Shape | -| :--- | :--- | :--- | :--- | :--- | -| **Layer 1: System-Wide** | `system_telemetry` | Every 1 Second | Last 120 Seconds | $120 \times \text{metrics}$ | -| **Layer 2: Top Processes**| `process_telemetry`| Every 5 Seconds | Last 120 Seconds | $24 \times \text{metrics}$ | - ---- - -## Detailed Function Breakdown - -The feature engineering layer executes its operations deterministically through four functions: - -### 1. `extract_and_engineer_system(db_path)` -* **Intent:** Pulls the high-frequency global system indicators (Layer 1) and translates flat numbers into directional trends. -* **Mechanism:** * Queries the last **120 rows** from the `system_telemetry` table ordered by timestamp, reversing them in memory to form a clean chronological left-to-right timeline. - * Utilizes `pandas` to calculate moving windows (e.g., 30-second rolling averages for CPU consumption) to smooth out short-term spikes. - * Calculates **I/O Storm Rates** and **Scheduler Context Switch Acceleration** by taking the difference between the most current metric entry and past rows (`df['metric'] - df['metric'].shift(1)`). -* **Output Shape:** A flat, single-row pandas DataFrame: `[1 × num_system_features]`. - -### 2. `extract_and_engineer_processes(db_path)` -* **Intent:** Unpacks the denormalized JSON arrays for the Top 5 CPU and Top 5 RAM consumers, correcting the shape mismatch and handling the "cold start" baseline issue. -* **Mechanism:** - * Queries the last **24 rows** (representing 120 seconds of 5-second steps) from the `process_telemetry` table. - * **The Chronological Upsampling Transform:** Because the database contains 24 rows but the final matrix requires a 1-second grid alignment, it upsamples the process rows into a 120-second array using forward-filling (`.ffill()`). This ensures data points match at every single second ticker. - * **Zero-Imputation (Cold Start Fix):** If a rogue process bursts into the Top 5 list halfway through the window, its missing history blocks are auto-populated with `0.0` instead of letting `NaN` values break the mathematical tracking loops. - * Computes process acceleration curves and resource gradients ($\Delta \text{Metric} / \Delta t$). -* **Output Shape:** Two independent flat, single-row arrays: `[1 × num_cpu_features]` and `[1 × num_ram_features]`. - -### 3. `build_unified_vector(sys_vec, cpu_vec, ram_vec)` -* **Intent:** Combines separate feature spaces into a single high-dimensional coordinate vector. -* **Mechanism:** - * Takes the horizontal outputs generated by the system and process computation layers. - * Executes an optimized columnar concatenation step (`pandas.concat(axis=1)`) to stitch the arrays together. - * Enforces a rigid, static schema definition so that column indexes never drift, ensuring the input dimensions exactly match what the downstream Isolation Forest expects. -* **Output Shape:** A single, high-dimensional flat vector row: `[1 × total_combined_features]`. - -### 4. `get_inference_payload(db_path)` -* **Intent:** Serves as the centralized public orchestrator function called directly by the core daemon script loop. -* **Mechanism:** - * Acts as a non-blocking execution gatekeeper. - * First runs a safety health check on the database capacity. If the background collectors haven't yet logged the baseline buffer of 120 system records, this function gracefully exits returning `None`, preventing the ML models from calculating false positives on partial windows. - * Executes functions 1, 2, and 3 sequentially completely in-memory, then passes the finalized ready vector row directly down to the machine learning execution loop (`i_forest.py`). -* **Output:** An inference-ready `pandas.DataFrame` row or `None`. - -7. Machine Learning Model -8. LLM Explanation Layer -9. Dashboard -10. Installation -11. Future Scope \ No newline at end of file diff --git a/os_doctor/alerts.py b/os_doctor/alerts.py new file mode 100644 index 0000000..f19881c --- /dev/null +++ b/os_doctor/alerts.py @@ -0,0 +1 @@ +"""Alert helpers for OS Doctor.""" diff --git a/os_doctor/i_forest.py b/os_doctor/anomaly_model.py similarity index 100% rename from os_doctor/i_forest.py rename to os_doctor/anomaly_model.py diff --git a/os_doctor/llm_layer.py b/os_doctor/diagnostics.py similarity index 100% rename from os_doctor/llm_layer.py rename to os_doctor/diagnostics.py diff --git a/os_doctor/documentation.md b/os_doctor/documentation.md deleted file mode 100644 index e749e36..0000000 --- a/os_doctor/documentation.md +++ /dev/null @@ -1,76 +0,0 @@ -OSDoctor -Real-Time Intelligent Operating System Monitoring & Anomaly Detection System - -Project Documentation - -1. Introduction - -OSDoctor is an intelligent observability system that continuously monitors operating system telemetry, detects abnormal system behavior using machine learning, identifies the likely root cause, and explains the issue in natural language. Its objective is to answer the question: “Why is my laptop slow right now?” - -2. Objectives - -- Monitor real-time system telemetry. -- Detect system anomalies automatically. -- Identify causes of performance issues. -- Detect CPU, memory, disk, and process-related faults. -- Explain issues in simple, human-readable language. -- Provide actionable alerts and recommendations. - - -3. System Architecture - -```text -CogniOS Telemetry Collector - │ - ▼ -OSDoctor (Data Extraction) - │ - ▼ -System Metrics + Process Metrics - │ - ▼ -SQLite Database - │ - ▼ -Feature Engineering - │ - ▼ -Isolation Forest - │ - ├── No Anomaly - │ │ - │ ▼ - │ Continue Monitoring - │ - └── Anomaly Detected - │ - ▼ - Alerts Table - │ - ▼ - LLM Explanation Layer - │ - ▼ - Streamlit Dashboard -``` - -4. File Structure - -```text -os_doctor/ -│── _init_.py # Marks the directory as a Python package -│── featuring.py # Feature engineering and preprocessing -│── i_forest.py # Isolation Forest model implementation -│── llm_layer.py # LLM integration and response generation -│── streamlit.py # Streamlit web application -│── README.md # Project overview and setup guide -``` - - -5. Function Documentation -6. Feature Engineering -7. Machine Learning Model -8. LLM Explanation Layer -9. Dashboard -10. Installation -11. Future Scope diff --git a/os_doctor/featuring.py b/os_doctor/featuring.py deleted file mode 100644 index 1034c7c..0000000 --- a/os_doctor/featuring.py +++ /dev/null @@ -1,2 +0,0 @@ -""" featuring raw data to useful data.""" - diff --git a/os_doctor/streamlit.py b/os_doctor/streamlit.py deleted file mode 100644 index 9752436..0000000 --- a/os_doctor/streamlit.py +++ /dev/null @@ -1 +0,0 @@ -"""Streamlit Dashboard code goes here""" \ No newline at end of file From 5582bf2d9126bad71043c2f95980983118c659bc Mon Sep 17 00:00:00 2001 From: mitsmania Date: Tue, 7 Jul 2026 10:32:52 +0530 Subject: [PATCH 47/47] defined a function to get feature vector --- blackbox/feature_engineering.py | 46 ++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/blackbox/feature_engineering.py b/blackbox/feature_engineering.py index 6061905..707ad23 100644 --- a/blackbox/feature_engineering.py +++ b/blackbox/feature_engineering.py @@ -1 +1,45 @@ -#converts raw DB rows to statistical feature vector \ No newline at end of file +#converts raw DB rows to statistical feature vector +import numpy as np +from utils.config import BLACKBOX_WARMUP_SEC + + +def extract_feature_vector(rows: list[dict]) -> list[float] | None: + if len(rows) < BLACKBOX_WARMUP_SEC: + return None + #not enough data yet + + rows = list(reversed(rows)) + + cpu = [r["cpu_usage_percent"] for r in rows if r["cpu_usage_percent"] is not None] + mem = [r["memory_percent"] for r in rows if r["memory_percent"] is not None] + disk_r = [r["disk_read"] for r in rows if r["disk_read"] is not None] + disk_w = [r["disk_write"] for r in rows if r["disk_write"] is not None] + ctx = [r["cpu_ctx_switches"] for r in rows if r["cpu_ctx_switches"] is not None] + + mean_cpu = float(np.mean(cpu)) if cpu else 0.0 + max_cpu = float(np.max(cpu)) if cpu else 0.0 + cpu_variance = float(np.var(cpu)) if cpu else 0.0 + cpu_growth = float(cpu[-1] - cpu[0]) / len(cpu) if len(cpu) > 1 else 0.0 + + mean_ram = float(np.mean(mem)) if mem else 0.0 + mem_growth = float(mem[-1] - mem[0]) / len(mem) if len(mem) > 1 else 0.0 + + disk_combined = [r + w for r, w in zip(disk_r, disk_w)] + if disk_combined: + disk_threshold = float(np.mean(disk_combined)) + float(np.std(disk_combined)) + disk_spike_freq = sum(1 for d in disk_combined if d > disk_threshold) / len(disk_combined) + else: + disk_spike_freq = 0.0 + + ctx_rate = float(np.mean(np.diff(ctx))) if len(ctx) > 1 else 0.0 + + return [ + mean_cpu, + max_cpu, + cpu_growth, + cpu_variance, + mean_ram, + mem_growth, + disk_spike_freq, + ctx_rate, + ] \ No newline at end of file