Skip to content

Smdn.Net.MuninNode version 1.0.0-beta4

Pre-release
Pre-release
Compare
Choose a tag to compare
@smdn smdn released this 08 Mar 13:13
· 122 commits to main since this release
f3bcebe

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-net5.0.apilist.cs b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net5.0.apilist.cs
index 5cf8355..1b9ef3b 100644
--- a/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net5.0.apilist.cs
+++ b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net5.0.apilist.cs
@@ -1,76 +1,77 @@
-// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.0beta3 (net5.0))
+// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.0.0-beta4)
 //   Name: Smdn.Net.MuninNode
 //   AssemblyVersion: 1.0.0.0
-//   InformationalVersion: 1.0beta3 (net5.0)
+//   InformationalVersion: 1.0.0-beta4+74ac45a2d17aefc7af0d7f2108fee3bf66ef7192
 //   TargetFramework: .NETCoreApp,Version=v5.0
 //   Configuration: Release
 
 using System;
 using System.Collections.Generic;
 using System.Net;
 using System.Threading.Tasks;
 using Smdn.Net.MuninPlugin;
 
 namespace Smdn.Net.MuninNode {
   public class LocalNode : IDisposable {
     public LocalNode(IReadOnlyList<Plugin> plugins, string hostName, TimeSpan timeout, int portNumber, Version nodeVersion = null, IServiceProvider serviceProvider = null) {}
 
     public string HostName { get; }
     public IPEndPoint LocalEndPoint { get; }
     public IReadOnlyList<Plugin> Plugins { get; }
     public TimeSpan Timeout { get; }
 
     [AsyncStateMachine]
     public Task AcceptClientAsync() {}
     public void Close() {}
+    protected virtual void Dispose(bool disposing) {}
+    public void Dispose() {}
     public void Start() {}
-    void IDisposable.Dispose() {}
   }
 }
 
 namespace Smdn.Net.MuninPlugin {
   public class Plugin {
     public Plugin(string name, PluginGraphConfiguration graphConfiguration, PluginFieldConfiguration fieldConfiguration) {}
 
     public PluginFieldConfiguration FieldConfiguration { get; }
     public PluginGraphConfiguration GraphConfiguration { get; }
     public string Name { get; }
   }
 
   public abstract class PluginFieldConfiguration {
     protected PluginFieldConfiguration(string defaultGraphStyle, Range? warningValueRange = null, Range? criticalValueRange = null) {}
 
     public Range? CriticalValueRange { get; }
     public string DefaultGraphStyle { get; }
     public Range? WarningValueRange { get; }
 
     public abstract IEnumerable<PluginField> FetchFields();
   }
 
   public class PluginGraphConfiguration {
     public PluginGraphConfiguration(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan updateRate, int? width = null, int? height = null) {}
 
     public string Arguments { get; }
     public string Category { get; }
     public int? Height { get; }
     public bool Scale { get; }
     public string Title { get; }
     public TimeSpan UpdateRate { get; }
     public string VerticalLabel { get; }
     public int? Width { get; }
   }
 
   public readonly struct PluginField {
     public PluginField(string label, double @value, string graphStyle = null) {}
     public PluginField(string name, string label, double @value, string graphStyle = null) {}
 
     public string GraphStyle { get; }
     public string Label { get; }
     public string Name { get; }
     public double? Value { get; }
 
     public static PluginField CreateUnknownValueField(string label, string graphStyle = null) {}
     public static PluginField CreateUnknownValueField(string name, string label, string graphStyle = null) {}
   }
 }
 
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
new file mode 100644
index 0000000..ee9c41e
--- /dev/null
+++ b/doc/api-list/Smdn.Net.MuninNode/Smdn.Net.MuninNode-net6.0.apilist.cs
@@ -0,0 +1,77 @@
+// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.0.0-beta4)
+//   Name: Smdn.Net.MuninNode
+//   AssemblyVersion: 1.0.0.0
+//   InformationalVersion: 1.0.0-beta4+74ac45a2d17aefc7af0d7f2108fee3bf66ef7192
+//   TargetFramework: .NETCoreApp,Version=v6.0
+//   Configuration: Release
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+using Smdn.Net.MuninPlugin;
+
+namespace Smdn.Net.MuninNode {
+  public class LocalNode : IDisposable {
+    public LocalNode(IReadOnlyList<Plugin> plugins, string hostName, TimeSpan timeout, int portNumber, Version nodeVersion = null, IServiceProvider serviceProvider = null) {}
+
+    public string HostName { get; }
+    public IPEndPoint LocalEndPoint { get; }
+    public IReadOnlyList<Plugin> Plugins { get; }
+    public TimeSpan Timeout { get; }
+
+    [AsyncStateMachine]
+    public Task AcceptClientAsync() {}
+    public void Close() {}
+    protected virtual void Dispose(bool disposing) {}
+    public void Dispose() {}
+    public void Start() {}
+  }
+}
+
+namespace Smdn.Net.MuninPlugin {
+  public class Plugin {
+    public Plugin(string name, PluginGraphConfiguration graphConfiguration, PluginFieldConfiguration fieldConfiguration) {}
+
+    public PluginFieldConfiguration FieldConfiguration { get; }
+    public PluginGraphConfiguration GraphConfiguration { get; }
+    public string Name { get; }
+  }
+
+  public abstract class PluginFieldConfiguration {
+    protected PluginFieldConfiguration(string defaultGraphStyle, Range? warningValueRange = null, Range? criticalValueRange = null) {}
+
+    public Range? CriticalValueRange { get; }
+    public string DefaultGraphStyle { get; }
+    public Range? WarningValueRange { get; }
+
+    public abstract IEnumerable<PluginField> FetchFields();
+  }
+
+  public class PluginGraphConfiguration {
+    public PluginGraphConfiguration(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan updateRate, int? width = null, int? height = null) {}
+
+    public string Arguments { get; }
+    public string Category { get; }
+    public int? Height { get; }
+    public bool Scale { get; }
+    public string Title { get; }
+    public TimeSpan UpdateRate { get; }
+    public string VerticalLabel { get; }
+    public int? Width { get; }
+  }
+
+  public readonly struct PluginField {
+    public PluginField(string label, double @value, string graphStyle = null) {}
+    public PluginField(string name, string label, double @value, string graphStyle = null) {}
+
+    public string GraphStyle { get; }
+    public string Label { get; }
+    public string Name { get; }
+    public double? Value { get; }
+
+    public static PluginField CreateUnknownValueField(string label, string graphStyle = null) {}
+    public static PluginField CreateUnknownValueField(string name, string label, string graphStyle = null) {}
+  }
+}
+
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 bedbc0d..64a85d9 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,76 +1,77 @@
-// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.0beta3 (netstandard2.1))
+// Smdn.Net.MuninNode.dll (Smdn.Net.MuninNode-1.0.0-beta4)
 //   Name: Smdn.Net.MuninNode
 //   AssemblyVersion: 1.0.0.0
