Skip to content

Smdn.Net.MuninNode version 1.2.0

Latest
Compare
Choose a tag to compare
@smdn smdn released this 27 Jul 10:55
· 1 commit to main since this release
a648f49

Released package

Release notes

The full release notes are available at gist.

Change log

Change log in this release:

API changes

API changes in this release:
diff --git a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net6.0.apilist.cs b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net6.0.apilist.cs
index 421f9f1..4bf85bf 100644
--- a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net6.0.apilist.cs
+++ b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net6.0.apilist.cs
@@ -1,188 +1,210 @@
-// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.1.0)
+// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.2.0)
 //   Name: Smdn.Net.MuninNode
-//   AssemblyVersion: 1.1.0.0
-//   InformationalVersion: 1.1.0+8c512e91195981258988a30fdf9c0ff4c53a6acc
+//   AssemblyVersion: 1.2.0.0
+//   InformationalVersion: 1.2.0+b0e11cd6b408018ad93f29b49e58cab7e9ef6b1b
 //   TargetFramework: .NETCoreApp,Version=v6.0
 //   Configuration: Release
 //   Referenced assemblies:
 //     Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Smdn.Fundamental.Exception, Version=3.0.0.0, Culture=neutral
 //     System.Collections, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.ComponentModel, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.IO.Pipelines, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 //     System.Linq, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Memory, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 //     System.Net.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Net.Sockets, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Security.Cryptography.Algorithms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Text.RegularExpressions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 #nullable enable annotations
 
 using System;
 using System.Collections.Generic;
 using System.Net;
 using System.Net.Sockets;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.Extensions.Logging;
 using Smdn.Net.MuninNode;
 using Smdn.Net.MuninPlugin;
 
 namespace Smdn.Net.MuninNode {
   public class LocalNode : NodeBase {
+    public LocalNode(IPluginProvider pluginProvider, string hostName, int port, ILogger? logger = null) {}
+    public LocalNode(IPluginProvider pluginProvider, string hostName, int port, IServiceProvider? serviceProvider = null) {}
     public LocalNode(IReadOnlyCollection<IPlugin> plugins, int port, IServiceProvider? serviceProvider = null) {}
     public LocalNode(IReadOnlyCollection<IPlugin> plugins, string hostName, int port, IServiceProvider? serviceProvider = null) {}
 
     public IPEndPoint LocalEndPoint { get; }
 
     protected override Socket CreateServerSocket() {}
     protected override bool IsClientAcceptable(IPEndPoint remoteEndPoint) {}
   }
 
   public abstract class NodeBase :
     IAsyncDisposable,
     IDisposable
   {
+    private protected class PluginProvider : IPluginProvider {
+      public PluginProvider(IReadOnlyCollection<IPlugin> plugins) {}
+
+      public IReadOnlyCollection<IPlugin> Plugins { get; }
+      public INodeSessionCallback? SessionCallback { get; }
+    }
+
+    protected NodeBase(IPluginProvider pluginProvider, string hostName, ILogger? logger) {}
     protected NodeBase(IReadOnlyCollection<IPlugin> plugins, string hostName, ILogger? logger) {}
 
     public virtual Encoding Encoding { get; }
     public string HostName { get; }
+    protected ILogger? Logger { get; }
     public virtual Version NodeVersion { get; }
+    [Obsolete("This member will be deprecated in future version.")]
     public IReadOnlyCollection<IPlugin> Plugins { get; }
 
     public async ValueTask AcceptAsync(bool throwIfCancellationRequested, CancellationToken cancellationToken) {}
     public async ValueTask AcceptSingleSessionAsync(CancellationToken cancellationToken = default) {}
     protected abstract Socket CreateServerSocket();
     protected virtual void Dispose(bool disposing) {}
     public void Dispose() {}
     public async ValueTask DisposeAsync() {}
     protected virtual async ValueTask DisposeAsyncCore() {}
     protected abstract bool IsClientAcceptable(IPEndPoint remoteEndPoint);
     public void Start() {}
   }
 }
 
 namespace Smdn.Net.MuninPlugin {
   public interface INodeSessionCallback {
     ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken);
     ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken);
   }
 
   public interface IPlugin {
     IPluginDataSource DataSource { get; }
     PluginGraphAttributes GraphAttributes { get; }
     string Name { get; }
     INodeSessionCallback? SessionCallback { get; }
   }
 
   public interface IPluginDataSource {
     IReadOnlyCollection<IPluginField> Fields { get; }
   }
 
   public interface IPluginField {
     PluginFieldAttributes Attributes { get; }
     string Name { get; }
 
     ValueTask<string> GetFormattedValueStringAsync(CancellationToken cancellationToken);
   }
 
