DiagnosticEngine class

A DiagnosticEngine keeps a list of collectors, runs them one after another, and returns what they collected. It has no collectors of its own: a program registers each one it wants, and the order of registration is the order of the results. It writes nothing to a screen or a file. Its results are read directly or passed to a formatter.

Dim engine As New DiagnosticEngine
engine.RegisterCollector New OSInfoCollector
engine.RegisterCollector New MemoryInfoCollector
engine.RegisterCollector New UptimeCollector

Dim results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))
Set results = engine.RunAll()

Dim i As Long
For i = 0 To results.Count - 1
    Debug.Print results.Item(i).Category & ": " & results.Item(i).Success
Next i
' Output:
' Operating System: True
' Memory: True
' Uptime: True

The result of every collector has the type DiagnosticResult(Of KeyValuePair(Of String, String)), so a list of results has the type List(Of DiagnosticResult(Of KeyValuePair(Of String, String))). The syntax lines on this page write the type out. The collectors are classes that implement IDiagnosticCollector; the package has eleven of them, and a program can add its own.

How a run proceeds

RunAll takes the collectors in the order in which they were registered. For each one it does these steps:

  1. It raises CollectorStarted.
  2. It raises Progress with the share of collectors that are already done, and the text Collecting: and the collector’s category name.
  3. It calls the collector’s Collect method, or its CollectSummary method for a summary run, and adds the result to Results.
  4. It raises CollectorCompleted.

When the last collector is done, it raises Progress with 100 and the text Complete, and then AllCompleted. A run is synchronous. All of this happens inside the call, in the thread of the caller.

Each notification exists twice, as an event and as a callback that a program sets with a property: OnCollectorStarted, OnProgress, OnCollectorCompleted and OnError. The engine raises the event first and calls the callback second, and does both when a program uses both. The callbacks are delegates. AllCompleted has no callback.

A collector that raises an error

A collector that raises a run-time error does not stop the run. The engine takes the text of the error (Err.Description), replaces the collector’s result with a new, empty result for the same category that holds the text as its ErrorMessage, and goes on with the next collector. The number and the source of the error are not kept. The engine raises Error for it before CollectorCompleted, which then reports success as False and an itemCount of 0. RunCategory and RunByRefreshMode handle an error in the same way.

A collector that fails without raising an error, such as one whose Windows call returns an error code, returns its own result with Success set to False. The engine does not call Error for it.

The samples on this page

The samples use two classes of the program. StubCollector is a collector that returns one item and can be told to fail. RunWatcher prints each event of an engine.

Class StubCollector
    Implements IDiagnosticCollector

    Public Category As String
    Public Fail As Boolean

    Private Property Get IDiagnosticCollector_CategoryName() As String
        Return Category
    End Property

    Private Property Get IDiagnosticCollector_Description() As String
        Return "A collector for the samples"
    End Property

    Private Property Get IDiagnosticCollector_RefreshMode() As RefreshMode
        Return RefreshMode.Static
    End Property

    Private Property Get IDiagnosticCollector_RequiresElevation() As Boolean
        Return False
    End Property

    Private Property Get IDiagnosticCollector_SupportsSummary() As Boolean
        Return False
    End Property

    Private Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))
        If Fail Then Err.Raise 5, "StubCollector", "stub failure"
        Dim result As DiagnosticResult(Of KeyValuePair(Of String, String))
        Set result = New DiagnosticResult(Of KeyValuePair(Of String, String))(Category)
        result.AddItem New KeyValuePair(Of String, String)("Answer", "42")
        Return result
    End Function

    Private Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))
        Return IDiagnosticCollector_Collect()
    End Function
End Class
Class RunWatcher
    Private WithEvents m_Engine As DiagnosticEngine

    Public Sub Attach(ByVal engine As DiagnosticEngine)
        Set m_Engine = engine
    End Sub

    Private Sub m_Engine_CollectorStarted(ByVal category As String)
        Debug.Print "started " & category
    End Sub

    Private Sub m_Engine_Progress(ByVal percent As Long, ByVal message As String)
        Debug.Print "progress " & percent & " " & message
    End Sub

    Private Sub m_Engine_Error(ByVal category As String, ByVal errorMessage As String)
        Debug.Print "error " & category & " " & errorMessage
    End Sub

    Private Sub m_Engine_CollectorCompleted(ByVal category As String, ByVal success As Boolean, ByVal itemCount As Long)
        Debug.Print "completed " & category & " " & success & " " & itemCount
    End Sub

    Private Sub m_Engine_AllCompleted(ByVal results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String))))
        Debug.Print "all " & results.Count
    End Sub