-//   InformationalVersion: 1.0beta3 (netstandard2.1)
+//   InformationalVersion: 1.0.0-beta4+74ac45a2d17aefc7af0d7f2108fee3bf66ef7192
 //   TargetFramework: .NETStandard,Version=v2.1
 //   Configuration: Release
 
 using System;
 using System.Collections.Generic;
 using System.Net;
 using System.Threading.Tasks;
 using Smdn.Net.MuninPlugin;
 
 namespace Smdn.Net.MuninNode {
   public class LocalNode : IDisposable {
     public LocalNode(IReadOnlyList<Plugin> plugins, string hostName, TimeSpan timeout, int portNumber, Version nodeVersion = null, IServiceProvider serviceProvider = null) {}
 
     public string HostName { get; }
     public IPEndPoint LocalEndPoint { get; }
     public IReadOnlyList<Plugin> Plugins { get; }
     public TimeSpan Timeout { get; }
 
     [AsyncStateMachine]
     public Task AcceptClientAsync() {}
     public void Close() {}
+    protected virtual void Dispose(bool disposing) {}
+    public void Dispose() {}
     public void Start() {}
-    void IDisposable.Dispose() {}
   }
 }
 
 namespace Smdn.Net.MuninPlugin {
   public class Plugin {
     public Plugin(string name, PluginGraphConfiguration graphConfiguration, PluginFieldConfiguration fieldConfiguration) {}
 
     public PluginFieldConfiguration FieldConfiguration { get; }
     public PluginGraphConfiguration GraphConfiguration { get; }
     public string Name { get; }
   }
 
   public abstract class PluginFieldConfiguration {
     protected PluginFieldConfiguration(string defaultGraphStyle, Range? warningValueRange = null, Range? criticalValueRange = null) {}
 
     public Range? CriticalValueRange { get; }
     public string DefaultGraphStyle { get; }
     public Range? WarningValueRange { get; }
 
     public abstract IEnumerable<PluginField> FetchFields();
   }
 
   public class PluginGraphConfiguration {
     public PluginGraphConfiguration(string title, string category, string verticalLabel, bool scale, string arguments, TimeSpan updateRate, int? width = null, int? height = null) {}
 
     public string Arguments { get; }
     public string Category { get; }
     public int? Height { get; }
     public bool Scale { get; }
     public string Title { get; }
     public TimeSpan UpdateRate { get; }
     public string VerticalLabel { get; }
     public int? Width { get; }
   }
 
   public readonly struct PluginField {
     public PluginField(string label, double @value, string graphStyle = null) {}
     public PluginField(string name, string label, double @value, string graphStyle = null) {}
 
     public string GraphStyle { get; }
     public string Label { get; }
     public string Name { get; }
     public double? Value { get; }
 
     public static PluginField CreateUnknownValueField(string label, string graphStyle = null) {}
     public static PluginField CreateUnknownValueField(string name, string label, string graphStyle = null) {}
   }
 }
 