+  public interface IPluginProvider {
+    IReadOnlyCollection<IPlugin> Plugins { get; }
+    INodeSessionCallback? SessionCallback { get; }
+  }
+
   public enum PluginFieldGraphStyle : int {
     Area = 1,
     AreaStack = 3,
     Default = 0,
     Line = 100,
     LineStack = 200,
     LineStackWidth1 = 201,
     LineStackWidth2 = 202,
     LineStackWidth3 = 203,
     LineWidth1 = 101,
     LineWidth2 = 102,
     LineWidth3 = 103,
     Stack = 2,
   }
 
   public class Plugin :
     INodeSessionCallback,
     IPlugin,
     IPluginDataSource
   {
     public Plugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<IPluginField> fields) {}
 
     public IReadOnlyCollection<IPluginField> Fields { get; }
     public PluginGraphAttributes GraphAttributes { get; }
     public string Name { get; }
     IPluginDataSource IPlugin.DataSource { get; }
     INodeSessionCallback? IPlugin.SessionCallback { get; }
     IReadOnlyCollection<IPluginField> IPluginDataSource.Fields { get; }
 
     protected virtual ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken) {}
     protected virtual ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken) {}
     ValueTask INodeSessionCallback.ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken) {}
     ValueTask INodeSessionCallback.ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken) {}
   }
 
   public static class PluginFactory {
     public static IPluginField CreateField(string label, Func<double?> fetchValue) {}
     public static IPluginField CreateField(string label, PluginFieldGraphStyle graphStyle, Func<double?> fetchValue) {}
     public static IPluginField CreateField(string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, Func<double?> fetchValue) {}
+    public static IPluginField CreateField(string name, string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, Func<double?> fetchValue) {}
     public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<IPluginField> fields) {}
     public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<PluginFieldBase> fields) {}
     public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, PluginFieldBase field) {}
     public static IPlugin CreatePlugin(string name, string fieldLabel, Func<double?> fetchFieldValue, PluginGraphAttributes graphAttributes) {}
     public static IPlugin CreatePlugin(string name, string fieldLabel, PluginFieldGraphStyle fieldGraphStyle, Func<double?> fetchFieldValue, PluginGraphAttributes graphAttributes) {}
   }
 
   public abstract class PluginFieldBase : IPluginField {
     protected PluginFieldBase(string label, string? name, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default, PluginFieldNormalValueRange normalRangeForWarning = default, PluginFieldNormalValueRange normalRangeForCritical = default) {}
 
     public PluginFieldGraphStyle GraphStyle { get; }
     public string Label { get; }
     public string Name { get; }
     public PluginFieldNormalValueRange NormalRangeForCritical { get; }
     public PluginFieldNormalValueRange NormalRangeForWarning { get; }
     PluginFieldAttributes IPluginField.Attributes { get; }
 
     protected abstract ValueTask<double?> FetchValueAsync(CancellationToken cancellationToken);
     async ValueTask<string> IPluginField.GetFormattedValueStringAsync(CancellationToken cancellationToken) {}
   }
 
   public sealed class PluginGraphAttributes {
+    [Obsolete("This member will be deprecated in future version.")]
     public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan updateRate, int? width = null, int? height = null) {}
+    public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate = null, int? width = null, int? height = null) {}
+    public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate, int? width, int? height, IEnumerable<string>? order) {}
 
     public string Arguments { get; }
     public string Category { get; }
     public int? Height { get; }
+    public string? Order { get; }
     public bool Scale { get; }
     public string Title { get; }
-    public TimeSpan UpdateRate { get; }
+    public TimeSpan? UpdateRate { get; }
     public string VerticalLabel { get; }
     public int? Width { get; }
   }
 
   public readonly struct PluginFieldAttributes {
     public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default) {}
     public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default, PluginFieldNormalValueRange normalRangeForWarning = default, PluginFieldNormalValueRange normalRangeForCritical = default) {}
 
     public PluginFieldGraphStyle GraphStyle { get; }
     public string Label { get; }
     public PluginFieldNormalValueRange NormalRangeForCritical { get; }
     public PluginFieldNormalValueRange NormalRangeForWarning { get; }
   }
 
   public readonly struct PluginFieldNormalValueRange {
     public static readonly PluginFieldNormalValueRange None; // = "Smdn.Net.MuninPlugin.PluginFieldNormalValueRange"
 
     public static PluginFieldNormalValueRange CreateMax(double max) {}
     public static PluginFieldNormalValueRange CreateMin(double min) {}
     public static PluginFieldNormalValueRange CreateRange(double min, double max) {}
 
     public bool HasValue { get; }
     public double? Max { get; }
     public double? Min { get; }
   }
 }
 // API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.2.0.
 // Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-netstandard2.1.apilist.cs b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-netstandard2.1.apilist.cs
index 2b85970..817c4b6 100644
--- a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-netstandard2.1.apilist.cs
+++ b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-netstandard2.1.apilist.cs
@@ -1,181 +1,203 @@
-// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.1.0)
+// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.2.0)
 //   Name: Smdn.Net.MuninNode
