Distinguisher9k is a Windows system utility designed for detailed real-time monitoring and analysis of disk write activity on a per-process basis. It focuses on identifying which processes are persistently writing data to the disk, especially in the form of log files or system event logs, which can contribute to constant disk activity preventing drive idling.

Specifically, the tool addresses scenarios like the one you shared earlier, where system processes such as "System" and "svchost.exe" write continuously to various system log files and NTFS metadata files, including but not limited to:

- Windows Event Trace Log files (.etl) in directories like C:\Windows\System32\LogFiles\WMI\RtBackup\
- Local service heartbeat files like lastalive0.dat within user service profile directories
- Critical file system metadata files such as $LogFile, $Mft, $BitMap, and USN Journal ($UsnJrnl:$J), which are essential to the NTFS file system's operation
- Registry log files (e.g., SYSTEM.LOG1 and SYSTEM) in C:\Windows\System32\Config\
- Prefetch files used by Windows to optimize boot times and application loading

These constant low-level disk writes generate continuous IO activity, which can cause the physical disk to stay active without resting, impacting system performance or power management.

Distinguisher9k assists by:

- Continuously tracking disk write operations of all running processes with per-second granularity.
- Listing active processes sorted by recent write throughput to surface the highest disk consumers.
- Detecting and highlighting which files each process has open, focusing on files that are typical for logging or system tracing.
- Providing filtering mechanisms so users can quickly locate suspicious or unusually active processes by name or write volume thresholds.
- Rendering real-time charts that show the write activity trends of selected processes over approximately the last 60 seconds, helping in identifying periodic spikes or sustained logging behavior.
- Allowing direct user interventions such as terminating a selected process, exporting monitoring data to CSV files, and applying filters to focus on relevant processes.

By correlating processes with their open log files and visually presenting disk write trends, the tool empowers system administrators and power users to pinpoint potentially misbehaving or overly verbose drivers, services, or applications contributing to constant disk usage.

This is crucial for scenarios where system diagnostic logging (like ETW sessions, WMI logs, and system registry activities) combined with core OS metadata maintenance leads to persistent disk throughput that prevents HDD spin-down and increases wear or energy consumption. The tool helps identify such "chatty" processes so they can be investigated, optimized, or curtailed, resulting in better system responsiveness and potentially extended hardware life.