End Class

With one collector that fails between two that do not:

Dim engine As New DiagnosticEngine
Dim watcher As New RunWatcher
watcher.Attach engine

Dim alpha As New StubCollector
Dim beta As New StubCollector
Dim gamma As New StubCollector
alpha.Category = "Alpha"
beta.Category = "Beta"
beta.Fail = True
gamma.Category = "Gamma"
engine.RegisterCollector alpha
engine.RegisterCollector beta
engine.RegisterCollector gamma

engine.RunAll
' Output:
' started Alpha
' progress 0 Collecting: Alpha
' completed Alpha True 1
' started Beta
' progress 33 Collecting: Beta
' error Beta stub failure
' completed Beta False 0
' started Gamma
' progress 67 Collecting: Gamma
' completed Gamma True 1
' progress 100 Complete
' all 3

Properties

CollectorCount

The number of registered collectors.

Syntax: engine.CollectorCount As Long

Read-only. It is the Count of the list that Collectors returns.

Collectors

The registered collectors, in the order of registration.

Syntax: engine.Collectors As List(Of IDiagnosticCollector)

Read-only. The property returns the list that the engine uses, not a copy. A collector added to the list directly is run, and the check for a duplicate category name that RegisterCollector makes is not made. A collector removed from it is no longer run. See List for the members of the list. Its index starts at 0.

IsRunning

Whether a run is in progress.

Syntax: engine.IsRunning As Boolean

Read-only. RunAll and RunByRefreshMode set it to True when they start and to False when their loop over the collectors ends. It is therefore True in the handlers of CollectorStarted, Error, CollectorCompleted and the Progress events that come before a collector, and False in the last Progress event (100, Complete) and in AllCompleted. RunCategory sets it to False before it raises CollectorCompleted. A run that a handler starts sets it to False when that run ends, while the outer run goes on.

OnCollectorCompleted

A callback for the end of each collector.

Syntax: engine.OnCollectorCompleted = AddressOf procedure

Write-only. The value is a DiagnosticCompleteHandler. The engine calls it after it raises CollectorCompleted, with the same three values.

OnCollectorStarted

A callback for the start of each collector.

Syntax: engine.OnCollectorStarted = AddressOf procedure

Write-only. The value is a DiagnosticCompleteHandler, the same delegate type as for the end of a collector. The engine calls it after it raises CollectorStarted, with the category name, success set to True and itemCount set to 0.

OnError

A callback for the error of a collector.

Syntax: engine.OnError = AddressOf procedure

Write-only. The value is a DiagnosticErrorHandler. The engine calls it after it raises Error, with the same two values.

OnProgress

A callback for the progress of a run.

Syntax: engine.OnProgress = AddressOf procedure

Write-only. The value is a DiagnosticProgressHandler. The engine calls it after it raises Progress, with the same two values.

The callbacks and Cancel are shown in this example, which stops the run when it reaches the collector Beta. The module holds the engine in a variable of its own for the callback to reach.

Public CallbackEngine As DiagnosticEngine

Public Sub StopAtBeta(ByVal percent As Long, ByVal message As String)
    Debug.Print percent & " " & message
    If message = "Collecting: Beta" Then CallbackEngine.Cancel
End Sub

Public Sub ShowCompleted(ByVal category As String, ByVal success As Boolean, ByVal itemCount As Long)
    Debug.Print "completed " & category & " " & itemCount
End Sub
Set CallbackEngine = New DiagnosticEngine
CallbackEngine.OnProgress = AddressOf StopAtBeta
CallbackEngine.OnCollectorCompleted = AddressOf ShowCompleted

Dim names() As String = Split("Alpha,Beta,Gamma", ",")
Dim c As StubCollector
Dim i As Long
For i = 0 To 2
    Set c = New StubCollector
    c.Category = names(i)
    CallbackEngine.RegisterCollector c
Next i

Dim results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))
Set results = CallbackEngine.RunAll()
Debug.Print results.Count
' Output:
' 0 Collecting: Alpha
' completed Alpha 1
' 33 Collecting: Beta
' completed Beta 1
' 100 Complete
' 2

Results

The results of the last RunAll.

Syntax: engine.Results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))