-//   AssemblyVersion: 1.1.0.0
-//   InformationalVersion: 1.1.0+8c512e91195981258988a30fdf9c0ff4c53a6acc
+//   AssemblyVersion: 1.2.0.0
+//   InformationalVersion: 1.2.0+b0e11cd6b408018ad93f29b49e58cab7e9ef6b1b
 //   TargetFramework: .NETStandard,Version=v2.1
 //   Configuration: Release
 //   Referenced assemblies:
 //     Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Smdn.Fundamental.Encoding.Buffer, Version=3.0.0.0, Culture=neutral
 //     Smdn.Fundamental.Exception, Version=3.0.0.0, Culture=neutral
 //     System.IO.Pipelines, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 //     netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 #nullable enable annotations
 
 using System;
 using System.Collections.Generic;
 using System.Net;
 using System.Net.Sockets;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.Extensions.Logging;
 using Smdn.Net.MuninNode;
 using Smdn.Net.MuninPlugin;
 
 namespace Smdn.Net.MuninNode {
   public class LocalNode : NodeBase {
+    public LocalNode(IPluginProvider pluginProvider, string hostName, int port, ILogger? logger = null) {}
+    public LocalNode(IPluginProvider pluginProvider, string hostName, int port, IServiceProvider? serviceProvider = null) {}
     public LocalNode(IReadOnlyCollection<IPlugin> plugins, int port, IServiceProvider? serviceProvider = null) {}
     public LocalNode(IReadOnlyCollection<IPlugin> plugins, string hostName, int port, IServiceProvider? serviceProvider = null) {}
 
     public IPEndPoint LocalEndPoint { get; }
 
     protected override Socket CreateServerSocket() {}
     protected override bool IsClientAcceptable(IPEndPoint remoteEndPoint) {}
   }
 
   public abstract class NodeBase :
     IAsyncDisposable,
     IDisposable
   {
+    private protected class PluginProvider : IPluginProvider {
+      public PluginProvider(IReadOnlyCollection<IPlugin> plugins) {}
+
+      public IReadOnlyCollection<IPlugin> Plugins { get; }
+      public INodeSessionCallback? SessionCallback { get; }
+    }
+
+    protected NodeBase(IPluginProvider pluginProvider, string hostName, ILogger? logger) {}
     protected NodeBase(IReadOnlyCollection<IPlugin> plugins, string hostName, ILogger? logger) {}
 
     public virtual Encoding Encoding { get; }
     public string HostName { get; }
+    protected ILogger? Logger { get; }
     public virtual Version NodeVersion { get; }
+    [Obsolete("This member will be deprecated in future version.")]
     public IReadOnlyCollection<IPlugin> Plugins { get; }
 
     public async ValueTask AcceptAsync(bool throwIfCancellationRequested, CancellationToken cancellationToken) {}
     public async ValueTask AcceptSingleSessionAsync(CancellationToken cancellationToken = default) {}
     protected abstract Socket CreateServerSocket();
     protected virtual void Dispose(bool disposing) {}
     public void Dispose() {}
     public async ValueTask DisposeAsync() {}
     protected virtual ValueTask DisposeAsyncCore() {}
     protected abstract bool IsClientAcceptable(IPEndPoint remoteEndPoint);
     public void Start() {}
   }
 }
 
 namespace Smdn.Net.MuninPlugin {
   public interface INodeSessionCallback {
     ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken);
     ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken);
   }
 
   public interface IPlugin {
     IPluginDataSource DataSource { get; }
     PluginGraphAttributes GraphAttributes { get; }
     string Name { get; }
     INodeSessionCallback? SessionCallback { get; }
   }
 
   public interface IPluginDataSource {
     IReadOnlyCollection<IPluginField> Fields { get; }
   }
 
   public interface IPluginField {
     PluginFieldAttributes Attributes { get; }
     string Name { get; }
 
     ValueTask<string> GetFormattedValueStringAsync(CancellationToken cancellationToken);
   }
 