```

import wx
import psutil
import threading
import time
import os
import csv

# Simple heuristic for log files (extensions + typical log dirs)
LOG_EXTENSIONS = ('.log', '.etl', '.evtx', '.txt')
LOG_DIRS = [
    os.path.expandvars(r'%SystemRoot%\System32\LogFiles'),
    os.path.expandvars(r'%ProgramData%'),
    os.path.expanduser(r'~\\AppData\\Local'),
    os.path.expanduser(r'~\\AppData\\Roaming'),
]

def is_log_file(filepath):
    if not filepath:
        return False
    fp = filepath.lower()
    if fp.endswith(LOG_EXTENSIONS):
        return True
    for d in LOG_DIRS:
        if d.lower() in fp:
            return True
    return False

class DiskWriteMonitor(wx.Frame):
    def __init__(self):
        super().__init__(None, title="Distinguisher9k - Disk Write Monitor", size=(800, 600))

        self.monitoring = False
        self.process_data = {}  # pid -> {'name': str, 'write_history': [int], 'open_logs': [str]}
        self.lock = threading.Lock()
        self.selected_pid = None

        self._build_ui()
        self.Centre()

    def _build_ui(self):
        panel = wx.Panel(self)

        # Control buttons
        btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.btn_start = wx.Button(panel, label="Start Monitoring")
        self.btn_stop = wx.Button(panel, label="Stop Monitoring")
        self.btn_stop.Disable()
        self.btn_terminate = wx.Button(panel, label="Terminate Process")
        self.btn_terminate.Disable()
        self.btn_export = wx.Button(panel, label="Export CSV")
        self.btn_export.Disable()
        for btn in (self.btn_start, self.btn_stop, self.btn_terminate, self.btn_export):
            btn_sizer.Add(btn, 0, wx.ALL, 5)

        # Filters
        filter_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.txt_filter_name = wx.TextCtrl(panel)
        self.txt_filter_name.SetHint("Filter by process name (case-insensitive)")
        self.txt_filter_throughput = wx.TextCtrl(panel)
        self.txt_filter_throughput.SetHint("Min write KB/s (numeric)")
        self.txt_filter_throughput.SetMinSize((150, -1))
        filter_sizer.Add(self.txt_filter_name, 1, wx.ALL | wx.EXPAND, 5)
        filter_sizer.Add(self.txt_filter_throughput, 0, wx.ALL, 5)

        # List control
        self.lst_processes = wx.ListCtrl(panel, style=wx.LC_REPORT | wx.BORDER_SUNKEN | wx.LC_SINGLE_SEL)
        self.lst_processes.InsertColumn(0, "Process Name", width=180)
        self.lst_processes.InsertColumn(1, "PID", width=80)
        self.lst_processes.InsertColumn(2, "Write KB/s", wx.LIST_FORMAT_RIGHT, 100)
        self.lst_processes.InsertColumn(3, "Open Log Files (max 3)", width=450)

        # Layout
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add(btn_sizer, 0, wx.CENTER)
        main_sizer.Add(filter_sizer, 0, wx.EXPAND)
        main_sizer.Add(self.lst_processes, 1, wx.ALL | wx.EXPAND, 5)

        panel.SetSizer(main_sizer)

        # Event bindings
        self.btn_start.Bind(wx.EVT_BUTTON, self.on_start)
        self.btn_stop.Bind(wx.EVT_BUTTON, self.on_stop)
        self.btn_terminate.Bind(wx.EVT_BUTTON, self.on_terminate)
        self.btn_export.Bind(wx.EVT_BUTTON, self.on_export)
        self.lst_processes.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_process_selected)
        self.txt_filter_name.Bind(wx.EVT_TEXT, self.on_filter_changed)
        self.txt_filter_throughput.Bind(wx.EVT_TEXT, self.on_filter_changed)

    def on_start(self, event):
        if self.monitoring:
            return
        self.monitoring = True
        self.process_data.clear()
        self.selected_pid = None

        self.btn_start.Disable()
        self.btn_stop.Enable()
        self.btn_terminate.Disable()
        self.btn_export.Disable()
        self.lst_processes.DeleteAllItems()

        self.monitor_thread = threading.Thread(target=self.monitor_loop, daemon=True)
        self.monitor_thread.start()

    def on_stop(self, event):
        if not self.monitoring:
            return
        self.monitoring = False
        if self.monitor_thread.is_alive():
            self.monitor_thread.join(timeout=3)

        self.btn_start.Enable()
        self.btn_stop.Disable()
        self.btn_terminate.Disable()
        self.btn_export.Disable()
        self.selected_pid = None
        self.process_data.clear()
        self.lst_processes.DeleteAllItems()

    def on_terminate(self, event):
        if self.selected_pid is None:
            wx.MessageBox("No process selected.", "Error", wx.ICON_ERROR)
            return
        try:
            proc = psutil.Process(self.selected_pid)
            proc.terminate()
            wx.MessageBox(f"Terminated process {proc.name()} (PID {self.selected_pid})", "Success", wx.ICON_INFORMATION)
        except Exception as e:
            wx.MessageBox(f"Failed to terminate process: {e}", "Error", wx.ICON_ERROR)

    def on_export(self, event):
        with wx.FileDialog(self, "Save CSV report", wildcard="CSV files (*.csv)|*.csv",
                           style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as dlg:
            if dlg.ShowModal() != wx.ID_OK:
                return
            path = dlg.GetPath()
            try:
                with open(path, 'w', newline='', encoding='utf-8') as f:
                    writer = csv.writer(f)
                    writer.writerow(["Process Name", "PID", "Current Write KB/s", "Open Log Files"])
                    with self.lock:
                        for pid, pdata in self.process_data.items():
                            current_rate = (pdata['write_history'][-1] if pdata['write_history'] else 0) / 1024
                            logs = '; '.join(pdata['open_logs']) if pdata['open_logs'] else ''
                            writer.writerow([pdata['name'], pid, f"{current_rate:.2f}", logs])
                wx.MessageBox(f"CSV saved successfully:\n{path}", "Success", wx.ICON_INFORMATION)
            except Exception as e:
                wx.MessageBox(f"CSV save failed:\n{e}", "Error", wx.ICON_ERROR)

    def on_process_selected(self, event):
        self.selected_pid = event.GetItem().GetData()
        if self.selected_pid is not None:
            self.btn_terminate.Enable()
            self.btn_export.Enable()
        else:
            self.btn_terminate.Disable()
            self.btn_export.Disable()

    def on_filter_changed(self, event):
        self.refresh_process_list()

    def monitor_loop(self):
        prev_write_bytes = {}

        while self.monitoring:
            snapshot = {}
            for proc in psutil.process_iter(['pid', 'name']):
                try:
                    io_counters = proc.io_counters()
                    if io_counters is None:
                        continue
                    pid = proc.pid
                    name = proc.info.get('name', '?')
                    write_bytes = io_counters.write_bytes
                    prev = prev_write_bytes.get(pid, None)
                    rate = (write_bytes - prev) if prev is not None else 0
                    prev_write_bytes[pid] = write_bytes

                    open_logs = []
                    try:
                        for f in proc.open_files():
                            if is_log_file(f.path):
                                open_logs.append(f.path)
                    except (psutil.AccessDenied, psutil.NoSuchProcess):
                        pass

                    snapshot[pid] = {
                        'name': name,
                        'write_rate': rate,
                        'open_logs': open_logs
                    }
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    continue

            with self.lock:
                # Update or add process data
                for pid, data in snapshot.items():
                    if pid not in self.process_data:
                        self.process_data[pid] = {'name': data['name'], 'write_history': [], 'open_logs': []}
                    self.process_data[pid]['write_history'].append(data['write_rate'])
                    # Keep history limited to 60 samples
                    if len(self.process_data[pid]['write_history']) > 60:
                        self.process_data[pid]['write_history'].pop(0)
                    self.process_data[pid]['open_logs'] = data['open_logs']

                # Remove disappeared pids
                to_remove = set(self.process_data) - set(snapshot)
                for pid in to_remove:
                    del self.process_data[pid]

            wx.CallAfter(self.refresh_process_list)
            time.sleep(1)

    def refresh_process_list(self):
        fn_filter = self.txt_filter_name.GetValue().lower()
        try:
            throughput_filter = float(self.txt_filter_throughput.GetValue())
            if throughput_filter < 0:
                throughput_filter = 0.0
        except Exception:
            throughput_filter = 0.0

        self.lst_processes.Freeze()
        self.lst_processes.DeleteAllItems()

        with self.lock:
            procs = sorted(
                self.process_data.items(),
                key=lambda item: item[1]['write_history'][-1] if item[1]['write_history'] else 0,
                reverse=True
            )

            for idx, (pid, pdata) in enumerate(procs):
                if not pdata['write_history']:
                    continue

                kb_rate = pdata['write_history'][-1] / 1024
                if kb_rate < throughput_filter:
                    continue
                if fn_filter and fn_filter not in pdata['name'].lower():
                    continue

                logs_display = ', '.join(os.path.basename(p) for p in pdata['open_logs'][:3])
                if len(pdata['open_logs']) > 3:
                    logs_display += ', ...'

                index = self.lst_processes.InsertItem(idx, pdata['name'])
                self.lst_processes.SetItem(index, 1, str(pid))
                self.lst_processes.SetItem(index, 2, f"{kb_rate:.2f}")
                self.lst_processes.SetItem(index, 3, logs_display)
                self.lst_processes.SetItemData(index, pid)

        self.lst_processes.Thaw()

        # Disable terminate/export if selected pid disappeared or no longer matches filters
        if self.selected_pid not in self.process_data:
            self.selected_pid = None
            self.btn_terminate.Disable()
            self.btn_export.Disable()

def main():
    app = wx.App(False)
    frame = DiskWriteMonitor()
    frame.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()



```