Read-only. It is the list that RunAll fills and returns, not a copy of it. RunAll empties it when it starts, so a variable that holds the result of an earlier run is empty or refilled once the next run has started. RunCategory and RunByRefreshMode do not add to it. Copy the items to a list of the program to keep them.

Dim engine As New DiagnosticEngine
engine.RegisterCollector New UptimeCollector

Dim first As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))
Set first = engine.RunAll()
Debug.Print first Is engine.Results   ' True
Debug.Print first.Count               ' 1

engine.UnregisterCollector "Uptime"
engine.RunAll
Debug.Print first.Count               ' 0
' Output:
' True
' 1
' 0

Methods

Cancel

Asks a run to stop before its next collector.

Syntax: engine.Cancel

Sets a flag that RunAll and RunByRefreshMode read before they start each collector. The collector that is running finishes. A run is synchronous, so the call can come only from code that runs during the run, which is a handler of an event or a callback. The two methods clear the flag when they start, so a call that comes before a run has no effect. RunAll still raises Progress with 100 and Complete and then AllCompleted after a cancel, with the results of the collectors that ran. RunCategory does not read the flag. See the example under OnProgress.

IsElevated

Whether the program runs with administrator rights.

Syntax: engine.IsElevated As Boolean

Calls IsUserAnAdmin of shell32.dll and returns True when the result is not zero. The engine does not use the value: it never reads the RequiresElevation property of its collectors, and the collectors of the package all return False for it.

New

Creates an engine.

Syntax: New DiagnosticEngine

The engine starts with no collectors, no results, no callbacks and IsRunning set to False.

RegisterCollector

Adds a collector to the engine.

Syntax: engine.RegisterCollector ( collector )

collector
required An object that implements IDiagnosticCollector.

The collector goes to the end of the list. Registering the same collector twice, or two collectors with the same CategoryName, is an error.

The method raises error 5 (Invalid procedure call or argument) in these cases, with the source DiagnosticEngine.RegisterCollector:

  • collector is Nothing. The text is Collector cannot be Nothing.
  • A registered collector has the same category name. The text is Collector already exists: and the name. The names are compared with the = operator, and the package has no Option Compare statement, so the comparison is binary and the letter case matters: Alpha and ALPHA are two names.
Dim engine As New DiagnosticEngine
Dim a As New StubCollector
a.Category = "Alpha"
engine.RegisterCollector a

On Error Resume Next
engine.RegisterCollector a
Debug.Print Err.Number & " " & Err.Source & ": " & Err.Description
Err.Clear
engine.RegisterCollector Nothing
Debug.Print Err.Number & " " & Err.Source & ": " & Err.Description
Debug.Print engine.CollectorCount
' Output:
' 5 DiagnosticEngine.RegisterCollector: Collector already exists: Alpha
' 5 DiagnosticEngine.RegisterCollector: Collector cannot be Nothing
' 1

RunAll

Runs every collector and returns the results.

Syntax: engine.RunAll [ ( useSummary ) ] As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))

useSummary
optional A Boolean. When True, a collector whose SupportsSummary property is True is asked for CollectSummary, and the others for Collect. Default: False.

Returns the list Results, which holds one result for each collector, in the order of registration. How a run proceeds describes the steps and the events. A run with no collector raises Progress with 100 and Complete and then AllCompleted with an empty list.

A summary result has IsSummary set to True when the collector sets it, as the built-in collectors that support a summary do.

Dim engine As New DiagnosticEngine
engine.RegisterCollector New CPUInfoCollector
engine.RegisterCollector New EnvironmentCollector

Dim results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))
Set results = engine.RunAll(True)
Debug.Print results.Item(0).Category & " " & results.Item(0).IsSummary
Debug.Print results.Item(1).Category & " " & results.Item(1).IsSummary
' Output:
' CPU Info False
' Environment True

RunByRefreshMode

Runs the collectors that have one refresh mode.

Syntax: engine.RunByRefreshMode ( mode [ , useSummary ] ) As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))

mode
required A RefreshMode member. Only the collectors whose RefreshMode property is equal to it run.
useSummary
optional A Boolean, as for RunAll. Default: False.

Returns a new list with one result for each collector that ran, in the order of registration. The list is not Results, which this method does not change. The method raises CollectorStarted, CollectorCompleted and Error for each collector, and neither Progress nor AllCompleted. It clears the flag of Cancel when it starts and reads it before each collector, as RunAll does.