Full changes

Full changes in this release:
diff --git src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj
index 0321562..8a246f6 100644
--- src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj
+++ src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.csproj
@@ -4,22 +4,31 @@ SPDX-License-Identifier: MIT
 -->
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <VersionPrefix>1.0</VersionPrefix>
-    <VersionSuffix>beta3</VersionSuffix>
+    <TargetFrameworks>netstandard2.1;net5.0;net6.0</TargetFrameworks>
+    <VersionPrefix>1.0.0</VersionPrefix>
+    <VersionSuffix>beta4</VersionSuffix>
+    <!-- <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion> -->
   </PropertyGroup>
 
-  <PropertyGroup Label="metadata">
+  <PropertyGroup Condition=" '$(Configuration)' == 'Release' " Label="Required properties to generate API list">
+    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
+  </PropertyGroup>
+
+  <PropertyGroup Label="assembly attributes">
     <Description>
 <![CDATA[Smdn.Net.MuninNode is a .NET implementation of Munin-Node and Munin-Plugin.
 ]]>
     </Description>
+    <CopyrightYear>2021</CopyrightYear>
+  </PropertyGroup>
+
+  <PropertyGroup Label="package properties">
     <PackageTags>Munin,Munin-Node,Munin-Plugin</PackageTags>
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="System.IO.Pipelines" Version="5.0.1" />
-    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
-    <PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
+    <PackageReference Include="System.IO.Pipelines" Version="6.0.2" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
     <PackageReference Include="Smdn.Fundamental.Encoding.Buffer" Version="[3.0.0,4.0.0)" Condition="$(TargetFramework.StartsWith('net4')) or $(TargetFramework.StartsWith('netstandard'))" />
     <PackageReference Include="Smdn.Fundamental.Exception" Version="[3.0.0,4.0.0)" />
   </ItemGroup>
diff --git src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs
index a9d9460..ff0e474 100644
--- src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs
+++ src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/LocalNode.cs
@@ -1,6 +1,10 @@
 // SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
 // SPDX-License-Identifier: MIT
 
+// TODO: use LoggerMessage.Define
+#pragma warning disable CA1848 // For improved performance, use the LoggerMessage delegates instead of calling 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])'
+#pragma warning disable CA2254 // The logging message template should not vary between calls to 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])'
+
 #define ENABLE_IPv6
 #undef ENABLE_IPv6
 
@@ -11,13 +15,11 @@
 using System;
 using System.Buffers;
 using System.Collections.Generic;
-using System.IO;
 using System.IO.Pipelines;
 using System.Linq;
 using System.Net;
 using System.Net.Sockets;
 using System.Text;
-using System.Threading;
 using System.Threading.Tasks;
 
 using Microsoft.Extensions.DependencyInjection;
@@ -28,10 +30,11 @@ using Smdn.Net.MuninPlugin;
 using Smdn.Text.Encodings;
 #endif
 
