1

Well, I'm using VirtualStringTree to create kind of a process manager...

I run into trouble because of updating the tree with a timer set to 1000ms (cpu usage is too high for my application retrieving a lot of data (filling about 20 columns).

So I wonder how would one build kind of a cache system so I can update the tree only when something changed which I guess seems to be the key decrementing the cpu usage for my application a lot?

Snip:

type
  TProcessNodeType = (ntParent, ntDummy);

  PProcessData = ^TProcessData;

  TProcessData = record

   pProcessName : String;
   pProcessID,
   pPrivMemory,
   pWorkingSet,
   pPeakWorkingSet,
   pVirtualSize,
   pPeakVirtualSize,
   pPageFileUsage,
   pPeakPageFileUsage,
   pPageFaults : Cardinal;
   pCpuUsageStr: string;
   pIOTotal: Cardinal;
...

  end;

If my application starts I fill the tree with all running processes. Remember this is called only once, later when the application runs I got notified of new processes or processes which are terminated via wmi so I dont need to call the following procedure in the timer later to update the tree...

procedure FillTree;
begin
var
  NodeData: PProcessData;
  Node: PVirtualNode;
  ParentNode: PVirtualNode;
  ChildNode: PVirtualNode;
  Process: TProcessItem;
  I : Integer;
begin
   ProcessTree.BeginUpdate;
   for I := 0 to FRunningProcesses.Count - 1 do
  begin
    Process := FRunningProcesses[i];

    NodeData^.pProcessID := ProcessItem.ProcessID;
    NodeData^.pProcessName := ProcessItem.ProcessName;

...

I have a Class which will retrieve all the data I want and store it into the tree like:

var
  FRunningProcesses: TProcessRunningProcesses;

So if I want to enumerate all running processes I just give it a call like:

  // clears all data inside the class and refills everything with the new data... 
  FRunningProcesses.UpdateProcesses;

The problem starts here while I enumerate everything and not only data which had changed which is quite cpu intensive:

procedure TMainForm.UpdateTimerTimer(Sender: TObject);
var
  NodeData: PProcessData;
  Node : PVirtualNode;
  Process: TProcessItem;
  I: Integer;
begin
   for I := 0 to FRunningProcesses.Count - 1 do
   begin
      Application.ProcessMessages;

      Process := FRunningProcesses[I];

      // returns PVirtualNode if the node is found inside the tree
      Node := FindNodeByPID(Process.ProcessID);

      if not(assigned(Node)) then
      exit;

      NodeData := ProcessVst.GetNodeData(Node);

      if not(assigned(NodeData)) then
       exit;

     // now starting updating the tree 
     // NodeData^.pWorkingsSet := Process.WorkingsSet; 
....

Basically the timer is only needed for cpu usage and all memory informations I can retrieve from a process like:

  • Priv.Memory
  • Working Set
  • Peak Working Set
  • Virtual Size
  • PageFile Usage
  • Peak PageFile Usage
  • Page Faults
  • Cpu Usage
  • Thread Count
  • Handle Count
  • GDI Handle Count
  • User Handle Count
  • Total Cpu Time
  • User Cpu Time
  • Kernel Cpu Time

So I think the above data must be cached and compared somehow if its changed or not just wonder how and what will be most efficient?

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
stOrM
  • 103
  • 1
  • 9

2 Answers2

0

I'd recommend you to have your VT's node data point directly to TProcessItem.

Pro's:

  1. Get rid of FindNodeByPID. Just update all the items from FRunningProcesses and then call VT.Refresh. When the process is terminated, delete corresponding item from FRunningProcesses. Currently you have quite expensive search in FindNodeByPID where you loop through all VT nodes, retrieve their data and check for PID.
  2. Get rid of Process := FRunningProcesses[I] where you have unnecessary data copy of the whole TProcessData record (btw, that should be done anyway, use pointers instead).
  3. Get rid of the whole // now starting updating the tree block.
  4. In general, by this change you decrease excess entities what is very good for application updating and debugging.

Con's:

  1. You'll have to keep VT and FRunningProcesses in sync. But that's quite trivial.
Fr0sT
  • 2,959
  • 2
  • 25
  • 18
0

You need only update the data in nodes which are currently are visible. you can use vst.getfirstvisible vst.getnextvisible to iterate thru these nodes.

the second way is also easy. use objects instead of the record. sample code of object usage

use getters for the different values. those getters query the processes for the values. maybe you need here a limit. refresh data only every second.

now you only need to set the vst into an invalidated status every second.

vst.invalidate

this forced the vst to repaint the visible area.

but all this works only if your data is not sorted by any changing values. if this necessary you need to update all record and this is your bottle neck - i think. remember COM and WMI are much slower than pure API. avoid (slow) loops and use a profiler to find the slow parts.

coding Bott
  • 4,287
  • 1
  • 27
  • 44
  • Its already no secret that wmi has bad performance. On the other hand I don't know any api which will tell you if a process died or is created.

    I don't want to refresh my whole list every second just for a process which was created or terminated and only this process is added or deleted to the tree not all anytime.
    I actually dont know of any api which will tell you about process creation or termination unless you would like to hook csrss or wring a driver and register for PsSetCreateProcessNotifyRoutine which I definitly cant :(
    – stOrM Jul 07 '11 at 11:18
  • 1
    @stOrM - then you will need to slow down the refresh. I would also start with the separated thread which will read the process data. If there will be the difference against the last process data then update the node (using a message synchronization) otherwise do nothing. You also don't need to refresh the whole VT and don't call `Application.ProcessMessages`; it might flicker a lot. –  Jul 07 '11 at 11:33
  • @daemon_x it sounds good so far ... just dont acutally get what to do with the pvirtualnode inside of my processcollection which I later need to store inside the tree record (maybe its to early for me to follow :)) – stOrM Jul 07 '11 at 11:45
  • @stOrM - the idea was that you will have a separate thread which will iterate through your process collection and check the last collected values against the current. If you notice a change then you will say; cool I have a change at PageFileUsage in the nth record (where the corresponding node will be present), I need to refresh it to the user. So your worker thread gives the data to the main thread and call `TreeView.InvalidateNode` which will refresh just this node. I'm not sure what is the most efficient but I've seen here on SO many of these solution. –  Jul 07 '11 at 11:59
  • @stOrM - I was asking the similar [here](http://stackoverflow.com/questions/5586257/is-it-safe-to-access-vt-data-from-the-other-thread), and I solved this with the critical sections locks. –  Jul 07 '11 at 12:13
  • @daemon_x or bernd could you roughly sketch out how the thread should look like especially the part where the background thread contains the PVirtualNode? (not asking to write me a complete solution here just roughly to get an idea of how things should looks like got a little confused about all that stuff now). Maybe just to mention where I fetch all informations which will be stored in the tree records basically looks like: TProcessItem = class(TCollectionItem) = All fields are stored there like FProcessName... Then TProcessCollection = class(TOwnedCollection) contains the UpdateProcess – stOrM Jul 07 '11 at 13:25
  • @stOrM - I'll try to post here what I've used some time ago when I get home –  Jul 07 '11 at 13:49