Dim engine As New DiagnosticEngine
engine.RegisterCollector New OSInfoCollector
engine.RegisterCollector New MemoryInfoCollector
engine.RegisterCollector New LocaleCollector
engine.RegisterCollector New UptimeCollector

Dim changing As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))
Set changing = engine.RunByRefreshMode(RefreshMode.Dynamic)

Dim i As Long
For i = 0 To changing.Count - 1
    Debug.Print changing.Item(i).Category
Next i
Debug.Print engine.Results.Count
' Output:
' Memory
' Uptime
' 0

RunCategory

Runs the collector with one category name.

Syntax: engine.RunCategory ( categoryName [ , useSummary ] ) As DiagnosticResult(Of KeyValuePair(Of String, String))

categoryName
required A String, the CategoryName of a registered collector. The first collector with that name runs.
useSummary
optional A Boolean, as for RunAll. Default: False.

Returns the result of the collector. The result is not added to Results. The method raises CollectorStarted, CollectorCompleted and Error, and neither Progress nor AllCompleted.

When no collector has the name, the method raises no error and no event. It returns a result for categoryName with Success set to False and the message Collector not found: and the name.

Dim engine As New DiagnosticEngine
engine.RegisterCollector New UptimeCollector

Dim r As DiagnosticResult(Of KeyValuePair(Of String, String))
Set r = engine.RunCategory("Uptime")
Debug.Print r.Category & " " & r.Success & " " & r.ItemCount

Set r = engine.RunCategory("Nothing here")
Debug.Print r.Category & " " & r.Success & " " & r.ErrorMessage
' Output:
' Uptime True 5
' Nothing here False Collector not found: Nothing here

UnregisterCollector

Removes a collector from the engine.

Syntax: engine.UnregisterCollector ( categoryName )

categoryName
required A String, the category name of the collector to remove.

Removes the first collector with that name. A name that no collector has is ignored without an error. The names are compared as in RegisterCollector, so the letter case matters.

Events

AllCompleted

Raised when RunAll has run its last collector.

Syntax: Event AllCompleted ( results )

results
The Results list, as a List(Of DiagnosticResult(Of KeyValuePair(Of String, String))).

It follows the last Progress event. A run that was cancelled raises it too. There is no callback for it.

CollectorCompleted

Raised after a collector has returned its result.

Syntax: Event CollectorCompleted ( category, success, itemCount )

category
A String, the category name of the collector.
success
A Boolean, the Success property of the result. It is False when the collector raised an error, and when it reported a failure itself.
itemCount
A Long, the ItemCount property of the result.

CollectorStarted

Raised before a collector is run.

Syntax: Event CollectorStarted ( category )

category
A String, the category name of the collector.

Error

Raised when a collector raised a run-time error.

Syntax: Event Error ( category, errorMessage )

category
A String, the category name of the collector.
errorMessage
A String, the Err.Description of the error.

It is raised before CollectorCompleted for the same collector. It is not raised for a result that a collector returns with Success set to False, or for a category name that no collector has. A handler is written as m_Engine_Error for a variable m_Engine declared with WithEvents.

Progress

Raised before each collector of a RunAll, and at its end.

Syntax: Event Progress ( percent, message )

percent
A Long. Before a collector it is the number of collectors that are already done, times 100, divided by the number of collectors, and converted with CLng: with three collectors the values are 0, 33 and 67. At the end it is 100.
message
A String. Before a collector it is Collecting: and the category name of the collector. At the end it is Complete.

RunCategory and RunByRefreshMode do not raise it.

Delegates

The three delegate types are declared in a module of the package, DelegatesModule, which has no page of its own. They are the types of the callback properties. A procedure for a callback is written with the same arguments, and passed with AddressOf.

DiagnosticCompleteHandler

The type of OnCollectorStarted and OnCollectorCompleted.

Syntax: Delegate Sub DiagnosticCompleteHandler ( ByVal category As String, ByVal success As Boolean, ByVal itemCount As Long )

The arguments are those of CollectorCompleted. When the engine calls a procedure of this type for the start of a collector, success is True and itemCount is 0.

DiagnosticErrorHandler

The type of OnError.

Syntax: Delegate Sub DiagnosticErrorHandler ( ByVal category As String, ByVal errorMessage As String )

The arguments are those of Error.

DiagnosticProgressHandler

The type of OnProgress.

Syntax: Delegate Sub DiagnosticProgressHandler ( ByVal percent As Long, ByVal message As String )

The arguments are those of Progress.

See Also