+  public interface IPluginProvider {
+    IReadOnlyCollection<IPlugin> Plugins { get; }
+    INodeSessionCallback? SessionCallback { get; }
+  }
+
   public enum PluginFieldGraphStyle : int {
     Area = 1,
     AreaStack = 3,
     Default = 0,
     Line = 100,
     LineStack = 200,
     LineStackWidth1 = 201,
     LineStackWidth2 = 202,
     LineStackWidth3 = 203,
     LineWidth1 = 101,
     LineWidth2 = 102,
     LineWidth3 = 103,
     Stack = 2,
   }
 
   public class Plugin :
     INodeSessionCallback,
     IPlugin,
     IPluginDataSource
   {
     public Plugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<IPluginField> fields) {}
 
     public IReadOnlyCollection<IPluginField> Fields { get; }
     public PluginGraphAttributes GraphAttributes { get; }
     public string Name { get; }
     IPluginDataSource IPlugin.DataSource { get; }
     INodeSessionCallback? IPlugin.SessionCallback { get; }
     IReadOnlyCollection<IPluginField> IPluginDataSource.Fields { get; }
 
     protected virtual ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken) {}
     protected virtual ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken) {}
     ValueTask INodeSessionCallback.ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken) {}
     ValueTask INodeSessionCallback.ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken) {}
   }
 
   public static class PluginFactory {
     public static IPluginField CreateField(string label, Func<double?> fetchValue) {}
     public static IPluginField CreateField(string label, PluginFieldGraphStyle graphStyle, Func<double?> fetchValue) {}
     public static IPluginField CreateField(string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, Func<double?> fetchValue) {}
+    public static IPluginField CreateField(string name, string label, PluginFieldGraphStyle graphStyle, PluginFieldNormalValueRange normalRangeForWarning, PluginFieldNormalValueRange normalRangeForCritical, Func<double?> fetchValue) {}
     public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<IPluginField> fields) {}
     public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, IReadOnlyCollection<PluginFieldBase> fields) {}
     public static IPlugin CreatePlugin(string name, PluginGraphAttributes graphAttributes, PluginFieldBase field) {}
     public static IPlugin CreatePlugin(string name, string fieldLabel, Func<double?> fetchFieldValue, PluginGraphAttributes graphAttributes) {}
     public static IPlugin CreatePlugin(string name, string fieldLabel, PluginFieldGraphStyle fieldGraphStyle, Func<double?> fetchFieldValue, PluginGraphAttributes graphAttributes) {}
   }
 
   public abstract class PluginFieldBase : IPluginField {
     protected PluginFieldBase(string label, string? name, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default, PluginFieldNormalValueRange normalRangeForWarning = default, PluginFieldNormalValueRange normalRangeForCritical = default) {}
 
     public PluginFieldGraphStyle GraphStyle { get; }
     public string Label { get; }
     public string Name { get; }
     public PluginFieldNormalValueRange NormalRangeForCritical { get; }
     public PluginFieldNormalValueRange NormalRangeForWarning { get; }
     PluginFieldAttributes IPluginField.Attributes { get; }
 
     protected abstract ValueTask<double?> FetchValueAsync(CancellationToken cancellationToken);
     async ValueTask<string> IPluginField.GetFormattedValueStringAsync(CancellationToken cancellationToken) {}
   }
 
   public sealed class PluginGraphAttributes {
+    [Obsolete("This member will be deprecated in future version.")]
     public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan updateRate, int? width = null, int? height = null) {}
+    public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate = null, int? width = null, int? height = null) {}
+    public PluginGraphAttributes(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan? updateRate, int? width, int? height, IEnumerable<string>? order) {}
 
     public string Arguments { get; }
     public string Category { get; }
     public int? Height { get; }
+    public string? Order { get; }
     public bool Scale { get; }
     public string Title { get; }
-    public TimeSpan UpdateRate { get; }
+    public TimeSpan? UpdateRate { get; }
     public string VerticalLabel { get; }
     public int? Width { get; }
   }
 
   public readonly struct PluginFieldAttributes {
     public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default) {}
     public PluginFieldAttributes(string label, PluginFieldGraphStyle graphStyle = PluginFieldGraphStyle.Default, PluginFieldNormalValueRange normalRangeForWarning = default, PluginFieldNormalValueRange normalRangeForCritical = default) {}
 
     public PluginFieldGraphStyle GraphStyle { get; }
     public string Label { get; }
     public PluginFieldNormalValueRange NormalRangeForCritical { get; }
     public PluginFieldNormalValueRange NormalRangeForWarning { get; }
   }
 
   public readonly struct PluginFieldNormalValueRange {
     public static readonly PluginFieldNormalValueRange None; // = "Smdn.Net.MuninPlugin.PluginFieldNormalValueRange"
 
     public static PluginFieldNormalValueRange CreateMax(double max) {}
     public static PluginFieldNormalValueRange CreateMin(double min) {}
     public static PluginFieldNormalValueRange CreateRange(double min, double max) {}
 
     public bool HasValue { get; }
     public double? Max { get; }
     public double? Min { get; }
   }
 }
 // API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.2.0.
 // Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)

Full changes

Full changes in this release:
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj
index ce3c7a3..9295e42 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj
@@ -5,7 +5,7 @@ SPDX-License-Identifier: MIT
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>netstandard2.1;net6.0</TargetFrameworks>
-    <VersionPrefix>1.1.0</VersionPrefix>
+    <VersionPrefix>1.2.0</VersionPrefix>
     <VersionSuffix></VersionSuffix>
     <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion>
     <RootNamespace/> <!-- empty the root namespace so that the namespace is determined only by the directory name, for code style rule IDE0030 -->
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs
index 6844152..b42f9dc 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs
@@ -59,11 +59,78 @@ public class LocalNode : NodeBase {
     int port,
     IServiceProvider? serviceProvider = null
   )
