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.
- Allowing direct user interventions such as terminating a selected process, and many much more conceptual possibly/impossible approaches.

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

# ------------------------
# Configuration constants

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(path):
    if not path:
        return False
    p = path.lower()
    if p.endswith(LOG_EXTENSIONS):
        return True
    for d in LOG_DIRS:
        if d and d.lower() in p:
            return True
    return False

# ------------------------
# Main application frame

class DiskWriteMonitorApp(wx.Frame):

    def __init__(self):
        super().__init__(None, title='Distinguisher9k - Disk Write Monitor', size=(920, 660))

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

        # Sorting: default by write throughput descending
        self.sort_col = 2
        self.sort_asc = False

        self._BuildUI()
        self.Center()

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

        hb_buttons = 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]:
            hb_buttons.Add(btn, 0, wx.ALL, 5)

        hb_filters = wx.BoxSizer(wx.HORIZONTAL)
        lbl_filters = wx.StaticText(panel, label='Filters:')
        self.txt_filter_name = wx.TextCtrl(panel)
        self.txt_filter_name.SetHint('Filter by process name (case-insensitive)')
        self.txt_filter_minkb = wx.TextCtrl(panel)
        self.txt_filter_minkb.SetHint('Min write KB/s (numeric)')
        self.txt_filter_minkb.SetMinSize((140, -1))
        hb_filters.Add(lbl_filters, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 8)
        hb_filters.Add(self.txt_filter_name, 1, wx.EXPAND | wx.RIGHT, 6)
        hb_filters.Add(self.txt_filter_minkb, 0)

        self.lst_processes = wx.ListCtrl(panel, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.BORDER_SUNKEN)
        self.lst_processes.InsertColumn(0, "Process Name", width=300)
        self.lst_processes.InsertColumn(1, "PID", width=80)
        self.lst_processes.InsertColumn(2, "Write KB/s", wx.LIST_FORMAT_RIGHT, 110)
        self.lst_processes.InsertColumn(3, "Open Log Files (max 3)", width=420)

        vsizer = wx.BoxSizer(wx.VERTICAL)
        vsizer.Add(hb_buttons, 0, wx.CENTER)
        vsizer.Add(hb_filters, 0, wx.EXPAND | wx.ALL, 10)
        vsizer.Add(self.lst_processes, 1, wx.EXPAND | wx.ALL, 10)

        panel.SetSizer(vsizer)

        # Bind events
        self.btn_start.Bind(wx.EVT_BUTTON, self.OnStart)
        self.btn_stop.Bind(wx.EVT_BUTTON, self.OnStop)
        self.btn_terminate.Bind(wx.EVT_BUTTON, self.OnTerminate)
        self.btn_export.Bind(wx.EVT_BUTTON, self.OnExport)
        self.lst_processes.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
        self.lst_processes.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
        self.txt_filter_name.Bind(wx.EVT_TEXT, self.OnFilterChanged)
        self.txt_filter_minkb.Bind(wx.EVT_TEXT, self.OnFilterChanged)

    # --- Button Handlers ---

    def OnStart(self, evt):
        if self.monitoring:
            return
        self.monitoring = True
        with self.lock:
            self.process_data.clear()
        self.selected_pid = None
        self._UpdateButtons()
        self.lst_processes.DeleteAllItems()

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

    def OnStop(self, evt):
        if not self.monitoring:
            return
        self.monitoring = False
        if self.monitor_thread.is_alive():
            self.monitor_thread.join(timeout=3)
        with self.lock:
            self.process_data.clear()
        self.selected_pid = None
        self._UpdateButtons()
        self.lst_processes.DeleteAllItems()

    def OnTerminate(self, evt):
        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 (psutil.NoSuchProcess, psutil.AccessDenied) as e:
            wx.MessageBox(f'Failed to terminate process: {e}', 'Error', wx.ICON_ERROR)

    def OnExport(self, evt):
        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():
                        if not pdata['write_history']:
                            continue
                        rate_kb = pdata['write_history'][-1] / 1024
                        logs = '; '.join(pdata['open_logs']) if pdata['open_logs'] else ''
                        writer.writerow([pdata['name'], pid, f"{rate_kb:.2f}", logs])
            wx.MessageBox(f'CSV saved successfully:\n{path}', 'Success', wx.ICON_INFORMATION)
        except Exception as e:
            wx.MessageBox(f'Failed to save CSV:\n{e}', 'Error', wx.ICON_ERROR)

    # --- List Control Handlers ---

    def OnItemSelected(self, evt):
        pid = evt.GetItem().GetData()
        self.selected_pid = pid
        self._UpdateButtons()

    def OnColClick(self, evt):
        col = evt.GetColumn()
        # Toggle sort order if same col, else sort ascending on new col
        if col == self.sort_col:
            self.sort_asc = not self.sort_asc
        else:
            self.sort_col = col
            self.sort_asc = True
        self.RefreshProcessList()

    def OnFilterChanged(self, evt):
        self.RefreshProcessList()

    def _UpdateButtons(self):
        if self.monitoring:
            self.btn_start.Disable()
            self.btn_stop.Enable()
            if self.selected_pid is not None:
                self.btn_terminate.Enable()
                self.btn_export.Enable()
            else:
                self.btn_terminate.Disable()
                self.btn_export.Disable()
        else:
            self.btn_start.Enable()
            self.btn_stop.Disable()
            self.btn_terminate.Disable()
            self.btn_export.Disable()

    # --- Monitoring background loop ---

    def _MonitorLoop(self):
        prev_write_bytes = {}

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

                    prev = prev_write_bytes.get(pid)
                    rate = (write_bytes - prev) if prev is not None else 0
                    prev_write_bytes[pid] = write_bytes

                    open_logs = []
                    try:
                        files = proc.open_files()
                        for f in 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 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'])
                    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']

                gone_pids = set(self.process_data.keys()) - set(snapshot.keys())
                for pid in gone_pids:
                    del self.process_data[pid]

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

    # --- Refresh process list UI with preservation of scroll & selection ---

    def RefreshProcessList(self):
        fn_filter = self.txt_filter_name.GetValue().strip().lower()
        try:
            min_thr = float(self.txt_filter_minkb.GetValue())
            if min_thr < 0:
                min_thr = 0.0
        except Exception:
            min_thr = 0.0

        # Save scroll pos and selection
        top_idx = self.lst_processes.GetTopItem()
        old_selected = self.selected_pid
        old_index = None
        count = self.lst_processes.GetItemCount()
        for i in range(count):
            if self.lst_processes.GetItemData(i) == old_selected:
                old_index = i
                break

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

        with self.lock:
            def sort_key(kv):
                pid, pdata = kv
                if not pdata['write_history']:
                    return -1
                # Sort based on column and asc/desc
                if self.sort_col == 0:
                    return pdata['name'].lower()
                elif self.sort_col == 1:
                    return pid
                elif self.sort_col == 2:
                    return pdata['write_history'][-1] / 1024
                elif self.sort_col == 3:
                    return len(pdata['open_logs'])
                return 0

            items = [(pid, pdata) for pid, pdata in self.process_data.items()]
            items.sort(key=sort_key, reverse=not self.sort_asc)

            selected_found = False
            row_idx = 0
            for pid, pdata in items:
                if not pdata['write_history']:
                    continue
                kb = pdata['write_history'][-1] / 1024
                if kb < min_thr:
                    continue
                if fn_filter and fn_filter not in pdata['name'].lower():
                    continue

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

                index = self.lst_processes.InsertItem(row_idx, pdata['name'])
                self.lst_processes.SetItem(index, 1, str(pid))
                self.lst_processes.SetItem(index, 2, f'{kb:.2f}')
                self.lst_processes.SetItem(index, 3, logs_disp)
                self.lst_processes.SetItemData(index, pid)

                # Zebra striping
                bg = wx.Colour(245, 245, 245) if row_idx % 2 == 0 else wx.WHITE
                self.lst_processes.SetItemBackgroundColour(index, bg)

                if pid == old_selected:
                    self.lst_processes.Select(index)
                    self.lst_processes.Focus(index)
                    selected_found = True

                row_idx += 1

        if not selected_found:
            self.selected_pid = None
            self.btn_terminate.Disable()
            self.btn_export.Disable()
        else:
            self.selected_pid = old_selected

        # Restore scroll position carefully
        try:
            visible_rows = max(1, self.lst_processes.GetSize().GetHeight() // self.lst_processes.GetItemHeight())
            self.lst_processes.ScrollList(top_idx, visible_rows)
        except Exception:
            pass

        self.lst_processes.Thaw()
        self._UpdateButtons()


# ------------------------
# Main

def main():
    app = wx.App(False)
    frm = DiskWriteMonitorApp()
    frm.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.
when you scroll mazafaka and mazafkas updating then scroll auto-scrolls upward to the top.
that vertical scrollbar auto-jumps towards the top on each list of processes update, not like it trying to fix its current coordinates properly.