External Collectors

Desktop Info can display data collected by an external collector. A collector is a stand alone application. You can use any tool at your disposal to write a collector that’s capable of collecting the data you need, constructing XML and writing it to a Windows shared memory area. The XML format must follow the format below. The collector implementation details are left to the author.

Once you have a collector running and writing XML to shared memory, you simply add a COLLECTOR item to your config file. The name option is the name of the shared memory area to which your collector is writing. That’s it. Desktop Info will attempt to open the shared memory and if successful, read the XML and convert it to data. Any errors in reading the data are noted in the log when error or debugonerror is enabled.

Your collector can collect as much data as you need and stuff it into a single shared memory area. One or many COLLECTOR items can read from a single shared memory area but only one collector can write to a given shared memory area. A single collector may create multiple shared memory areas for different types of data.

Sample Delphi Collector

This is the complete text of the MainForm.pas for my sample Delphi 10.3 collector. It has 2 constant strings containing the name used for the shared memory area and a sample XML. The shared area name is case sensitive. In the FormCreate, the shared memory area is created. This stays open for the life of the application. In the Button1Click event, the XML is written to the shared memory. In between these two things is where you collect your data and convert it to XML. In the FormClose event, the shared memory area is closed.

My little sample collector has a memo on the main form so I can experiment with the XML, the data, the data types etc, then I click the button to write that XML to the shared memory.

I have set the area size to 4096. This seems to be the page granularity of memory. If you need a bigger area just make this bigger. The actual area size created will be rounded up to the next whole page. Although it’s not documented anywhere, it seems the security attributes is absolutely necessary to create the shared memory. I’m using WideChar because that seems to be compatible with C++.

unit MainForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
    procedure WriteData(data: String);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

var
  HSharedArea: Cardinal;
  PSharedArea: Pointer;
  AreaSize : DWORD = 4096;
  SecAttr : TSecurityAttributes;
  pSecAttr: PSecurityAttributes;
  SecDesc : TSecurityDescriptor;

const
  CollectorName = 'SampleCollector';
  SampleXml = '<desktop_info>'#13#10+
              '  <raw_values>'#13#10+
              '    <row>'#13#10+
              '      <data name="col1" type="3">250</data>'#13#10+
              '      <data name="col2" type="5">115.739</data>'#13#10+
              '      <data name="col3" type="256">Hello!</data>'#13#10+
              '    </row>'#13#10+
              '  </raw_values>'#13#10+
              '</desktop_info>';

  // data type is System variant type codes

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
     // security attributes
     // Create a Null DACL, which is basically "wide-open" security. Access will never be denied.
     pSecAttr := nil;
     if (InitializeSecurityDescriptor(@SecDesc, SECURITY_DESCRIPTOR_REVISION) and
         SetSecurityDescriptorDacl(@SecDesc, TRUE, nil, FALSE)) then
     begin
          SecAttr.nLength := sizeof(SecAttr);
          SecAttr.lpSecurityDescriptor := @SecDesc;
          SecAttr.bInheritHandle := False;
          pSecAttr := @SecAttr;
     end;

     // create or open a named mapping object
     HSharedArea := CreateFileMapping(INVALID_HANDLE_VALUE,
                                      pSecAttr,
                                      PAGE_READWRITE,
                                      0,
                                      AreaSize,
                                      PChar(CollectorName));

     // create the mapped view
     // offset=0, size=all
     if (HSharedArea <> NULL) and (HSharedArea > 0) then
        PSharedArea := MapViewOfFile(HSharedArea, FILE_MAP_WRITE, 0, 0, 0);

     Memo1.Text := SampleXml;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
     UnmapViewOfFile(PSharedArea);
     CloseHandle(HSharedArea);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
     WriteData(Memo1.Text);
end;

procedure TForm1.WriteData(data: String);
Var ws : WideString;
    pw : PWideChar;
begin
     if assigned(PSharedArea) then
     begin
          // zero out the shared memory so there's no hangover from previous data
          FillChar(PSharedArea^, AreaSize, 0);
          // now put the data in, widechar is compatible with C++ ?
          ws := data;
          pw := PWideChar(ws);
          CopyMemory(PSharedArea, pw, Length(ws)*SizeOf(WideChar));
     end;
end;

end.

Desktop Info Configuration

With your collector running and writing XML to the shared memory, add a COLLECTOR item to your Desktop Info ini file.

[items]
COLLECTOR=name:SampleCollector, interval:1, display:%1

If all is working, Desktop Info will display your collector data.

You can have many items displaying multiple shared memory areas or many items displaying the same area but for each shared memory area you need at least one COLLECTOR item.

[items]
COLLECTOR=name:SampleCollector, interval:1, display:%1 %2
COLLECTOR=name:SampleCollector, interval:1, display:%3
COLLECTOR=name:JoeCollector, interval:1, display:%joe_data%

XML Format

The name attribute is the column name that can be used in the display template. The type is the data type listed below.

<desktop_info>
  <raw_values>
    <row>
      <data name="col1" type="3">25</data>
      <data name="col2" type="258">Hello World</data>
      <data name="col3" type="6">40.55</data>
    </row>
  </raw_values>
</desktop_info>

Data Types

Numeric values may be formatted in the display template and charted in the usual way. Strings may contain expressions and/or user variables.

2 = smallint
3 = integer
4 = single
5 = double
6 = currency
7 = date (TDateTime)
8 = bstr
11 = bool
16 = shortint
17 = byte
18 = word
19 = longword / uint32
20 = int64
21 = uint64

256 = pascal string
258 = unicode string
Glenn's Page