"""
```



Here is a summary of the main mistakes and errors related to the overall construct that occurred throughout this chat:

    Missing Initialization of Key Attributes
    The matplotlib components (self.chart_ax, self.chart_fig, and self.chart_canvas) were sometimes not properly initialized as instance attributes before being used in methods like clear_chart(). This caused repeated AttributeError exceptions saying these attributes did not exist.

    Incorrect Use of wx.ListCtrl Methods
    The method ScrollList was called without the required two arguments (lines and per_page), causing a TypeError about insufficient arguments.

    Handling Updates in wx.ListCtrl
    Attempts to preserve selection and scroll position during list updates sometimes led to invisible or flickering rows in the process list. Complex logic for freezing/thawing the control and restoring the UI state was prone to causing visual glitches and instability.

    Confusion Over Insertion Methods
    Using InsertItem combined with SetItem and SetItemData for list control updates brought complexity and increased the chance of off-by-one or indexing errors, compared to simpler Append with a tuple of columns which worked more reliably.

    Premature or Improper UI Updates
    Calling UI update functions that referenced uninitialized or non-existing attributes resulted in errors. Calls to redraw or refresh controls were sometimes missing or misplaced, causing invisible or incomplete UI rendering.

    Thread-Safety and UI Thread Interaction
    While mostly addressed, there remained subtle risks due to improper synchronization between monitoring threads and UI updates. Using wx.CallAfter was required but not always consistently applied in earlier versions.

    Overcomplication and Repeated Rewrites
    Multiple rewrites of the entire app introduced features and complexity faster than could be fully tested and debugged, leading to recurring problems and oscillating issues instead of incremental, stable improvements.

    Ignoring Working Foundations
    Several later versions moved away from the simpler but more stable initial working code, adding unnecessary complexities that caused regressions in UI visibility and functionality.

    Inconsistent or Missing Comments and Documentation
    The clarity of the code and explanations was sometimes sacrificed, which hampered diagnosing issues and verifying correctness.

In summary, most errors were due to improper initialization order, incorrect GUI method calls, overcomplicated UI state management, and the challenges of properly coordinating background thread updates with the wxPython event-driven UI. The stable and effective solution relied on keeping UI updates simple, initializing graphics objects upfront, and carefully updating the list control with minimal but robust logic.


```
""".


we also probably do not need that matplotlib graph thing at the bottom at all.

you go dramatically improve overall thing and outshow thousand lines of pure malfuntioning chaos.