-namespace Smdn.Net.MuninNode {
+namespace Smdn.Net.MuninNode;
+
 public class LocalNode : IDisposable {
   private static readonly int maxClients = 1;
-    private static readonly Version defaultNodeVersion = new Version(1, 0, 0, 0);
+  private static readonly Version defaultNodeVersion = new(1, 0, 0, 0);
 
   public IReadOnlyList<Plugin> Plugins { get; }
   public string HostName { get; }
@@ -52,42 +55,55 @@ namespace Smdn.Net.MuninNode {
     IServiceProvider serviceProvider = null
   )
   {
-      this.Plugins = plugins ?? throw new ArgumentNullException(nameof(plugins));
+    Plugins = plugins ?? throw new ArgumentNullException(nameof(plugins));
 
     if (hostName == null)
       throw new ArgumentNullException(nameof(hostName));
     if (hostName.Length == 0)
       throw ExceptionUtils.CreateArgumentMustBeNonEmptyString(nameof(hostName));
 
-      this.HostName = hostName;
-      this.Timeout = timeout;
+    HostName = hostName;
+    Timeout = timeout;
 
-      this.LocalEndPoint = new IPEndPoint(
+    LocalEndPoint = new IPEndPoint(
+#pragma warning disable SA1114
 #if ENABLE_IPv6
       IPAddress.IPv6Loopback,
 #else
       IPAddress.Loopback,
 #endif
+#pragma warning restore SA1114
       portNumber
     );
 
     this.nodeVersion = nodeVersion ?? defaultNodeVersion;
 
-      this.server = new Socket(
+    server = new Socket(
+#pragma warning disable SA1114
 #if ENABLE_IPv6
       AddressFamily.InterNetworkV6,
 #else
       AddressFamily.InterNetwork,
 #endif
+#pragma warning restore SA1114
       SocketType.Stream,
       ProtocolType.Tcp
     );
 
-      this.logger = serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<LocalNode>();
+    logger = serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<LocalNode>();
+  }
+
+  public void Dispose()
+  {
+    Dispose(true);
+    GC.SuppressFinalize(this);
   }
 
-    void IDisposable.Dispose()
+  protected virtual void Dispose(bool disposing)
   {
+    if (!disposing)
+      return;
+
     server?.Disconnect(true);
     server?.Dispose();
     server = null;
@@ -103,7 +119,7 @@ namespace Smdn.Net.MuninNode {
     server.Bind(LocalEndPoint);
     server.Listen(maxClients);
 
-      logger?.LogInformation($"started");
+    logger?.LogInformation("started");
   }
 
   public async Task AcceptClientAsync()
@@ -113,9 +129,7 @@ namespace Smdn.Net.MuninNode {
     var client = await server.AcceptAsync().ConfigureAwait(false);
 
     try {
-        var remoteEndPoint = client.RemoteEndPoint as IPEndPoint;
-
-        if (remoteEndPoint == null) {
+      if (client.RemoteEndPoint is not IPEndPoint remoteEndPoint) {
         logger?.LogWarning($"cannot accept: {client.RemoteEndPoint.AddressFamily}");
         return;
       }
@@ -141,12 +155,12 @@ namespace Smdn.Net.MuninNode {
         ReadAsync(client, pipe.Reader)
       ).ConfigureAwait(false);
 
-        logger?.LogInformation($"session ending");
+      logger?.LogInformation("session ending");
     }
     finally {
       client.Close();
 
-        logger?.LogInformation($"connection closed");
+      logger?.LogInformation("connection closed");
     }
 
     async Task FillAsync(Socket socket, PipeWriter writer)
@@ -278,14 +292,18 @@ namespace Smdn.Net.MuninNode {
 
   private ValueTask ProcessCommandAsync(Socket client, ReadOnlySequence<byte> commandLine)
   {
-      if (ExpectCommand(commandLine, commandFetch.Span, out var fetchArguments))
+    if (ExpectCommand(commandLine, commandFetch.Span, out var fetchArguments)) {
       return ProcessCommandFetchAsync(client, fetchArguments);
-      else if (ExpectCommand(commandLine, commandNodes.Span, out _))
+    }
+    else if (ExpectCommand(commandLine, commandNodes.Span, out _)) {
       return ProcessCommandNodesAsync(client);
-      else if (ExpectCommand(commandLine, commandList.Span, out var listArguments))
+    }
+    else if (ExpectCommand(commandLine, commandList.Span, out var listArguments)) {
       return ProcessCommandListAsync(client, listArguments);
-      else if (ExpectCommand(commandLine, commandConfig.Span, out var configArguments))
+    }
+    else if (ExpectCommand(commandLine, commandConfig.Span, out var configArguments)) {
       return ProcessCommandConfigAsync(client, configArguments);
+    }
     else if (
       ExpectCommand(commandLine, commandQuit.Span, out _) ||
       (commandLine.Length == 1 && commandLine.FirstSpan[0] == commandQuitShort)
@@ -294,20 +312,23 @@ namespace Smdn.Net.MuninNode {
 #if NET5_0_OR_GREATER
       return ValueTask.CompletedTask;
 #else
-        return default(ValueTask);
+      return default;
 #endif
     }
-      else if (ExpectCommand(commandLine, commandCap.Span, out var capArguments))
+    else if (ExpectCommand(commandLine, commandCap.Span, out var capArguments)) {
       return ProcessCommandCapAsync(client, capArguments);
-      else if (ExpectCommand(commandLine, commandVersion.Span, out _))
+    }
+    else if (ExpectCommand(commandLine, commandVersion.Span, out _)) {
       return ProcessCommandVersionAsync(client);
-      else
+    }
+    else {
       return SendResponseAsync(
         client,
         encoding,
         "# Unknown command. Try cap, list, nodes, config, fetch, version or quit"
       );
     }
+  }
 
   private static readonly byte[] endOfLine = new[] { (byte)'\n' };
 
@@ -334,7 +355,7 @@ namespace Smdn.Net.MuninNode {
       encoding,
       new[] {
         HostName,
-          "."
+        ".",
       }
     );
   }
@@ -348,7 +369,12 @@ namespace Smdn.Net.MuninNode {
     );
   }
 
-    private ValueTask ProcessCommandCapAsync(Socket client, ReadOnlySequence<byte> arguments)
+  private ValueTask ProcessCommandCapAsync(
+    Socket client,
+#pragma warning disable IDE0060
+    ReadOnlySequence<byte> arguments
+#pragma warning restore IDE0060
+  )
   {
     // TODO: multigraph (http://guide.munin-monitoring.org/en/latest/plugin/protocol-multigraph.html)
     // TODO: dirtyconfig (http://guide.munin-monitoring.org/en/latest/plugin/protocol-dirtyconfig.html)
@@ -360,7 +386,12 @@ namespace Smdn.Net.MuninNode {
     );
   }
 
-    private ValueTask ProcessCommandListAsync(Socket client, ReadOnlySequence<byte> arguments)
+  private ValueTask ProcessCommandListAsync(
+    Socket client,
+#pragma warning disable IDE0060
+    ReadOnlySequence<byte> arguments
+#pragma warning restore IDE0060
+  )
   {
     // XXX: ignore [node] arguments
     return SendResponseAsync(
@@ -456,4 +487,3 @@ namespace Smdn.Net.MuninNode {
     );
   }
 }
-}
\ No newline at end of file
diff --git src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/Plugin.cs src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/Plugin.cs
index 725cca0..cc36069 100644
--- src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/Plugin.cs
+++ src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/Plugin.cs
@@ -3,7 +3,8 @@
 
 using System;
 
-namespace Smdn.Net.MuninPlugin {
+namespace Smdn.Net.MuninPlugin;
+
 public class Plugin {
   public string Name { get; }
   public PluginGraphConfiguration GraphConfiguration { get; }
@@ -20,9 +21,8 @@ namespace Smdn.Net.MuninPlugin {
     if (name.Length == 0)
       throw ExceptionUtils.CreateArgumentMustBeNonEmptyString(nameof(name));
 
-      this.Name = name;
-      this.GraphConfiguration = graphConfiguration ?? throw new ArgumentNullException(nameof(graphConfiguration));
-      this.FieldConfiguration = fieldConfiguration ?? throw new ArgumentNullException(nameof(fieldConfiguration));
-    }
+    Name = name;
+    GraphConfiguration = graphConfiguration ?? throw new ArgumentNullException(nameof(graphConfiguration));
+    FieldConfiguration = fieldConfiguration ?? throw new ArgumentNullException(nameof(fieldConfiguration));
   }
 }
diff --git src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginField.cs src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginField.cs
index 321cd64..c4c8f04 100644
--- src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginField.cs
+++ src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginField.cs
@@ -4,17 +4,19 @@
 using System;
 using System.Text.RegularExpressions;
 
-namespace Smdn.Net.MuninPlugin {
+namespace Smdn.Net.MuninPlugin;
+
 public readonly struct PluginField {
   public string Name { get; }
   public string Label { get; }
-    /// <summary>A value for plugin field.</summary>
-    /// <remarks/>Reports 'UNKNOWN' as a plugin field value if <see cref="Value"/> is <see langword="null"/>.<remarks>
+
+  /// <summary>Gets a value for plugin field.</summary>
+  /// <remarks>Reports 'UNKNOWN' as a plugin field value if <see cref="Value"/> is <see langword="null"/>.</remarks>
   public double? Value { get; }
   public string GraphStyle { get; }
 
   internal string FormattedValueString => Value.HasValue
-      ? Value.Value.ToString() // TODO: format specifier
+    ? Value.Value.ToString(provider: null) // TODO: format specifier
     : "U" /* UNKNOWN */;
 
   public static PluginField CreateUnknownValueField(
@@ -24,7 +26,7 @@ namespace Smdn.Net.MuninPlugin {
     => new(
       name: GetDefaultNameFromLabel(label),
       label: label,
-        value: (double?)null,
+      value: null,
       graphStyle: graphStyle
     );
 
@@ -36,7 +38,7 @@ namespace Smdn.Net.MuninPlugin {
     => new(
       name: name,
       label: label,
-        value: (double?)null,
+      value: null,
       graphStyle
     );
 
@@ -82,10 +84,10 @@ namespace Smdn.Net.MuninPlugin {
     if (!regexValidFieldLabel.IsMatch(label))
       throw new ArgumentException($"'{label}' is invalid for field name. The value of {nameof(label)} must match the following regular expression: '{regexValidFieldLabel}'", nameof(label));
 
-      this.Name = name;
-      this.Label = label ?? name;
-      this.Value = value;
-      this.GraphStyle = graphStyle;
+    Name = name;
+    Label = label ?? name;
+    Value = value;
+    GraphStyle = graphStyle;
   }
 
   // http://guide.munin-monitoring.org/en/latest/reference/plugin.html#field-name-attributes
@@ -127,4 +129,3 @@ namespace Smdn.Net.MuninPlugin {
     );
   }
 }
-}
\ No newline at end of file
diff --git src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginFieldConfiguration.cs src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginFieldConfiguration.cs
index ad5db52..a9aa127 100644
--- src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginFieldConfiguration.cs
+++ src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginFieldConfiguration.cs
@@ -3,9 +3,9 @@
 
 using System;
 using System.Collections.Generic;
-using System.Text.RegularExpressions;
 
-namespace Smdn.Net.MuninPlugin {
+namespace Smdn.Net.MuninPlugin;
+
 public abstract class PluginFieldConfiguration {
   public string DefaultGraphStyle { get; }
   public Range? WarningValueRange { get; }
@@ -17,11 +17,10 @@ namespace Smdn.Net.MuninPlugin {
     Range? criticalValueRange = null
   )
   {
-      this.DefaultGraphStyle = defaultGraphStyle;
-      this.WarningValueRange = warningValueRange;
-      this.CriticalValueRange = criticalValueRange;
+    DefaultGraphStyle = defaultGraphStyle;
+    WarningValueRange = warningValueRange;
+    CriticalValueRange = criticalValueRange;
   }
 
   public abstract IEnumerable<PluginField> FetchFields();
 }
-}
\ No newline at end of file
diff --git src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphConfiguration.cs src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphConfiguration.cs
index 1c047b7..7667325 100644
--- src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphConfiguration.cs
+++ src/Smdn.Net.MuninNode/Smdn.Net.MuninPlugin/PluginGraphConfiguration.cs
@@ -3,7 +3,8 @@
 
 using System;
 
-namespace Smdn.Net.MuninPlugin {
+namespace Smdn.Net.MuninPlugin;
+
 public class PluginGraphConfiguration {
   public string Title { get; }
   public string Category { get; }
@@ -46,18 +47,17 @@ namespace Smdn.Net.MuninPlugin {
     if (height.HasValue && height.Value <= 0)
       throw ExceptionUtils.CreateArgumentMustBeGreaterThan(0, nameof(height), height);
 
-      this.Title = title;
-      this.Category = category;
-      this.Arguments = arguments;
-      this.Scale = scale;
-      this.VerticalLabel = verticalLabel;
-      this.Width = width;
-      this.Height = height;
+    Title = title;
+    Category = category;
+    Arguments = arguments;
+    Scale = scale;
+    VerticalLabel = verticalLabel;
+    Width = width;
+    Height = height;
 
     if (updateRate < TimeSpan.FromSeconds(1.0))
       throw new ArgumentOutOfRangeException(nameof(updateRate), updateRate, "must be at least 1 seconds");
 
-      this.UpdateRate = updateRate;
-    }
+    UpdateRate = updateRate;
   }
 }

Full Changelog: v1.0.0-beta3...releases/Smdn.Net.MuninNode-1.0.0-beta4