-    : base(
-      plugins: plugins,
+    : this(
+      pluginProvider: new PluginProvider(plugins ?? throw new ArgumentNullException(nameof(plugins))),
+      hostName: hostName,
+      port: port,
+      serviceProvider: serviceProvider
+    )
+  {
+  }
+
+  /// <summary>
+  /// Initializes a new instance of the <see cref="LocalNode"/> class.
+  /// </summary>
+  /// <param name="pluginProvider">
+  /// The <see cref="IPluginProvider"/> that provides <see cref="IPlugin"/>s for this node.
+  /// </param>
+  /// <param name="hostName">
+  /// The hostname advertised by this node. This value is used as the display name for HTML generated by Munin.
+  /// </param>
+  /// <param name="port">
+  /// The port number on which this node accepts connections.
+  /// </param>
+  /// <param name="serviceProvider">
+  /// The <see cref="IServiceProvider"/>.
+  /// This constructor overload attempts to get a service of <see cref="ILoggerFactory"/>, to create an <see cref="ILogger"/>.
+  /// </param>
+  /// <remarks>
+  /// Most Munin-Node uses port 4949 by default, but it is recommended to use other port numbers to avoid conflicts with other nodes.
+  /// </remarks>
+  public LocalNode(
+    IPluginProvider pluginProvider,
+    string hostName,
+    int port,
+    IServiceProvider? serviceProvider = null
+  )
+    : this(
+      pluginProvider: pluginProvider ?? throw new ArgumentNullException(nameof(pluginProvider)),
       hostName: hostName,
+      port: port,
       logger: serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<LocalNode>()
     )
+  {
+  }
+
+  /// <summary>
+  /// Initializes a new instance of the <see cref="LocalNode"/> class.
+  /// </summary>
+  /// <param name="pluginProvider">
+  /// The <see cref="IPluginProvider"/> that provides <see cref="IPlugin"/>s for this node.
+  /// </param>
+  /// <param name="hostName">
+  /// The hostname advertised by this node. This value is used as the display name for HTML generated by Munin.
+  /// </param>
+  /// <param name="port">
+  /// The port number on which this node accepts connections.
+  /// </param>
+  /// <param name="logger">
+  /// The <see cref="ILogger"/> to report the situation.
+  /// </param>
+  /// <remarks>
+  /// Most Munin-Node uses port 4949 by default, but it is recommended to use other port numbers to avoid conflicts with other nodes.
+  /// </remarks>
+  public LocalNode(
+    IPluginProvider pluginProvider,
+    string hostName,
+    int port,
+    ILogger? logger = null
+  )
+    : base(
+      pluginProvider: pluginProvider,
+      hostName: hostName,
+      logger: logger
+    )
   {
     if (Socket.OSSupportsIPv6) {
       LocalEndPoint = new IPEndPoint(
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/NodeBase.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/NodeBase.cs
index 84fe125..d3a7ebd 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/NodeBase.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/NodeBase.cs
@@ -31,22 +31,50 @@ namespace Smdn.Net.MuninNode;
 public abstract class NodeBase : IDisposable, IAsyncDisposable {
   private static readonly Version defaultNodeVersion = new(1, 0, 0, 0);
 
-  public IReadOnlyCollection<IPlugin> Plugins { get; }
+  [Obsolete("This member will be deprecated in future version.")]
+  public IReadOnlyCollection<IPlugin> Plugins => pluginProvider.Plugins;
+
   public string HostName { get; }
 
   public virtual Version NodeVersion => defaultNodeVersion;
   public virtual Encoding Encoding => Encoding.Default;
 
-  private readonly ILogger? logger;
+  private readonly IPluginProvider pluginProvider;
+
+  protected ILogger? Logger { get; }
+
   private Socket? server;
 
   protected NodeBase(
     IReadOnlyCollection<IPlugin> plugins,
     string hostName,
     ILogger? logger
+  )
+    : this(
+      pluginProvider: new PluginProvider(plugins ?? throw new ArgumentNullException(nameof(plugins))),
+      hostName: hostName,
+      logger: logger
+    )
+  {
+  }
+
+  private protected class PluginProvider : IPluginProvider {
+    public IReadOnlyCollection<IPlugin> Plugins { get; }
+    public INodeSessionCallback? SessionCallback => null;
+
+    public PluginProvider(IReadOnlyCollection<IPlugin> plugins)
+    {
+      Plugins = plugins;
+    }
+  }
+
+  protected NodeBase(
+    IPluginProvider pluginProvider,
+    string hostName,
+    ILogger? logger
   )
   {
-    Plugins = plugins ?? throw new ArgumentNullException(nameof(plugins));
+    this.pluginProvider = pluginProvider ?? throw new ArgumentNullException(nameof(pluginProvider));
 
     if (hostName == null)
       throw new ArgumentNullException(nameof(hostName));
@@ -55,7 +83,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
 
     HostName = hostName;
 
-    this.logger = logger;
+    Logger = logger;
   }
 
   public void Dispose()
@@ -125,11 +153,11 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
     if (server is not null)
       throw new InvalidOperationException("already started");
 
-    logger?.LogInformation($"starting");
+    Logger?.LogInformation($"starting");
 
     server = CreateServerSocket() ?? throw new InvalidOperationException("cannot start server");
 
-    logger?.LogInformation("started (end point: {LocalEndPoint})", server.LocalEndPoint);
+    Logger?.LogInformation("started (end point: {LocalEndPoint})", server.LocalEndPoint);
   }
 
   protected abstract bool IsClientAcceptable(IPEndPoint remoteEndPoint);
@@ -184,7 +212,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
     if (server is null)
       throw new InvalidOperationException("not started or already closed");
 
-    logger?.LogInformation("accepting...");
+    Logger?.LogInformation("accepting...");
 
     var client = await server
 #if SYSTEM_NET_SOCKETS_SOCKET_ACCEPTASYNC_CANCELLATIONTOKEN
@@ -202,7 +230,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
       remoteEndPoint = client.RemoteEndPoint as IPEndPoint;
 
       if (remoteEndPoint is null) {
-        logger?.LogWarning(
+        Logger?.LogWarning(
           "cannot accept {RemoteEndPoint} ({RemoteEndPointAddressFamily})",
           client.RemoteEndPoint?.ToString() ?? "(null)",
           client.RemoteEndPoint?.AddressFamily
@@ -211,7 +239,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
       }
 
       if (!IsClientAcceptable(remoteEndPoint)) {
-        logger?.LogWarning("access refused: {RemoteEndPoint}", remoteEndPoint);
+        Logger?.LogWarning("access refused: {RemoteEndPoint}", remoteEndPoint);
         return;
       }
 
@@ -219,7 +247,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
 
       cancellationToken.ThrowIfCancellationRequested();
 
-      logger?.LogDebug("[{RemoteEndPoint}] sending banner", remoteEndPoint);
+      Logger?.LogDebug("[{RemoteEndPoint}] sending banner", remoteEndPoint);
 
       try {
         await SendResponseAsync(
@@ -236,7 +264,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
           SocketError.OperationAborted or // ECANCELED (125)
           SocketError.ConnectionReset // ECONNRESET (104)
       ) {
-        logger?.LogWarning(
+        Logger?.LogWarning(
           "[{RemoteEndPoint}] client closed session while sending banner",
           remoteEndPoint
         );
@@ -245,7 +273,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
       }
 #pragma warning disable CA1031
       catch (Exception ex) {
-        logger?.LogCritical(
+        Logger?.LogCritical(
           ex,
           "[{RemoteEndPoint}] unexpected exception occured while sending banner",
           remoteEndPoint
@@ -257,10 +285,13 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
 
       cancellationToken.ThrowIfCancellationRequested();
 
-      logger?.LogInformation("[{RemoteEndPoint}] session started; ID={SessionId}", remoteEndPoint, sessionId);
+      Logger?.LogInformation("[{RemoteEndPoint}] session started; ID={SessionId}", remoteEndPoint, sessionId);
 
       try {
-        foreach (var plugin in Plugins) {
+        if (pluginProvider.SessionCallback is not null)
+          await pluginProvider.SessionCallback.ReportSessionStartedAsync(sessionId, cancellationToken).ConfigureAwait(false);
+
+        foreach (var plugin in pluginProvider.Plugins) {
           if (plugin.SessionCallback is not null)
             await plugin.SessionCallback.ReportSessionStartedAsync(sessionId, cancellationToken).ConfigureAwait(false);
         }
@@ -273,19 +304,22 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
           ProcessCommandAsync(client, remoteEndPoint, pipe.Reader, cancellationToken)
         ).ConfigureAwait(false);
 
-        logger?.LogInformation("[{RemoteEndPoint}] session closed; ID={SessionId}", remoteEndPoint, sessionId);
+        Logger?.LogInformation("[{RemoteEndPoint}] session closed; ID={SessionId}", remoteEndPoint, sessionId);
       }
       finally {
-        foreach (var plugin in Plugins) {
+        foreach (var plugin in pluginProvider.Plugins) {
           if (plugin.SessionCallback is not null)
             await plugin.SessionCallback.ReportSessionClosedAsync(sessionId, cancellationToken).ConfigureAwait(false);
         }
+
+        if (pluginProvider.SessionCallback is not null)
+          await pluginProvider.SessionCallback.ReportSessionClosedAsync(sessionId, cancellationToken).ConfigureAwait(false);
       }
     }
     finally {
       client.Close();
 
-      logger?.LogInformation("[{RemoteEndPoint}] connection closed", remoteEndPoint);
+      Logger?.LogInformation("[{RemoteEndPoint}] connection closed", remoteEndPoint);
     }
   }
 
@@ -348,7 +382,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
           SocketError.OperationAborted or // ECANCELED (125)
           SocketError.ConnectionReset // ECONNRESET (104)
       ) {
-        logger?.LogInformation(
+        Logger?.LogInformation(
           "[{RemoteEndPoint}] expected socket exception ({NumericSocketErrorCode} {SocketErrorCode})",
           remoteEndPoint,
           (int)ex.SocketErrorCode,
@@ -357,14 +391,14 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
         break; // expected exception
       }
       catch (ObjectDisposedException) {
-        logger?.LogInformation(
+        Logger?.LogInformation(
           "[{RemoteEndPoint}] socket has been disposed",
           remoteEndPoint
         );
         break; // expected exception
       }
       catch (OperationCanceledException) {
-        logger?.LogInformation(
+        Logger?.LogInformation(
           "[{RemoteEndPoint}] operation canceled",
           remoteEndPoint
         );
@@ -372,7 +406,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
       }
 #pragma warning disable CA1031
       catch (Exception ex) {
-        logger?.LogCritical(
+        Logger?.LogCritical(
           ex,
           "[{RemoteEndPoint}] unexpected exception while receiving",
           remoteEndPoint
@@ -417,7 +451,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
         }
       }
       catch (OperationCanceledException) {
-        logger?.LogInformation(
+        Logger?.LogInformation(
           "[{RemoteEndPoint}] operation canceled",
           remoteEndPoint
         );
@@ -425,7 +459,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
       }
 #pragma warning disable CA1031
       catch (Exception ex) {
-        logger?.LogCritical(
+        Logger?.LogCritical(
           ex,
           "[{RemoteEndPoint}] unexpected exception while processing command",
           remoteEndPoint
@@ -720,7 +754,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
     return SendResponseAsync(
       client: client,
       encoding: Encoding,
-      responseLine: string.Join(" ", Plugins.Select(static plugin => plugin.Name)),
+      responseLine: string.Join(" ", pluginProvider.Plugins.Select(static plugin => plugin.Name)),
       cancellationToken: cancellationToken
     );
   }
@@ -731,7 +765,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
     CancellationToken cancellationToken
   )
   {
-    var plugin = Plugins.FirstOrDefault(
+    var plugin = pluginProvider.Plugins.FirstOrDefault(
       plugin => string.Equals(Encoding.GetString(arguments), plugin.Name, StringComparison.Ordinal)
     );
 
@@ -792,7 +826,7 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
     CancellationToken cancellationToken
   )
   {
-    var plugin = Plugins.FirstOrDefault(
+    var plugin = pluginProvider.Plugins.FirstOrDefault(
       plugin => string.Equals(Encoding.GetString(arguments), plugin.Name, StringComparison.Ordinal)
     );
 
@@ -816,13 +850,16 @@ public abstract class NodeBase : IDisposable, IAsyncDisposable {
       $"graph_args {graphAttrs.Arguments}",
       $"graph_scale {(graphAttrs.Scale ? "yes" : "no")}",
       $"graph_vlabel {graphAttrs.VerticalLabel}",
-      $"update_rate {(int)graphAttrs.UpdateRate.TotalSeconds}",
     };
 
+    if (graphAttrs.UpdateRate.HasValue)
+      responseLines.Add($"update_rate {(int)graphAttrs.UpdateRate.Value.TotalSeconds}");
     if (graphAttrs.Width.HasValue)
       responseLines.Add($"graph_width {graphAttrs.Width.Value}");
     if (graphAttrs.Height.HasValue)
       responseLines.Add($"graph_height {graphAttrs.Height.Value}");
+    if (!string.IsNullOrEmpty(graphAttrs.Order))
+      responseLines.Add($"graph_order {graphAttrs.Order}");
 
     foreach (var field in plugin.DataSource.Fields) {
       var fieldAttrs = field.Attributes;
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/IPluginProvider.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/IPluginProvider.cs
new file mode 100644
index 0000000..875400c
--- /dev/null
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/IPluginProvider.cs
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+using System.Collections.Generic;
+
+namespace Smdn.Net.MuninPlugin;
+
+/// <summary>
+/// Provides an interface that abstracts the plugin provider.
+/// </summary>
+public interface IPluginProvider {
+  /// <summary>Gets a readonly collection of <see cref="IPlugin"/> provided by this provider.</summary>
+  /// <remarks>
+  ///   <para>This property is referenced each time a during processing of a request session from the <c>munin-update</c>, such as fetching data or getting configurations.</para>
+  ///   <para>The the collection returned from this property should not be changed during the processing of each request session.</para>
+  /// </remarks>
+  /// <seealso cref="IPlugin"/>
+  /// <seealso cref="MuninNode.NodeBase"/>
+  IReadOnlyCollection<IPlugin> Plugins { get; }
+
+  /// <summary>Gets a <see cref="INodeSessionCallback"/>, which defines the callbacks when a request session from the <c>munin-update</c> starts or ends, such as fetching data or getting configurations.
+  /// <seealso cref="INodeSessionCallback"/>
+  /// <seealso cref="MuninNode.NodeBase"/>
+  INodeSessionCallback? SessionCallback { get; }
+}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginFactory.CreateField.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginFactory.CreateField.cs
index 7366e86..50ec013 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginFactory.CreateField.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginFactory.CreateField.cs
@@ -52,6 +52,23 @@ partial class PluginFactory {
       fetchValue: fetchValue
     );
 
+  public static IPluginField CreateField(
+    string name,
+    string label,
+    PluginFieldGraphStyle graphStyle,
+    PluginFieldNormalValueRange normalRangeForWarning,
+    PluginFieldNormalValueRange normalRangeForCritical,
+    Func<double?> fetchValue
+  )
+    => new ValueFromFuncPluginField(
+      label: label,
+      name: name,
+      graphStyle: graphStyle,
+      normalRangeForWarning: normalRangeForWarning,
+      normalRangeForCritical: normalRangeForCritical,
+      fetchValue: fetchValue
+    );
+
   private sealed class ValueFromFuncPluginField : PluginFieldBase {
     private readonly Func<double?> fetchValue;
 
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphAttributes.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphAttributes.cs
index f5f5010..0331728 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphAttributes.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphAttributes.cs
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: MIT
 
 using System;
+using System.Collections.Generic;
 
 namespace Smdn.Net.MuninPlugin;
 
@@ -36,7 +37,7 @@ public sealed class PluginGraphAttributes {
 
   /// <summary>Gets a value for the <c>update_rate</c>.</summary>
   /// <seealso href="http://guide.munin-monitoring.org/en/latest/reference/plugin.html#update-rate">Plugin reference - Global attributes - update_rate</seealso>
-  public TimeSpan UpdateRate { get; }
+  public TimeSpan? UpdateRate { get; }
 
   /// <summary>Gets a value for the <c>graph_width</c>.</summary>
   /// <seealso href="http://guide.munin-monitoring.org/en/latest/reference/plugin.html#graph-width">Plugin reference - Global attributes - graph_width</seealso>
@@ -46,6 +47,35 @@ public sealed class PluginGraphAttributes {
   /// <seealso href="http://guide.munin-monitoring.org/en/latest/reference/plugin.html#graph-height">Plugin reference - Global attributes - graph_height</seealso>
   public int? Height { get; }
 
+  /// <summary>Gets a value for the <c>graph_order</c>.</summary>
+  /// <seealso href="http://guide.munin-monitoring.org/en/latest/reference/plugin.html#graph-order">Plugin reference - Global attributes - graph_order</seealso>
+  public string? Order { get; }
+
+  public PluginGraphAttributes(
+    string title,
+    string category,
+    string verticalLabel,
+    bool scale,
+    string arguments,
+    TimeSpan? updateRate = null,
+    int? width = null,
+    int? height = null
+  )
+    : this(
+      title: title,
+      category: category,
+      verticalLabel: verticalLabel,
+      scale: scale,
+      arguments: arguments,
+      updateRate: updateRate,
+      width: width,
+      height: height,
+      order: null
+    )
+  {
+  }
+
+  [Obsolete("This member will be deprecated in future version.")]
   public PluginGraphAttributes(
     string title,
     string category,
@@ -55,6 +85,31 @@ public sealed class PluginGraphAttributes {
     TimeSpan updateRate,
     int? width = null,
     int? height = null
+  )
+    : this(
+      title: title,
+      category: category,
+      verticalLabel: verticalLabel,
+      scale: scale,
+      arguments: arguments,
+      updateRate: updateRate,
+      width: width,
+      height: height,
+      order: null
+    )
+  {
+  }
+
+  public PluginGraphAttributes(
+    string title,
+    string category,
+    string verticalLabel,
+    bool scale,
+    string arguments,
+    TimeSpan? updateRate,
+    int? width,
+    int? height,
+    IEnumerable<string>? order
   )
   {
     if (title == null)
@@ -85,8 +140,9 @@ public sealed class PluginGraphAttributes {
     VerticalLabel = verticalLabel;
     Width = width;
     Height = height;
+    Order = order is null ? null : string.Join(" ", order);
 
-    if (updateRate < TimeSpan.FromSeconds(1.0))
+    if (updateRate.HasValue && updateRate.Value < TimeSpan.FromSeconds(1.0))
       throw new ArgumentOutOfRangeException(nameof(updateRate), updateRate, "must be at least 1 seconds");
 
     UpdateRate = updateRate;

Notes

Full Changelog: releases/Smdn.Net.MuninNode-1.1.0...releases/Smdn.Net.MuninNode-1.2.0