Es wurden zwei neue Projekte (`EDMI.API` und `EDMIService`) zur Solution hinzugefügt und in die Build-Konfiguration integriert. Die Benutzeroberfläche (`frmAdmin_AppserviceTest.Designer.vb`) wurde angepasst, einschließlich Änderungen an Button-Größen, Positionen und `NumericUpDown1`-Werten. Die Logik für Benutzer- und Sitzungsinformationen wurde verbessert, um Null-Checks und die Anzeige kürzlich aktiver Benutzer zu unterstützen. Der Lasttest wurde optimiert: Verwendung von `ConcurrentBag`, Einführung von `IProgress` für UI-Feedback und Begrenzung der Parallelität. Beim Schließen des Validators (`frmMain.vb`) wurde ein DevExpress-Overlay hinzugefügt, um den Ladezustand anzuzeigen, und die Performance beim Neuaufbau des Grids verbessert. Allgemeine Verbesserungen umfassen explizite Typkonvertierungen, bessere Fehlerbehandlung und Logging.
300 lines
15 KiB
VB.net
300 lines
15 KiB
VB.net
Imports System.Collections.Concurrent
|
||
Imports System.Threading
|
||
Imports System.Threading.Tasks
|
||
Imports DigitalData.Modules.EDMI.API
|
||
Imports DigitalData.Modules.EDMI.API.Constants
|
||
Imports DigitalData.Modules.EDMI.API.DatabaseWithFallback
|
||
Imports DigitalData.Modules.Logging
|
||
Public Class frmAdmin_AppserviceTest
|
||
|
||
' ── Lasttest-Felder ────────────────────────────────────────────────────
|
||
Private _loadClients As List(Of Client) ' Typisiert auf Client statt Object
|
||
Private _loadTimer As System.Windows.Forms.Timer
|
||
Private _loadLog As New ConcurrentQueue(Of String)
|
||
Private _loadCts As CancellationTokenSource
|
||
Private _timerTick As Integer = 0
|
||
Private LOAD_CLIENT_COUNT As Integer = 10
|
||
Private Shared ReadOnly _rnd As New Random() ' ◄ ergänzen
|
||
Private Const RECENT_USER_MAX_AGE_MINUTES As Integer = 15 ' ◄ NEU – muss mit Service-Wert übereinstimmen
|
||
Private Sub SimpleButton1_Click(sender As Object, e As EventArgs) Handles SimpleButton1.Click
|
||
' Status abrufen
|
||
Dim oStatus = _EDMIClient.GetStatus()
|
||
|
||
If oStatus Is Nothing Then
|
||
LOGGER.Warn("GetStatus() returned null. AppService is offline or unreachable.")
|
||
Return
|
||
Else
|
||
LOGGER.Info(oStatus.ToString())
|
||
End If
|
||
|
||
' Einzelne Felder
|
||
MemoEdit1.Text = ""
|
||
|
||
MemoEdit1.Text &= $"Online-Status: {oStatus.IsOnline}" & Environment.NewLine
|
||
MemoEdit1.Text &= $"Channel-State: {oStatus.ChannelState}" & Environment.NewLine
|
||
MemoEdit1.Text &= $"Anzahl Verbindungen: {oStatus.ActiveConnections}" & Environment.NewLine
|
||
MemoEdit1.Text &= $"Cached Tables ({oStatus.CachedTableCount})" & Environment.NewLine
|
||
If oStatus.IsOnline And oStatus.ChannelState = "Opened" And oStatus.ActiveConnections > 0 And oStatus.CachedTableCount > 1 Then
|
||
lblServiceOnline.Visible = True
|
||
lblAppServiceOffline.Visible = False
|
||
Else
|
||
lblServiceOnline.Visible = False
|
||
lblAppServiceOffline.Visible = True
|
||
End If
|
||
Dim oClients = _EDMIClient.GetAllClientStatus()
|
||
MemoEdit1.Text &= "=============== Geöffnete Clients ===============" & Environment.NewLine
|
||
For Each oStatus In oClients
|
||
MemoEdit1.Text &= oStatus.ToString() & Environment.NewLine
|
||
LOGGER.Info(oStatus.ToString())
|
||
Next
|
||
Dim oStatus2 = _EDMIClient.GetServiceStatus()
|
||
|
||
If oStatus2 Is Nothing Then
|
||
LOGGER.Warn("GetServiceStatus() returned null. AppService is offline or unreachable.")
|
||
Return
|
||
Else
|
||
LOGGER.Info($"ServiceStatus: Sessions={oStatus2.ActiveSessionCount}")
|
||
End If
|
||
|
||
MemoEdit1.Text &= $"Aktive Sessions: {oStatus2.ActiveSessionCount}" & Environment.NewLine
|
||
MemoEdit1.Text &= "─────────────────────────────" & Environment.NewLine
|
||
MemoEdit1.Text &= "Aktive Benutzer:" & Environment.NewLine
|
||
|
||
If oStatus2.ActiveUsers IsNot Nothing AndAlso oStatus2.ActiveUsers.Count > 0 Then
|
||
For Each oUser In oStatus2.ActiveUsers
|
||
MemoEdit1.Text &= $" • {oUser}" & Environment.NewLine
|
||
Next
|
||
Else
|
||
MemoEdit1.Text &= " (keine)" & Environment.NewLine
|
||
End If
|
||
|
||
MemoEdit1.Text &= "─────────────────────────────" & Environment.NewLine
|
||
MemoEdit1.Text &= "Sessions (Id → Benutzer):" & Environment.NewLine
|
||
|
||
If oStatus2.ActiveSessions IsNot Nothing AndAlso oStatus2.ActiveSessions.Count > 0 Then
|
||
For Each oPair In oStatus2.ActiveSessions
|
||
MemoEdit1.Text &= $" {oPair.Key.Substring(0, Math.Min(8, oPair.Key.Length))}… → {oPair.Value}" & Environment.NewLine
|
||
Next
|
||
Else
|
||
MemoEdit1.Text &= " (keine)" & Environment.NewLine
|
||
End If
|
||
|
||
MemoEdit1.Text &= "─────────────────────────────" & Environment.NewLine
|
||
MemoEdit1.Text &= $"Kürzlich aktive Benutzer (letzte {RECENT_USER_MAX_AGE_MINUTES} Min.):" & Environment.NewLine
|
||
If oStatus2.RecentUsers IsNot Nothing AndAlso oStatus2.RecentUsers.Count > 0 Then
|
||
For Each oRecentUser In oStatus2.RecentUsers
|
||
MemoEdit1.Text &= $" • {oRecentUser}" & Environment.NewLine
|
||
Next
|
||
Else
|
||
MemoEdit1.Text &= " (keine)" & Environment.NewLine
|
||
End If
|
||
End Sub
|
||
|
||
Private Sub SimpleButton2_Click(sender As Object, e As EventArgs) Handles btnLoadStart.Click
|
||
If _loadTimer IsNot Nothing AndAlso _loadTimer.Enabled Then
|
||
MessageBox.Show("Lasttest läuft bereits.", "Hinweis", MessageBoxButtons.OK, MessageBoxIcon.Information)
|
||
Return
|
||
End If
|
||
|
||
LOAD_CLIENT_COUNT = CInt(NumericUpDown1.Value)
|
||
_loadLog = New ConcurrentQueue(Of String)
|
||
_loadCts = New CancellationTokenSource()
|
||
_timerTick = 0
|
||
|
||
btnLoadStart.Enabled = False
|
||
btnLoadStop.Enabled = True
|
||
btnLoadStop.Visible = True
|
||
MemoEdit2.Text = ""
|
||
MemoEdit2.Text &= $"[{Now:HH:mm:ss}] Baue {LOAD_CLIENT_COUNT} Verbindungen auf…" & Environment.NewLine
|
||
|
||
' ► IProgress für thread-sicheres UI-Feedback während des Aufbaus
|
||
Dim progress As New Progress(Of String)(Sub(msg)
|
||
MemoEdit2.Text &= msg & Environment.NewLine
|
||
MemoEdit2.SelectionStart = MemoEdit2.Text.Length
|
||
MemoEdit2.ScrollToCaret()
|
||
End Sub)
|
||
|
||
Dim token = _loadCts.Token
|
||
|
||
Task.Run(
|
||
Async Function()
|
||
' ► ConcurrentBag ersetzt List(Of Client) + SyncLock beim Hinzufügen
|
||
Dim bag As New System.Collections.Concurrent.ConcurrentBag(Of Client)
|
||
Dim errors As Integer = 0
|
||
|
||
Dim options As New ParallelOptions With {
|
||
.CancellationToken = token,
|
||
.MaxDegreeOfParallelism = Math.Min(LOAD_CLIENT_COUNT, 32) ' ◄ Threadpool schonen
|
||
}
|
||
|
||
Try
|
||
Parallel.For(0, LOAD_CLIENT_COUNT, options,
|
||
Sub(i)
|
||
token.ThrowIfCancellationRequested()
|
||
Try
|
||
Dim oClientName = $"PerformanceClient_{i + 1}"
|
||
Dim oClient = New Client(LOGCONFIG, CONFIG.Config.EDMIAppServer, oClientName)
|
||
Dim oConnected = oClient.Connect()
|
||
|
||
If oConnected Then
|
||
bag.Add(oClient)
|
||
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Client #{i + 1} verbunden")
|
||
Else
|
||
oClient.Dispose()
|
||
Interlocked.Increment(errors)
|
||
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Client #{i + 1} Connect() fehlgeschlagen")
|
||
End If
|
||
Catch ex As OperationCanceledException
|
||
' Abbruch gewünscht – still beenden
|
||
Catch ex As Exception
|
||
Interlocked.Increment(errors)
|
||
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Client #{i + 1} Fehler: {ex.Message}")
|
||
End Try
|
||
End Sub)
|
||
Catch ex As OperationCanceledException
|
||
' ◄ Parallel.For selbst kann werfen – abfangen
|
||
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Verbindungsaufbau abgebrochen.")
|
||
End Try
|
||
|
||
' ► Erst nach Abschluss: thread-sicher in _loadClients übertragen
|
||
Dim connectedClients = bag.ToList()
|
||
|
||
Me.Invoke(Sub()
|
||
' ◄ Zuweisung auf UI-Thread – keine Race Condition
|
||
_loadClients = connectedClients
|
||
FlushLoadLog()
|
||
MemoEdit2.Text &= $"[{Now:HH:mm:ss}] {_loadClients.Count} Clients aktiv, {errors} Fehler" & Environment.NewLine
|
||
If _loadClients.Count > 0 Then
|
||
StartLoadTimer()
|
||
Else
|
||
' ◄ Kein einziger Client verbunden → Test nicht starten
|
||
MemoEdit2.Text &= $"[{Now:HH:mm:ss}] Kein Client verbunden – Lasttest wird nicht gestartet." & Environment.NewLine
|
||
btnLoadStart.Enabled = True
|
||
btnLoadStop.Enabled = False
|
||
End If
|
||
End Sub)
|
||
End Function)
|
||
End Sub
|
||
Private Sub StopLoadTest()
|
||
_loadCts?.Cancel()
|
||
If _loadTimer IsNot Nothing Then
|
||
_loadTimer.Stop()
|
||
_loadTimer.Dispose()
|
||
_loadTimer = Nothing
|
||
End If
|
||
|
||
' Alle Client-Verbindungen sauber schließen
|
||
If _loadClients IsNot Nothing Then
|
||
For Each oClient In _loadClients
|
||
Try
|
||
oClient.Dispose()
|
||
Catch
|
||
End Try
|
||
Next
|
||
_loadClients.Clear()
|
||
End If
|
||
|
||
MemoEdit2.Text &= $"[{Now:HH:mm:ss}] Lasttest beendet." & Environment.NewLine
|
||
btnLoadStart.Enabled = True
|
||
btnLoadStop.Enabled = False
|
||
End Sub
|
||
|
||
' ── Timer für zyklische Lastoperationen ───────────────────────────────
|
||
Private Sub StartLoadTimer()
|
||
_loadTimer = New System.Windows.Forms.Timer With {.Interval = 1000} ' jede Sekunde
|
||
AddHandler _loadTimer.Tick, AddressOf LoadTimer_Tick
|
||
_loadTimer.Start()
|
||
End Sub
|
||
|
||
Private Sub LoadTimer_Tick(sender As Object, e As EventArgs)
|
||
_timerTick += 1
|
||
FlushLoadLog()
|
||
|
||
Task.Run(Sub()
|
||
If _loadClients Is Nothing OrElse _loadClients.Count = 0 Then Return
|
||
|
||
Dim oSnapshot As List(Of Client)
|
||
SyncLock _loadClients
|
||
oSnapshot = _loadClients.ToList()
|
||
End SyncLock
|
||
|
||
' ← MaxDegreeOfParallelism begrenzt gleichzeitige Service-Calls
|
||
Parallel.ForEach(oSnapshot,
|
||
New ParallelOptions With {
|
||
.CancellationToken = _loadCts.Token,
|
||
.MaxDegreeOfParallelism = 10 ' ← NEU: max. 10 gleichzeitige Calls
|
||
},
|
||
Sub(oClient As Client)
|
||
Try
|
||
Select Case _timerTick Mod 4
|
||
Case 0
|
||
' ► GetStatus je Client
|
||
Dim st = oClient.GetStatus()
|
||
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Status: Online={st.IsOnline}, Conn={st.ActiveConnections}")
|
||
|
||
Case 1
|
||
' ► Zufällige Tabelle aus dem Cache des jeweiligen Clients laden
|
||
Dim oCachedTables = oClient.GetStatus().CachedTables
|
||
If oCachedTables IsNot Nothing AndAlso oCachedTables.Count > 0 Then
|
||
Dim sTable As String
|
||
SyncLock _rnd
|
||
sTable = oCachedTables(_rnd.Next(0, oCachedTables.Count))
|
||
End SyncLock
|
||
Dim oResult = oClient.GetDatatableByName(sTable)
|
||
_loadLog.Enqueue($"[{Now:HH:mm:ss}] GetDatatableByName → {sTable} OK={oResult?.OK} Rows={oResult?.Table?.Rows.Count}")
|
||
Else
|
||
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Cache leer – kein Tabellen-Zugriff möglich")
|
||
End If
|
||
|
||
Case 2
|
||
' ► Cache-Zugriff
|
||
Dim st = oClient.GetStatus()
|
||
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Cached Tables: {st.CachedTableCount}")
|
||
|
||
Case 3
|
||
' ► Channel-State prüfen
|
||
Dim st = oClient.GetStatus()
|
||
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Channel: {st.ChannelState}")
|
||
End Select
|
||
Catch ex As OperationCanceledException
|
||
' Lasttest gestoppt
|
||
Catch ex As Exception
|
||
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Fehler: {ex.Message}")
|
||
End Try
|
||
End Sub)
|
||
|
||
Me.Invoke(Sub() FlushLoadLog())
|
||
End Sub)
|
||
End Sub
|
||
|
||
' ── Log-Einträge in ListBox schreiben ─────────────────────────────────
|
||
Private Sub FlushLoadLog()
|
||
Dim msg As String = Nothing
|
||
Dim added As Boolean = False
|
||
Dim sb As New System.Text.StringBuilder()
|
||
|
||
While _loadLog.TryDequeue(msg)
|
||
sb.AppendLine(msg)
|
||
added = True
|
||
End While
|
||
|
||
If added Then
|
||
MemoEdit2.Text &= sb.ToString()
|
||
MemoEdit2.SelectionStart = MemoEdit2.Text.Length
|
||
MemoEdit2.ScrollToCaret()
|
||
End If
|
||
End Sub
|
||
|
||
' ── Aufräumen beim Schließen ──────────────────────────────────────────
|
||
Private Sub frmAdmin_AppserviceTest_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
|
||
StopLoadTest()
|
||
End Sub
|
||
|
||
Private Sub btnLoadStop_Click(sender As Object, e As EventArgs) Handles btnLoadStop.Click
|
||
StopLoadTest()
|
||
End Sub
|
||
|
||
Private Sub frmAdmin_AppserviceTest_Load(sender As Object, e As EventArgs) Handles Me.Load
|
||
|
||
End Sub
|
||
End Class |