diff --git a/src/OpcPlcServer.cs b/src/OpcPlcServer.cs index 7ac3d014..e78a5ad2 100644 --- a/src/OpcPlcServer.cs +++ b/src/OpcPlcServer.cs @@ -22,6 +22,9 @@ namespace OpcPlc; public class OpcPlcServer { + private const int DefaultMinThreads = 20; + private const int DefaultCompletionPortThreads = 20; + private string[] _args; private CancellationTokenSource _cancellationTokenSource; private ImmutableList _pluginNodes; @@ -87,9 +90,16 @@ public async Task StartAsync(string[] args, CancellationToken cancellationToken LogLogo(); - Logger.LogInformation("Current directory: {currentDirectory}", Directory.GetCurrentDirectory()); - Logger.LogInformation("Log file: {logFileName}", Path.GetFullPath(Config.LogFileName)); - Logger.LogInformation("Log level: {logLevel}", Config.LogLevelCli); + ThreadPool.SetMinThreads(DefaultMinThreads, DefaultCompletionPortThreads); + ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads); + Logger.LogInformation( + "Min worker threads: {MinWorkerThreads}, min completion port threads: {MinCompletionPortThreads}", + minWorkerThreads, + minCompletionPortThreads); + + Logger.LogInformation("Current directory: {CurrentDirectory}", Directory.GetCurrentDirectory()); + Logger.LogInformation("Log file: {LogFileName}", Path.GetFullPath(Config.LogFileName)); + Logger.LogInformation("Log level: {LogLevel}", Config.LogLevelCli); // Show OPC PLC version. var fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); diff --git a/src/PlcServer.cs b/src/PlcServer.cs index 198bb5f5..e8d72b00 100644 --- a/src/PlcServer.cs +++ b/src/PlcServer.cs @@ -16,6 +16,7 @@ namespace OpcPlc; using System.Collections.Immutable; using System.Diagnostics; using System.IO; +using System.Linq; using System.Reflection; using System.Threading; @@ -50,10 +51,25 @@ public PlcServer(OpcPlcConfiguration config, PlcSimulation plcSimulation, TimeSe (state) => { try { + var curProc = Process.GetCurrentProcess(); + + ThreadPool.GetAvailableThreads(out int availWorkerThreads, out int availCompletionPortThreads); + _logger.LogInformation( - "Open sessions: {Sessions}, open subscriptions: {Subscriptions}", + "\n\t# Open sessions: {Sessions}\n" + + "\t# Open subscriptions: {Subscriptions}\n" + + "\t# Monitored items: {MonitoredItems:N0}\n" + + "\t# Working set {WorkingSet:N0} MB\n" + + "\t# Available worker threads: {AvailWorkerThreads:N0}\n" + + "\t# Available completion port threads: {AvailCompletionPortThreads:N0}\n" + + "\t# Thread count: {ThreadCount:N0}", ServerInternal.SessionManager.GetSessions().Count, - ServerInternal.SubscriptionManager.GetSubscriptions().Count); + ServerInternal.SubscriptionManager.GetSubscriptions().Count, + ServerInternal.SubscriptionManager.GetSubscriptions().Sum(s => s.MonitoredItemCount), + curProc.WorkingSet64 / 1024 / 1024, + availWorkerThreads, + availCompletionPortThreads, + curProc.Threads.Count); } catch { @@ -148,6 +164,9 @@ public override ResponseHeader CreateMonitoredItems( out MonitoredItemCreateResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { + results = default; + diagnosticInfos = default; + try { OperationContext context = ValidateRequest(requestHeader, RequestType.CreateMonitoredItems); @@ -164,9 +183,43 @@ public override ResponseHeader CreateMonitoredItems( return responseHeader; } + catch (ServiceResultException ex) when (ex.StatusCode == StatusCodes.BadNoSubscription) + { + MetricsHelper.RecordTotalErrors(nameof(CreateMonitoredItems)); + + _logger.LogDebug( + ex, + "Failed creating monitored items: {StatusCode}", + StatusCodes.BadNoSubscription.ToString()); + + return new ResponseHeader { ServiceResult = StatusCodes.BadNoSubscription }; + } + catch (ServiceResultException ex) when (ex.StatusCode == StatusCodes.BadSessionIdInvalid) + { + MetricsHelper.RecordTotalErrors(nameof(CreateMonitoredItems)); + + _logger.LogDebug( + ex, + "Failed creating monitored items: {StatusCode}", + StatusCodes.BadSessionIdInvalid.ToString()); + + return new ResponseHeader { ServiceResult = StatusCodes.BadSessionIdInvalid }; + } + catch (ServiceResultException ex) when (ex.StatusCode == StatusCodes.BadSecureChannelIdInvalid) + { + MetricsHelper.RecordTotalErrors(nameof(CreateMonitoredItems)); + + _logger.LogDebug( + ex, + "Failed creating monitored items: {StatusCode}", + StatusCodes.BadSecureChannelIdInvalid.ToString()); + + return new ResponseHeader { ServiceResult = StatusCodes.BadSecureChannelIdInvalid }; + } catch (Exception ex) { MetricsHelper.RecordTotalErrors(nameof(CreateMonitoredItems)); + _logger.LogError(ex, "Error creating monitored items"); throw; } @@ -182,6 +235,13 @@ public override ResponseHeader Publish( out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { + subscriptionId = default; + availableSequenceNumbers = default; + moreNotifications = default; + notificationMessage = default; + results = default; + diagnosticInfos = default; + try { OperationContext context = ValidateRequest(requestHeader, RequestType.Publish); @@ -216,48 +276,40 @@ public override ResponseHeader Publish( return responseHeader; } - catch (ServiceResultException ex) + catch (ServiceResultException ex) when (ex.StatusCode == StatusCodes.BadNoSubscription) { MetricsHelper.RecordTotalErrors(nameof(Publish)); - subscriptionId = default; - availableSequenceNumbers = default; - moreNotifications = default; - notificationMessage = default; - results = default; - diagnosticInfos = default; - - if (ex.StatusCode == StatusCodes.BadNoSubscription) - { - _logger.LogDebug( - "Failed to publish: {StatusCode}", - StatusCodes.BadNoSubscription.ToString()); - - return new ResponseHeader { ServiceResult = StatusCodes.BadNoSubscription }; - } + _logger.LogDebug( + ex, + "Failed to publish: {StatusCode}", + StatusCodes.BadNoSubscription.ToString()); - if (ex.StatusCode == StatusCodes.BadSessionIdInvalid) - { - _logger.LogDebug( - "Failed to publish: {StatusCode}", - StatusCodes.BadSessionIdInvalid.ToString()); + return new ResponseHeader { ServiceResult = StatusCodes.BadNoSubscription }; + } + catch (ServiceResultException ex) when (ex.StatusCode == StatusCodes.BadSessionIdInvalid) + { + MetricsHelper.RecordTotalErrors(nameof(Publish)); - return new ResponseHeader { ServiceResult = StatusCodes.BadSessionIdInvalid }; - } + _logger.LogDebug( + ex, + "Failed to publish: {StatusCode}", + StatusCodes.BadSessionIdInvalid.ToString()); - if (ex.StatusCode == StatusCodes.BadSecureChannelIdInvalid) - { - _logger.LogDebug( - "Failed to publish: {StatusCode}", - StatusCodes.BadSecureChannelIdInvalid.ToString()); + return new ResponseHeader { ServiceResult = StatusCodes.BadSessionIdInvalid }; + } + catch (ServiceResultException ex) when (ex.StatusCode == StatusCodes.BadSecureChannelIdInvalid) + { + MetricsHelper.RecordTotalErrors(nameof(Publish)); - return new ResponseHeader { ServiceResult = StatusCodes.BadSecureChannelIdInvalid }; - } + _logger.LogDebug( + ex, + "Failed to publish: {StatusCode}", + StatusCodes.BadSecureChannelIdInvalid.ToString()); - _logger.LogError(ex, "Error publishing"); - throw; + return new ResponseHeader { ServiceResult = StatusCodes.BadSecureChannelIdInvalid }; } - catch(Exception ex) + catch (Exception ex) { MetricsHelper.RecordTotalErrors(nameof(Publish)); @@ -274,6 +326,9 @@ public override ResponseHeader Read( out DataValueCollection results, out DiagnosticInfoCollection diagnosticInfos) { + results = default; + diagnosticInfos = default; + try { var responseHeader = base.Read(requestHeader, maxAge, timestampsToReturn, nodesToRead, out results, out diagnosticInfos); @@ -282,9 +337,43 @@ public override ResponseHeader Read( return responseHeader; } + catch (ServiceResultException ex) when (ex.StatusCode == StatusCodes.BadNoSubscription) + { + MetricsHelper.RecordTotalErrors(nameof(Read)); + + _logger.LogDebug( + ex, + "Failed to read: {StatusCode}", + StatusCodes.BadNoSubscription.ToString()); + + return new ResponseHeader { ServiceResult = StatusCodes.BadNoSubscription }; + } + catch (ServiceResultException ex) when (ex.StatusCode == StatusCodes.BadSessionIdInvalid) + { + MetricsHelper.RecordTotalErrors(nameof(Read)); + + _logger.LogDebug( + ex, + "Failed to read: {StatusCode}", + StatusCodes.BadSessionIdInvalid.ToString()); + + return new ResponseHeader { ServiceResult = StatusCodes.BadSessionIdInvalid }; + } + catch (ServiceResultException ex) when (ex.StatusCode == StatusCodes.BadSecureChannelIdInvalid) + { + MetricsHelper.RecordTotalErrors(nameof(Read)); + + _logger.LogDebug( + ex, + "Failed to read: {StatusCode}", + StatusCodes.BadSecureChannelIdInvalid.ToString()); + + return new ResponseHeader { ServiceResult = StatusCodes.BadSecureChannelIdInvalid }; + } catch (Exception ex) { MetricsHelper.RecordTotalErrors(nameof(Read)); + _logger.LogError(ex, "Error reading"); throw; } diff --git a/version.json b/version.json index d8b46454..6977793d 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.12.19", + "version": "2.12.20", "versionHeightOffset": -1, "publicReleaseRefSpec": [ "^refs/heads/main$",