diff --git a/Build-COSFPkgs.ps1 b/Build-COSFPkgs.ps1 index ef7eeaf4..f7de696e 100644 --- a/Build-COSFPkgs.ps1 +++ b/Build-COSFPkgs.ps1 @@ -23,11 +23,11 @@ function Build-SFPkg { try { Push-Location $scriptPath - Build-SFPkg "Microsoft.ServiceFabricApps.ClusterObserver.Linux.SelfContained.2.2.8" "$scriptPath\bin\release\ClusterObserver\linux-x64\self-contained\ClusterObserverType" - Build-SFPkg "Microsoft.ServiceFabricApps.ClusterObserver.Linux.FrameworkDependent.2.2.8" "$scriptPath\bin\release\ClusterObserver\linux-x64\framework-dependent\ClusterObserverType" + Build-SFPkg "Microsoft.ServiceFabricApps.ClusterObserver.Linux.SelfContained.2.3.0" "$scriptPath\bin\release\ClusterObserver\linux-x64\self-contained\ClusterObserverType" + Build-SFPkg "Microsoft.ServiceFabricApps.ClusterObserver.Linux.FrameworkDependent.2.3.0" "$scriptPath\bin\release\ClusterObserver\linux-x64\framework-dependent\ClusterObserverType" - Build-SFPkg "Microsoft.ServiceFabricApps.ClusterObserver.Windows.SelfContained.2.2.8" "$scriptPath\bin\release\ClusterObserver\win-x64\self-contained\ClusterObserverType" - Build-SFPkg "Microsoft.ServiceFabricApps.ClusterObserver.Windows.FrameworkDependent.2.2.8" "$scriptPath\bin\release\ClusterObserver\win-x64\framework-dependent\ClusterObserverType" + Build-SFPkg "Microsoft.ServiceFabricApps.ClusterObserver.Windows.SelfContained.2.3.0" "$scriptPath\bin\release\ClusterObserver\win-x64\self-contained\ClusterObserverType" + Build-SFPkg "Microsoft.ServiceFabricApps.ClusterObserver.Windows.FrameworkDependent.2.3.0" "$scriptPath\bin\release\ClusterObserver\win-x64\framework-dependent\ClusterObserverType" } finally { Pop-Location diff --git a/Build-SFPkgs.ps1 b/Build-SFPkgs.ps1 index c15cda10..26e40472 100644 --- a/Build-SFPkgs.ps1 +++ b/Build-SFPkgs.ps1 @@ -23,11 +23,11 @@ function Build-SFPkg { try { Push-Location $scriptPath - Build-SFPkg "Microsoft.ServiceFabricApps.FabricObserver.Linux.SelfContained.3.2.15" "$scriptPath\bin\release\FabricObserver\linux-x64\self-contained\FabricObserverType" - Build-SFPkg "Microsoft.ServiceFabricApps.FabricObserver.Linux.FrameworkDependent.3.2.15" "$scriptPath\bin\release\FabricObserver\linux-x64\framework-dependent\FabricObserverType" + Build-SFPkg "Microsoft.ServiceFabricApps.FabricObserver.Linux.SelfContained.3.3.0" "$scriptPath\bin\release\FabricObserver\linux-x64\self-contained\FabricObserverType" + Build-SFPkg "Microsoft.ServiceFabricApps.FabricObserver.Linux.FrameworkDependent.3.3.0" "$scriptPath\bin\release\FabricObserver\linux-x64\framework-dependent\FabricObserverType" - Build-SFPkg "Microsoft.ServiceFabricApps.FabricObserver.Windows.SelfContained.3.2.15" "$scriptPath\bin\release\FabricObserver\win-x64\self-contained\FabricObserverType" - Build-SFPkg "Microsoft.ServiceFabricApps.FabricObserver.Windows.FrameworkDependent.3.2.15" "$scriptPath\bin\release\FabricObserver\win-x64\framework-dependent\FabricObserverType" + Build-SFPkg "Microsoft.ServiceFabricApps.FabricObserver.Windows.SelfContained.3.3.0" "$scriptPath\bin\release\FabricObserver\win-x64\self-contained\FabricObserverType" + Build-SFPkg "Microsoft.ServiceFabricApps.FabricObserver.Windows.FrameworkDependent.3.3.0" "$scriptPath\bin\release\FabricObserver\win-x64\framework-dependent\FabricObserverType" } finally { Pop-Location diff --git a/ClusterObserver.nuspec.template b/ClusterObserver.nuspec.template index 3ab3aea6..1a9cbf14 100644 --- a/ClusterObserver.nuspec.template +++ b/ClusterObserver.nuspec.template @@ -2,11 +2,9 @@ %PACKAGE_ID% - 2.2.8 + 2.3.0 -- *Breaking Change*: Telemetry configuration settings are now required to be overridden in ApplicationManifest.xml to support versionless, parameter-only application upgrades for telemetry settings. See [Issue 292](https://github.com/microsoft/service-fabric-observer/issues/292) for details. Just move your related settings' Value strings from Settings.xml to ApplicationManifest.xml app parameter (the names of these settings are the same). -- Bug fix in app param update for log path and max archive lifetime settings. -- Updated nuget package dependencies to latest versions. +- .NET 8 implementation of ClusterObserver. This version is built for .NET 8 and SF Runtime >= 9.1 (Self-Contained FO builds only). If you have deployed SF Runtime version >= 10.1 Cumulative Update 3.0 (CU3), then you can deploy the framework-dependent release build for the target platform (Windows or Linux). If you are not running SF Runtime version >= 10.1 CU3, then you must deploy the Self-Contained release build for the target platform (Windows or Linux). **If you can't upgrade to .NET 8 yet, then do not upgrade to this version.** Microsoft MIT @@ -15,14 +13,14 @@ icon.png conuget.md en-US - This package contains the Service Fabric ClusterObserver(CO) Application - built for .NET 6.0 and SF Runtime 9.x. CO is a highly configurable and extensible Service Fabric stateless service that monitors aggregated cluster health and emits SF entity-specific telemetry. It is designed to be run in Service Fabric Windows and Linux clusters. This package contains the entire application and can be used to build .NET Standard 2.0 observer plugins. NOTE: If you want to target .NET 6 for your plugins, then you must use Microsoft.ServiceFabricApps.FabricObserver.Extensibility.3.2.9 nuget package to build them. + This package contains the Service Fabric ClusterObserver(CO) Application - built for .NET 8.0 and SF Runtime 10.1.x. CO is a highly configurable and extensible Service Fabric stateless service that monitors aggregated cluster health and emits SF entity-specific telemetry. It is designed to be run in Service Fabric Windows and Linux clusters. This package contains the entire application and can be used to build .NET Standard 2.0/.NET6 observer plugins. NOTE: If you want to target .NET 6 for your plugins, then you must use Microsoft.ServiceFabricApps.FabricObserver.Extensibility.3.2.9 or higher nuget package to build them. - + - + https://aka.ms/sf/FabricObserver @@ -31,9 +29,9 @@ - - - + + + diff --git a/ClusterObserver/ClusterObserver.cs b/ClusterObserver/ClusterObserver.cs index 1c0eb228..1332d40c 100644 --- a/ClusterObserver/ClusterObserver.cs +++ b/ClusterObserver/ClusterObserver.cs @@ -71,11 +71,11 @@ public bool EmitWarningDetails get; set; } - public ClusterObserver(StatelessServiceContext serviceContext, bool ignoreDefaultQueryTimeout = false) + public ClusterObserver(StatelessServiceContext serviceContext, bool ignoreDefaultQueryTimeout = false) : base (null, serviceContext) { - NodeStatusDictionary = new Dictionary(); - ApplicationUpgradesCompletedStatus = new Dictionary(); + NodeStatusDictionary = []; + ApplicationUpgradesCompletedStatus = []; this.ignoreDefaultQueryTimeout = ignoreDefaultQueryTimeout; @@ -400,7 +400,7 @@ private async Task ReportApplicationUpgradeStatus(Uri appName, CancellationToken { ServiceFabricUpgradeEventData appUpgradeInfo = await FabricClientRetryHelper.ExecuteFabricActionWithRetryAsync( - () => UpgradeChecker.GetApplicationUpgradeDetailsAsync(FabricClientInstance, Token, appName), + () => UpgradeChecker.GetApplicationUpgradeDetailsAsync(FabricClientInstance, appName, Token), Token); if (appUpgradeInfo?.ApplicationUpgradeProgress == null || Token.IsCancellationRequested) @@ -534,7 +534,7 @@ private async Task ProcessApplicationHealthAsync(ApplicationHealthState appHealt appHealth.HealthEvents.Where( e => e.HealthInformation.HealthState is HealthState.Error or HealthState.Warning).ToList(); - if (!appHealthEvents.Any()) + if (appHealthEvents.Count == 0) { return; } diff --git a/ClusterObserver/ClusterObserver.csproj b/ClusterObserver/ClusterObserver.csproj index 0fe0ac49..6894bd99 100644 --- a/ClusterObserver/ClusterObserver.csproj +++ b/ClusterObserver/ClusterObserver.csproj @@ -4,18 +4,21 @@ ClusterObserver ClusterObserver Exe - net6.0 + net8.0 disable True win-x64;linux-x64 True ClusterObserver - 2.2.8 - 2.2.8 + 2.3.0 + 2.3.0 + Copyright © 2024 true false ClusterObserver.Program x64 + @@ -24,7 +27,7 @@ - + diff --git a/ClusterObserver/ClusterObserverManager.cs b/ClusterObserver/ClusterObserverManager.cs index a9bcece2..43c99c6b 100644 --- a/ClusterObserver/ClusterObserverManager.cs +++ b/ClusterObserver/ClusterObserverManager.cs @@ -39,7 +39,7 @@ public sealed class ClusterObserverManager : IDisposable private bool appParamsUpdating; // Folks often use their own version numbers. This is for internal diagnostic telemetry. - private const string InternalVersionNumber = "2.2.8"; + private const string InternalVersionNumber = "2.3.0"; public bool EnableOperationalTelemetry { diff --git a/ClusterObserver/FabricClusterObserver.cs b/ClusterObserver/FabricClusterObserver.cs index e7384a54..27a726dd 100644 --- a/ClusterObserver/FabricClusterObserver.cs +++ b/ClusterObserver/FabricClusterObserver.cs @@ -11,19 +11,17 @@ using System.Reflection; using System.Linq; using FabricObserver.Utilities; +using FabricObserver.Observers.Utilities; +using FabricObserver.Observers.Utilities.Telemetry; namespace ClusterObserver { /// /// An instance of this class is created for each service instance by the Service Fabric runtime. /// - internal sealed class FabricClusterObserver : StatelessService + internal sealed class FabricClusterObserver(StatelessServiceContext context) : StatelessService(context) { - public FabricClusterObserver(StatelessServiceContext context) - : base(context) - { - - } + private readonly Logger logger = new("ClusterObserverService"); /// /// This is the main entry point for your service instance. @@ -68,11 +66,12 @@ private void LoadObserversFromPlugins(IServiceCollection services) } PluginLoader[] pluginLoaders = new PluginLoader[pluginDlls.Length]; - Type[] sharedTypes = { typeof(FabricObserverStartupAttribute), typeof(IFabricObserverStartup), typeof(IServiceCollection) }; + Type[] sharedTypes = [typeof(FabricObserverStartupAttribute), typeof(IFabricObserverStartup), typeof(IServiceCollection)]; + string dll = ""; for (int i = 0; i < pluginDlls.Length; ++i) { - string dll = pluginDlls[i]; + dll = pluginDlls[i]; PluginLoader loader = PluginLoader.CreateFromAssemblyFile(dll, sharedTypes, a => a.IsUnloadable = false); pluginLoaders[i] = loader; } @@ -109,8 +108,33 @@ private void LoadObserversFromPlugins(IServiceCollection services) } catch (Exception e) when (e is ArgumentException or BadImageFormatException or IOException) { + if (e is IOException) + { + string error = $"Plugin dll {dll} could not be loaded. {e.Message}"; + HealthReport healthReport = new() + { + AppName = new Uri($"{Context.CodePackageActivationContext.ApplicationName}"), + EmitLogEvent = true, + HealthMessage = error, + EntityType = EntityType.Application, + HealthReportTimeToLive = TimeSpan.FromMinutes(10), + State = System.Fabric.Health.HealthState.Warning, + Property = "ClusterObserverPluginLoadError", + SourceId = $"ClusterObserverService-{Context.NodeContext.NodeName}", + NodeName = Context.NodeContext.NodeName, + }; + + ObserverHealthReporter observerHealth = new(logger); + observerHealth.ReportHealthToServiceFabric(healthReport); + } + continue; } + catch (Exception e) when (e is not OutOfMemoryException) + { + logger.LogError($"Unhandled exception in ClusterObserverService Instance: {e.Message}"); + throw; + } } } } diff --git a/ClusterObserver/PackageRoot/Config/Settings.xml b/ClusterObserver/PackageRoot/Config/Settings.xml index 8688518e..321d7505 100644 --- a/ClusterObserver/PackageRoot/Config/Settings.xml +++ b/ClusterObserver/PackageRoot/Config/Settings.xml @@ -13,8 +13,7 @@ **NOTE: For Linux runtime target, just supply the name of the directory (not a path with drive letter like you for Windows).** --> + will be locally logged. This is the recommended setting. --> @@ -66,8 +65,7 @@ + will be locally logged. This is the recommended setting. --> @@ -92,10 +90,11 @@ If you want to enable versionless, parameter-only application upgrades, then add MustOverride to the Parameters you want to be able to change without redeploying CO and add them to ApplicationManifest.xml just like for ClusterObserver. -
+
+
--> diff --git a/ClusterObserver/PackageRoot/ServiceManifest.xml b/ClusterObserver/PackageRoot/ServiceManifest.xml index 43b4e5ca..c03a7af7 100644 --- a/ClusterObserver/PackageRoot/ServiceManifest.xml +++ b/ClusterObserver/PackageRoot/ServiceManifest.xml @@ -1,6 +1,6 @@  @@ -11,7 +11,7 @@ - + ClusterObserver @@ -21,11 +21,11 @@ - + - + diff --git a/ClusterObserver/Readme.md b/ClusterObserver/Readme.md index 221f0385..9e2b393e 100644 --- a/ClusterObserver/Readme.md +++ b/ClusterObserver/Readme.md @@ -1,11 +1,13 @@ -### ClusterObserver 2.2.8 -#### This version - and all subsequent versions - requires SF Runtime >= 9.0 and targets .NET 6 +### ClusterObserver 2.3.0 (.NET 8) +#### This version targets .NET 8 and SF Runtime >= 9.1. -ClusterObserver (CO) is a stateless singleton Service Fabric .NET 6 service that runs on one node in a cluster. CO observes cluster health (aggregated) +ClusterObserver (CO) is a stateless singleton Service Fabric .NET 8 service that runs on one node in a cluster. CO observes cluster health (aggregated) and sends telemetry when a cluster is in Error or Warning. CO shares a very small subset of FabricObserver's (FO) code. It is designed to be completely independent from FO sources, but lives in this repo (and SLN) because it is very useful to have both services deployed, especially for those who want cluster-level health observation and reporting in addition to the node-level user-defined resource monitoring, health event creation, and health reporting done by FO. FabricObserver is designed to generate Service Fabric health events based on user-defined resource usage Warning and Error thresholds which ClusterObserver sends to your log analytics and alerting service. +Starting with version 2.3.0, you must deploy the self-contained release package unless you are deploying to a cluster running SF Version >= 10.1 CU3 or higher, then you can deploy framework-dependent release. + By design, CO will send an Ok health state report when a cluster goes from Warning or Error state to Ok. CO only sends telemetry when something is wrong or when something that was previously wrong recovers. This limits the amount of data sent to your log analytics service. Like FabricObserver, you can implement whatever analytics backend @@ -30,7 +32,7 @@ Application Parameter Upgrade Example: ```Powershell $appName = "fabric:/ClusterObserver" -$appVersion = "2.2.8" +$appVersion = "2.3.0" $application = Get-ServiceFabricApplication -ApplicationName $appName @@ -78,8 +80,7 @@ Start-ServiceFabricApplicationUpgrade -ApplicationName $appName -ApplicationType **NOTE: For Linux runtime target, just supply the name of the directory (not a path with drive letter like you for Windows).** --> + will be locally logged. This is the recommended setting. --> @@ -122,8 +123,7 @@ Start-ServiceFabricApplicationUpgrade -ApplicationName $appName -ApplicationType + will be locally logged. This is the recommended setting. --> @@ -161,7 +161,7 @@ Start-ServiceFabricApplicationUpgrade -ApplicationName $appName -ApplicationType ``` XML - + @@ -190,7 +190,7 @@ Start-ServiceFabricApplicationUpgrade -ApplicationName $appName -ApplicationType should match the Name and Version attributes of the ServiceManifest element defined in the ServiceManifest.xml file. --> - + @@ -262,8 +262,8 @@ Here is a full example of exactly what is sent in one of these telemetry events, "TaskName": "ClusterObserver", "ClusterId": "00000000-1111-1111-0000-00f00d000d", "ClusterType": "SFRP", - "COVersion": "2.2.0.960", - "Timestamp": "2022-07-12T19:02:04.4287671Z", + "COVersion": "2.3.0", + "Timestamp": "2024-06-06T19:02:04.4287671Z", "OS": "Windows" } ``` diff --git a/ClusterObserver/Utilities/UpgradeChecker.cs b/ClusterObserver/Utilities/UpgradeChecker.cs index 32379ed9..6b6d7a06 100644 --- a/ClusterObserver/Utilities/UpgradeChecker.cs +++ b/ClusterObserver/Utilities/UpgradeChecker.cs @@ -23,7 +23,7 @@ public static class UpgradeChecker /// CancellationToken /// ApplicationName (Uri) /// An instance of ServiceFabricUpgradeEventData containing ApplicationUpgradeProgress instance. - internal static async Task GetApplicationUpgradeDetailsAsync(FabricClient fabricClient, CancellationToken token, Uri app) + internal static async Task GetApplicationUpgradeDetailsAsync(FabricClient fabricClient, Uri app, CancellationToken token) { if (token.IsCancellationRequested) { diff --git a/ClusterObserverApp/ApplicationPackageRoot/ApplicationManifest.xml b/ClusterObserverApp/ApplicationPackageRoot/ApplicationManifest.xml index 1bca1d7c..51ae3925 100644 --- a/ClusterObserverApp/ApplicationPackageRoot/ApplicationManifest.xml +++ b/ClusterObserverApp/ApplicationPackageRoot/ApplicationManifest.xml @@ -1,5 +1,5 @@  - + @@ -11,8 +11,8 @@ - + @@ -28,7 +28,7 @@ - + @@ -43,7 +43,7 @@ should match the Name and Version attributes of the ServiceManifest element defined in the ServiceManifest.xml file. --> - + diff --git a/Documentation/Deployment/service-fabric-cluster-observer.json b/Documentation/Deployment/service-fabric-cluster-observer.json index 958c15c5..04cdc689 100644 --- a/Documentation/Deployment/service-fabric-cluster-observer.json +++ b/Documentation/Deployment/service-fabric-cluster-observer.json @@ -11,7 +11,7 @@ }, "applicationTypeVersionClusterObserver": { "type": "string", - "defaultValue": "2.2.8", + "defaultValue": "2.3.0", "metadata": { "description": "Provide the app version number of ClusterObserver. This must be identical to the version specified in the corresponding sfpkg." } diff --git a/Documentation/Deployment/service-fabric-cluster-observer.v2.2.8.parameters.json b/Documentation/Deployment/service-fabric-cluster-observer.v2.3.0.parameters.json similarity index 90% rename from Documentation/Deployment/service-fabric-cluster-observer.v2.2.8.parameters.json rename to Documentation/Deployment/service-fabric-cluster-observer.v2.3.0.parameters.json index a6e4ad41..25f50903 100644 --- a/Documentation/Deployment/service-fabric-cluster-observer.v2.2.8.parameters.json +++ b/Documentation/Deployment/service-fabric-cluster-observer.v2.3.0.parameters.json @@ -1,15 +1,15 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "clusterName": { - "value": "" - }, - "applicationTypeVersionClusterObserver": { - "value": "2.2.8" - }, - "packageUrlClusterObserver": { - "value": "" - } - } +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "clusterName": { + "value": "" + }, + "applicationTypeVersionClusterObserver": { + "value": "2.3.0" + }, + "packageUrlClusterObserver": { + "value": "" + } + } } \ No newline at end of file diff --git a/Documentation/Deployment/service-fabric-observer.json b/Documentation/Deployment/service-fabric-observer.json index 5e5e6b8e..3f2e818c 100644 --- a/Documentation/Deployment/service-fabric-observer.json +++ b/Documentation/Deployment/service-fabric-observer.json @@ -11,16 +11,16 @@ }, "applicationTypeVersionFabricObserver": { "type": "string", - "defaultValue": "3.2.15", + "defaultValue": "3.3.0", "metadata": { - "description": "Provide the app version number of FabricObserver. This must be identical to the version, 3.2.15, in the referenced sfpkg specified in packageUrlFabricObserver." + "description": "Provide the app version number of FabricObserver. This must be identical to the version, 3.3.0, in the referenced sfpkg specified in packageUrlFabricObserver." } }, "packageUrlFabricObserver": { "type": "string", "defaultValue": "", "metadata": { - "description": "This has to be a public accessible URL for the sfpkg file which contains the FabricObserver app package. Example: https://github.com/microsoft/service-fabric-observer/releases/download/[xxxxxxxx]/Microsoft.ServiceFabricApps.FabricObserver.Windows.SelfContained.3.2.15.sfpkg" + "description": "This has to be a public accessible URL for the sfpkg file which contains the FabricObserver app package. Example: https://github.com/microsoft/service-fabric-observer/releases/download/[xxxxxxxx]/Microsoft.ServiceFabricApps.FabricObserver.Windows.SelfContained.3.3.0.sfpkg" } } }, diff --git a/Documentation/Deployment/service-fabric-observer.v3.2.15.parameters.json b/Documentation/Deployment/service-fabric-observer.v3.3.0.parameters.json similarity index 93% rename from Documentation/Deployment/service-fabric-observer.v3.2.15.parameters.json rename to Documentation/Deployment/service-fabric-observer.v3.3.0.parameters.json index 6fbb487d..9e7bea4c 100644 --- a/Documentation/Deployment/service-fabric-observer.v3.2.15.parameters.json +++ b/Documentation/Deployment/service-fabric-observer.v3.3.0.parameters.json @@ -6,7 +6,7 @@ "value": "" }, "applicationTypeVersionFabricObserver": { - "value": "3.2.15" + "value": "3.3.0" }, "packageUrlFabricObserver": { "value": "" diff --git a/Documentation/Design.md b/Documentation/Design.md index 6904c083..ab89e7b5 100644 --- a/Documentation/Design.md +++ b/Documentation/Design.md @@ -3,8 +3,7 @@ FO is implemented using Service Fabric’s public API surface only. It does not ship with SF. It is independent of the SF runtime engineering schedule. -FabricObserver does not need nor use much CPU, Working Set or Disk space (depending on configuration - CSV files can add up if you choose to store long running data locally.). FabricObserver does not listen on any ports. The only way to access information generated by FO is use the node-local (default impl) FabricObserverWebApi service, which is not accessible from the Internet, by default (you can choose to change that), but is readily available from the node. So, your service can easily query FO observer states by making a REST call to a localhost URI (port 5000 by default. Configurable...), which will return a JSON blob describing health state for said observer. You can read more about -this in the [FabricObserverWeb ReadMe](/FabricObserverWeb/ReadMe.md) +FabricObserver does not need nor use much CPU, Working Set or Disk space (depending on configuration - CSV files can add up if you choose to store long running data locally). FabricObserver does not listen on any ports. ***FabricObserver Components*** diff --git a/Documentation/Observers.md b/Documentation/Observers.md index 09b34e3e..8337ef7c 100644 --- a/Documentation/Observers.md +++ b/Documentation/Observers.md @@ -63,7 +63,6 @@ The top section above is the list of Application Parameters that you can modify | [NetworkObserver](#networkobserver) | Monitors outbound connection state for user-supplied endpoints (hostname/port pairs). This observer checks that the node can reach specific endpoints (over both http (e.g., REST) and direct tcp socket connections). | | [NodeObserver](#nodeobserver) | Monitors VM level resource usage across CPU, Memory, firewall rules, static and dynamic ports (aka ephemeral ports), File Handles (Linux). | | [OSObserver](#osobserver) | Records basic OS properties across OS version, OS health status, physical/virtual memory use, number of running processes, number of active TCP ports (active/ephemeral), number of enabled firewall rules (Windows), list of recent patches/hotfixes (with hyper-links to related KB articles). | -| [SFConfigurationObserver](#sfconfigurationobserver) | Records information about the currently installed Service Fabric runtime environment. This observer is currently only useful if the FO Web Api service is deployed. | # Observers - What they do and how to configure them @@ -549,9 +548,7 @@ services you would care about: Fabric.exe and FabricGateway.exe. Fabric.exe is g - + @@ -722,20 +719,12 @@ This observer records basic OS properties across OS version, OS health status, p **Input**: For Windows, you can set OSObserverEnableWindowsAutoUpdateCheck setting to true of false. This will let you know if your OS is misconfigured with respect to how Windows Update manages update downloads and installation. In general, you should not configure Windows to automatically download Windows Update binaries. Instead, use VMSS Automatic Image Upgrade service. **Output**: Log text(Error/Warning), Node Level Service Fabric Health Reports (Ok/Warning/Error), structured telemetry (ApplicationInsights, LogAnalytics), ETW, optional HTML output for FO Web API service. -The output of OSObserver is stored in its local log file when the FabricObserverWebApi service is deployed/enabled. The only Fabric health reports generated by this observer is an Error when OS Status is not "OK" (which means something is wrong at the OS level and this means trouble), a Warning if Windows Update Automatic Update service is configured to automatically download updates, and long-lived Ok Health Report that contains the information it collected about the VM it's running on. +The only Fabric health reports generated by this observer is an Error when OS Status is not "OK" (which means something is wrong at the OS level and this means trouble), a Warning if Windows Update Automatic Update service is configured to automatically download updates, and long-lived Ok Health Report that contains the information it collected about the VM it's running on. Example SFX output (Informational): ![alt text](/Documentation/Images/FONodeDetails.png "OSObserver output example.") - -## SFConfigurationObserver - -This observer doesn't monitor or report health status. -It provides information about the currently installed Service Fabric runtime environment. -The output (a local file) is used by the FabricObserver API service, rendered as HTML (e.g., http://localhost:5000/api/ObserverManager). You can learn more about API service [here](/FabricObserverWeb/ReadMe.md). - - ## Writing a Custom Observer Please see the [SampleObserver project](/SampleObserverPlugin) for a complete sample observer plugin implementation with code comments and readme. Also, see [How to implement an observer plugin using our extensibility model](/Documentation/Plugins.md) diff --git a/Documentation/OperationalTelemetry.md b/Documentation/OperationalTelemetry.md index 1ec0f62b..4f06ff5c 100644 --- a/Documentation/OperationalTelemetry.md +++ b/Documentation/OperationalTelemetry.md @@ -18,7 +18,7 @@ As with most of FabricObserver's application settings, you can also do this with Connect-ServiceFabricCluster ... $appParams = @{ "ObserverManagerEnableOperationalFOTelemetry" = "false"; } -Start-ServiceFabricApplicationUpgrade -ApplicationName fabric:/FabricObserver -ApplicationParameter $appParams -ApplicationTypeVersion 3.2.15 -UnMonitoredAuto +Start-ServiceFabricApplicationUpgrade -ApplicationName fabric:/FabricObserver -ApplicationParameter $appParams -ApplicationTypeVersion 3.3.0 -UnMonitoredAuto ``` @@ -44,11 +44,11 @@ Here is a full example of exactly what is sent in one of these telemetry events, "ClusterId": "00000000-1111-1111-0000-00f00d000d", "ClusterType": "SFRP", "NodeNameHash": "3e83569d4c6aad78083cd081215dafc81e5218556b6a46cb8dd2b183ed0095ad", - "FOVersion": "3.2.15", + "FOVersion": "3.3.0", "HasPlugins": "False", "SFRuntimeVersion":"9.0.1028.9590" "UpTime": "1.00:30:18.8058379", - "Timestamp": "2022-07-08T02:45:28.9827940Z", + "Timestamp": "2024-08-06T02:45:28.9827940Z", "OS": "Windows", "EnabledObserverCount": 5, "AppObserverTotalMonitoredApps": 5, diff --git a/Documentation/Plugins.md b/Documentation/Plugins.md index 660202df..6b3d455d 100644 --- a/Documentation/Plugins.md +++ b/Documentation/Plugins.md @@ -2,7 +2,7 @@ #### Note that starting in version 2.2.0, ClusterObserver supports the FO plugin model. So, you can build cluster-level monitoring plugins should you so desire. -1. Create a .NET 6 Library project. +1. Create a .NET 8 Library project. 2. Install the latest Microsoft.ServiceFabricApps.FabricObserver.Extensibility nupkg from https://www.nuget.org/profiles/ServiceFabricApps into your plugin project. @@ -72,5 +72,5 @@ cd C:\Users\me\source\repos\service-fabric-observer ./Build-FabricObserver ./Build-NugetPackages ``` -The output from the above commands contains FabricObserver platform-specific nupkgs and a nupkg you have to use for plugin authoring named Microsoft.ServiceFabricApps.FabricObserver.Extensibility.3.2.15.nupkg. Nuget packages will be located in +The output from the above commands contains FabricObserver platform-specific nupkgs and a nupkg you have to use for plugin authoring named Microsoft.ServiceFabricApps.FabricObserver.Extensibility.3.3.0.nupkg. Nuget packages will be located in C:\Users\me\source\repos\service-fabric-observer\bin\release\FabricObserver\Nugets. \ No newline at end of file diff --git a/Documentation/Using.md b/Documentation/Using.md index 3f114937..ae9e2641 100644 --- a/Documentation/Using.md +++ b/Documentation/Using.md @@ -623,8 +623,7 @@ ClusterObserver's ObserverManager config (Settings.xml). These are not overridab **NOTE: For Linux runtime target, just supply the name of the directory (not a path with drive letter like you for Windows).** --> + will be locally logged. This is the recommended setting. --> + will be locally logged. This is the recommended setting. --> @@ -710,7 +708,7 @@ $appParams = @{ "FabricSystemObserverEnabled" = "true"; "FabricSystemObserverMem Then execute the application upgrade with ```Powershell -Start-ServiceFabricApplicationUpgrade -ApplicationName fabric:/FabricObserver -ApplicationTypeVersion 3.2.15 -ApplicationParameter $appParams -Monitored -FailureAction rollback +Start-ServiceFabricApplicationUpgrade -ApplicationName fabric:/FabricObserver -ApplicationTypeVersion 3.3.0 -ApplicationParameter $appParams -Monitored -FailureAction rollback ``` **Important**: This action will overwrite previous app paramemter changes that were made in an earlier application upgrade, for example. If you want to preserve any earlier changes, then you will need to @@ -718,7 +716,7 @@ supply those parameter values again along with the new ones. You do this in the ```PowerShell $appName = "fabric:/FabricObserver" -$appVersion = "3.2.15" +$appVersion = "3.3.0" $application = Get-ServiceFabricApplication -ApplicationName $appName $appParamCollection = $application.ApplicationParameters $applicationParameterMap = @{} diff --git a/FabricObserver.Extensibility.nuspec.template b/FabricObserver.Extensibility.nuspec.template index 43a7e162..1d859e5d 100644 --- a/FabricObserver.Extensibility.nuspec.template +++ b/FabricObserver.Extensibility.nuspec.template @@ -2,9 +2,9 @@ %PACKAGE_ID% - 3.2.15 + 3.3.0 - +This is the .NET 8 implementation of FabricObserver's Extensibility library. Use this library to build .NET 8 FabricObserver and ClusterObserver plugins. Microsoft MIT @@ -13,12 +13,12 @@ icon.png foextlib.md en-US - This package contains the FabricObserver Extensibility library (.NET 6) for use in building FabricObserver and ClusterObserver observer plugins in Visual Studio. Each time a new version of FabricObserver is released, a new version of this library (same as FabricObserver's version) will be released to the nuget.org gallery. Unless specified as part of a release with breaking changes that would impact building a plugin (exceedingly rare), you can use earlier versions of this library to build your plugins and they will successfully work with higher versions of FabricObserver/ClusterObserver. In other words, you do not need to rebuild your plugins each time a new version of FabricObserver or ClusterObserver is released unless you are required to, which will be made clear. + This package contains the FabricObserver Extensibility library (NET8) for use in building FabricObserver and ClusterObserver observer plugins in Visual Studio. Each time a new version of FabricObserver is released, a new version of this library (same as FabricObserver's version) will be released to the nuget.org gallery. Unless specified as part of a release with breaking changes that would impact building a plugin (exceedingly rare), you can use earlier versions of this library to build your plugins and they will successfully work with higher versions of FabricObserver/ClusterObserver. In other words, you do not need to rebuild your plugins each time a new version of FabricObserver or ClusterObserver is released unless you are required to, which will be made clear. - + @@ -35,8 +35,8 @@ © Microsoft Corporation. All rights reserved. - - + + diff --git a/FabricObserver.Extensibility/Extensibility/FabricObserverStartupAttribute.cs b/FabricObserver.Extensibility/Extensibility/FabricObserverStartupAttribute.cs index 5b594ca9..f91b0947 100644 --- a/FabricObserver.Extensibility/Extensibility/FabricObserverStartupAttribute.cs +++ b/FabricObserver.Extensibility/Extensibility/FabricObserverStartupAttribute.cs @@ -8,16 +8,11 @@ namespace FabricObserver { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class FabricObserverStartupAttribute : Attribute + public sealed class FabricObserverStartupAttribute(Type startupType) : Attribute { - public FabricObserverStartupAttribute(Type startupType) - { - StartupType = startupType; - } - public Type StartupType { get; - } + } = startupType; } } diff --git a/FabricObserver.Extensibility/FabricObserver.Extensibility.csproj b/FabricObserver.Extensibility/FabricObserver.Extensibility.csproj index c43ed403..9c148778 100644 --- a/FabricObserver.Extensibility/FabricObserver.Extensibility.csproj +++ b/FabricObserver.Extensibility/FabricObserver.Extensibility.csproj @@ -1,22 +1,22 @@  - net6.0 + net8.0 x64 FabricObserver - Copyright © 2023 + Copyright © 2024 FabricObserver - 3.2.15 - 3.2.15 - CA1416 + 3.3.0 + 3.3.0 + true - + - + - + diff --git a/FabricObserver.Extensibility/ObserverBase.cs b/FabricObserver.Extensibility/ObserverBase.cs index bb37f5fc..57d3b682 100644 --- a/FabricObserver.Extensibility/ObserverBase.cs +++ b/FabricObserver.Extensibility/ObserverBase.cs @@ -33,6 +33,12 @@ public abstract class ObserverBase : IDisposable private bool disposed; private ConcurrentDictionary ServiceDumpCountDictionary; private readonly object lockObj = new(); + private static readonly bool isWindows; + + static ObserverBase() + { + isWindows = OperatingSystem.IsWindows(); + } private bool IsTelemetryProviderEnabled { @@ -53,7 +59,7 @@ public static StatelessServiceContext FabricServiceContext get; private set; } - public static bool IsWindows => OperatingSystem.IsWindows(); + public static bool IsWindows => isWindows; // Process dump settings. Only AppObserver and Windows is supported. \\ public string DumpsPath @@ -93,11 +99,6 @@ public Uri ServiceName get; set; } - public bool IsObserverWebApiAppDeployed - { - get; set; - } - public string NodeName { get; set; @@ -390,12 +391,6 @@ protected ObserverBase(FabricClient fabricClient, StatelessServiceContext servic InitializeObserverLoggingInfra(); HealthReporter = new ObserverHealthReporter(ObserverLogger); - - IsObserverWebApiAppDeployed = - bool.TryParse( - GetSettingParameterValue( - ObserverConstants.ObserverManagerConfigurationSectionName, - ObserverConstants.ObserverWebApiEnabled), out bool obsWeb) && obsWeb && IsObserverWebApiAppInstalled(); } public void InitializeObserverLoggingInfra(bool isConfigUpdate = false) @@ -592,11 +587,11 @@ public bool DumpWindowsServiceProcess(int processId, string procName, string met } - if (!ServiceDumpCountDictionary.ContainsKey(dumpKey)) + if (!ServiceDumpCountDictionary.TryGetValue(dumpKey, out (int DumpCount, DateTime LastDumpDate) value)) { _ = ServiceDumpCountDictionary.TryAdd(dumpKey, (0, DateTime.UtcNow)); } - else if (DateTime.UtcNow.Subtract(ServiceDumpCountDictionary[dumpKey].LastDumpDate) >= MaxDumpsTimeWindow) + else if (DateTime.UtcNow.Subtract(value.LastDumpDate) >= MaxDumpsTimeWindow) { ServiceDumpCountDictionary[dumpKey] = (0, DateTime.UtcNow); } @@ -1282,7 +1277,7 @@ public void ProcessResourceDataReportHealth( { AppName = appName, Code = errorWarningCode, - EmitLogEvent = EnableVerboseLogging || IsObserverWebApiAppDeployed, + EmitLogEvent = EnableVerboseLogging, HealthData = telemetryData, HealthMessage = healthMessage.ToString(), HealthReportTimeToLive = healthReportTtl, @@ -1354,7 +1349,7 @@ public void ProcessResourceDataReportHealth( AppName = appName, ServiceName = serviceName, Code = data.ActiveErrorOrWarningCode, - EmitLogEvent = EnableVerboseLogging || IsObserverWebApiAppDeployed, + EmitLogEvent = EnableVerboseLogging, HealthData = telemetryData, HealthMessage = $"{data.Property} is now within normal/expected range.", HealthReportTimeToLive = default, @@ -1612,22 +1607,5 @@ private void InitializeCsvLogger() CsvFileLogger.BaseDataLogFolderPath = !string.IsNullOrWhiteSpace(dataLogPath) ? Path.Combine(dataLogPath, ObserverName) : Path.Combine(Environment.CurrentDirectory, "fabric_observer_csvdata", ObserverName); } - - private static bool IsObserverWebApiAppInstalled() - { - try - { - var deployedObsWebApps = - FabricClientInstance.QueryManager.GetApplicationListAsync(new Uri("fabric:/FabricObserverWebApi")).GetAwaiter().GetResult(); - - return deployedObsWebApps?.Count > 0; - } - catch (Exception e) when (e is FabricException or TimeoutException) - { - - } - - return false; - } } } \ No newline at end of file diff --git a/FabricObserver.Extensibility/Utilities/CpuUsageProcess.cs b/FabricObserver.Extensibility/Utilities/CpuUsageProcess.cs index c59e41c0..0345ad68 100644 --- a/FabricObserver.Extensibility/Utilities/CpuUsageProcess.cs +++ b/FabricObserver.Extensibility/Utilities/CpuUsageProcess.cs @@ -69,7 +69,7 @@ public double GetCurrentCpuUsagePercentage(int procId, string procName = null, S } catch (Exception e) { - ProcessInfoProvider.ProcessInfoLogger.LogWarning($"GetCurrentCpuUsagePercentage(NET6 Process impl) failure (pid = {procId}): {e.Message}"); + ProcessInfoProvider.ProcessInfoLogger.LogWarning($"GetCurrentCpuUsagePercentage(NET8 Process impl) failure (pid = {procId}): {e.Message}"); throw; } } diff --git a/FabricObserver.Extensibility/Utilities/CpuUtilization/CpuUtilizationProvider.cs b/FabricObserver.Extensibility/Utilities/CpuUtilization/CpuUtilizationProvider.cs index 63b73fdc..fe724cbf 100644 --- a/FabricObserver.Extensibility/Utilities/CpuUtilization/CpuUtilizationProvider.cs +++ b/FabricObserver.Extensibility/Utilities/CpuUtilization/CpuUtilizationProvider.cs @@ -28,7 +28,7 @@ public static CpuUtilizationProvider Instance { instance = new WindowsCpuUtilizationProvider(); } - else + else if (OperatingSystem.IsLinux()) { instance = new LinuxCpuUtilizationProvider(); } diff --git a/FabricObserver.Extensibility/Utilities/CpuUtilization/LinuxCpuUtilizationProvider.cs b/FabricObserver.Extensibility/Utilities/CpuUtilization/LinuxCpuUtilizationProvider.cs index cc9c1535..d81caf2a 100644 --- a/FabricObserver.Extensibility/Utilities/CpuUtilization/LinuxCpuUtilizationProvider.cs +++ b/FabricObserver.Extensibility/Utilities/CpuUtilization/LinuxCpuUtilizationProvider.cs @@ -4,9 +4,11 @@ // ------------------------------------------------------------ using System; +using System.Runtime.Versioning; namespace FabricObserver.Observers.Utilities { + [SupportedOSPlatform("linux")] public class LinuxCpuUtilizationProvider : CpuUtilizationProvider { private float uptimeInSeconds; diff --git a/FabricObserver.Extensibility/Utilities/CpuUtilization/WindowsCpuUtilizationProvider.cs b/FabricObserver.Extensibility/Utilities/CpuUtilization/WindowsCpuUtilizationProvider.cs index 5afc9ebe..696c0017 100644 --- a/FabricObserver.Extensibility/Utilities/CpuUtilization/WindowsCpuUtilizationProvider.cs +++ b/FabricObserver.Extensibility/Utilities/CpuUtilization/WindowsCpuUtilizationProvider.cs @@ -4,9 +4,11 @@ // ------------------------------------------------------------ using System.Diagnostics; +using System.Runtime.Versioning; namespace FabricObserver.Observers.Utilities { + [SupportedOSPlatform("windows")] public class WindowsCpuUtilizationProvider : CpuUtilizationProvider { private const string ProcessorCategoryName = "Processor"; diff --git a/FabricObserver.Extensibility/Utilities/DataTableFileLogger.cs b/FabricObserver.Extensibility/Utilities/DataTableFileLogger.cs index 16b3d43b..d26ded76 100644 --- a/FabricObserver.Extensibility/Utilities/DataTableFileLogger.cs +++ b/FabricObserver.Extensibility/Utilities/DataTableFileLogger.cs @@ -14,7 +14,7 @@ namespace FabricObserver.Observers.Utilities // CSV file logger for long-running monitoring data (memory/cpu/disk/network usage data). public class DataTableFileLogger { - private static ILogger DataLogger + private static NLog.Logger DataLogger { get; set; } diff --git a/FabricObserver.Extensibility/Utilities/DiskUsage.cs b/FabricObserver.Extensibility/Utilities/DiskUsage.cs index a3a7d1ad..c3e7f4d3 100644 --- a/FabricObserver.Extensibility/Utilities/DiskUsage.cs +++ b/FabricObserver.Extensibility/Utilities/DiskUsage.cs @@ -5,8 +5,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; using System.IO; using System.Linq; @@ -14,23 +12,6 @@ namespace FabricObserver.Observers.Utilities { public static class DiskUsage { - private static PerformanceCounter _performanceCounter = null; - - private static PerformanceCounter QueueLengthCounter - { - get - { - _performanceCounter ??= new PerformanceCounter - { - CategoryName = "LogicalDisk", - CounterName = "Avg. Disk Queue Length", - ReadOnly = true - }; - - return _performanceCounter; - } - } - public static bool ShouldCheckDrive(DriveInfo driveInfo) { if (!driveInfo.IsReady) @@ -113,55 +94,20 @@ public static double GetUsedDiskSpace(string driveName, SizeUnit sizeUnit = Size public static float GetAverageDiskQueueLength(string instance) { - // We do not support this on Linux for now. - if (OperatingSystem.IsLinux()) - { - return 0F; - } - - try - { - QueueLengthCounter.InstanceName = instance; - - // Warm up counter. - _ = QueueLengthCounter.RawValue; - - return QueueLengthCounter.NextValue(); - } - catch (Exception e) - { - Logger logger = new("Utilities"); - - if (e is ArgumentNullException or PlatformNotSupportedException or Win32Exception or UnauthorizedAccessException) - { - logger.LogWarning($"{QueueLengthCounter.CategoryName} {QueueLengthCounter.CounterName} PerfCounter handled exception: " + e); - - // Don't throw. - return 0F; - } - - logger.LogError($"{QueueLengthCounter.CategoryName} {QueueLengthCounter.CounterName} PerfCounter unhandled exception: " + e); - throw; - } + return OSInfoProvider.Instance.GetAverageDiskQueueLength(instance); } private static double ConvertToSizeUnits(double amount, SizeUnit sizeUnit) { - switch(sizeUnit) + return sizeUnit switch { - case SizeUnit.Bytes: - return amount; - case SizeUnit.Kilobytes: - return amount / 1024; - case SizeUnit.Megabytes: - return amount / 1024 / 1024; - case SizeUnit.Gigabytes: - return amount / 1024 / 1024 / 1024; - case SizeUnit.Terabytes: - return amount / 1024 / 1024 / 1024 / 1024; - default: - return amount; - } + SizeUnit.Bytes => amount, + SizeUnit.Kilobytes => amount / 1024, + SizeUnit.Megabytes => amount / 1024 / 1024, + SizeUnit.Gigabytes => amount / 1024 / 1024 / 1024, + SizeUnit.Terabytes => amount / 1024 / 1024 / 1024 / 1024, + _ => amount, + }; } } diff --git a/FabricObserver.Extensibility/Utilities/InvalidPluginException.cs b/FabricObserver.Extensibility/Utilities/InvalidPluginException.cs index eb997751..e60cf13d 100644 --- a/FabricObserver.Extensibility/Utilities/InvalidPluginException.cs +++ b/FabricObserver.Extensibility/Utilities/InvalidPluginException.cs @@ -4,7 +4,6 @@ // ------------------------------------------------------------ using System; -using System.Runtime.Serialization; namespace FabricObserver.Utilities { @@ -22,9 +21,5 @@ public InvalidPluginException(string message) : base(message) public InvalidPluginException(string message, Exception innerException) : base(message, innerException) { } - - protected InvalidPluginException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } } } diff --git a/FabricObserver.Extensibility/Utilities/LinuxPermissionException.cs b/FabricObserver.Extensibility/Utilities/LinuxPermissionException.cs index f1a54b62..13fb53b0 100644 --- a/FabricObserver.Extensibility/Utilities/LinuxPermissionException.cs +++ b/FabricObserver.Extensibility/Utilities/LinuxPermissionException.cs @@ -4,7 +4,6 @@ // ------------------------------------------------------------ using System; -using System.Runtime.Serialization; namespace FabricObserver.Observers.Utilities { @@ -22,9 +21,5 @@ public LinuxPermissionException(string message) : base(message) public LinuxPermissionException(string message, Exception innerException) : base(message, innerException) { } - - protected LinuxPermissionException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } } } \ No newline at end of file diff --git a/FabricObserver.Extensibility/Utilities/NativeMethods.cs b/FabricObserver.Extensibility/Utilities/NativeMethods.cs index 2ba81778..4c819d5e 100644 --- a/FabricObserver.Extensibility/Utilities/NativeMethods.cs +++ b/FabricObserver.Extensibility/Utilities/NativeMethods.cs @@ -1289,7 +1289,7 @@ public static bool RefreshSFUserProcessDataCache(bool getChildProcesses = false) public static List<(string procName, int procId)> NtGetSFSystemServiceProcessInfo() { SYSTEM_PROCESS_INFORMATION[] procInfoList = NtGetSysProcInfo(); - List<(string procName, int procId)> result = new(); + List<(string procName, int procId)> result = []; uint fabricHostPid = 0; // If NtGetSysProcInfo returns null, it means that something went wrong (logged). FSO accounts for this and @@ -1373,8 +1373,8 @@ private static bool NtSetSFUserServiceCaches(bool getChildProcesses = false) return false; } - descendantsDictionary = new(); - currentSFServiceProcCache = new(); + descendantsDictionary = []; + currentSFServiceProcCache = []; for (int i = 0; i < procInfoList.Count; ++i) { @@ -1414,10 +1414,10 @@ private static bool NtSetSFUserServiceCaches(bool getChildProcesses = false) if (!descendantsDictionary.ContainsKey((int)parentPid)) { - List<(string childProcName, int childProcId, DateTime childProcStartTime)> descendants = new() - { + List<(string childProcName, int childProcId, DateTime childProcStartTime)> descendants = + [ child - }; + ]; _ = descendantsDictionary.TryAdd((int)parentPid, descendants); } @@ -1521,7 +1521,7 @@ public static void ClearSFUserProcessDataCache() return null; } - List<(string procName, int procId, DateTime processStartTime)> childProcs = new(); + List<(string procName, int procId, DateTime processStartTime)> childProcs = []; do { @@ -1948,7 +1948,7 @@ internal static bool GetSytemPerformanceInfo(ref PerformanceInformation pi) return null; } - List<(string, uint)> ret = new(); + List<(string, uint)> ret = []; for (int i = 0; i < currentProcIds.Length; ++i) { @@ -2141,7 +2141,7 @@ private static List NtGetFilteredProcessInfo() return null; } - List procInfoList = new(); + List procInfoList = []; for (int i = 0; i < procInfo.Length; ++i) { @@ -2308,7 +2308,7 @@ private static List NtGetFilteredProcessInfo() private static SYSTEM_PROCESS_INFORMATION[] NtGetSysProcInfo() { const int MAX_TRIES = 5; - ArrayList arrProcInfo = new(); + ArrayList arrProcInfo = []; uint size = 1024; int tried = 0; IntPtr procInfoBuffer = IntPtr.Zero; diff --git a/FabricObserver.Extensibility/Utilities/NetworkUsage.cs b/FabricObserver.Extensibility/Utilities/NetworkUsage.cs index be3c0633..fc82f2ac 100644 --- a/FabricObserver.Extensibility/Utilities/NetworkUsage.cs +++ b/FabricObserver.Extensibility/Utilities/NetworkUsage.cs @@ -5,7 +5,6 @@ using System; using System.IO; -using System.Management; using System.Xml; namespace FabricObserver.Observers.Utilities @@ -59,34 +58,11 @@ public static (int LowPort, int HighPort) TupleGetFabricApplicationPortRangeForN return (-1, -1); } + // Leave this function here to protect against breaking current consumers. This was changed to prevent cross platform visibility warnings at compile time. + // See OperatingSystemProvider.cs. public static int GetActiveFirewallRulesCount() { - int count = -1; - - // This method is not implemented for Linux yet. - if (OperatingSystem.IsLinux()) - { - return count; - } - - try - { - var scope = new ManagementScope("\\\\.\\ROOT\\StandardCimv2"); - var q = new ObjectQuery("SELECT * FROM MSFT_NetFirewallRule WHERE Enabled=1"); - using (var searcher = new ManagementObjectSearcher(scope, q)) - { - using (var results = searcher.Get()) - { - count = results.Count; - } - } - } - catch (ManagementException) - { - - } - - return count; + return OSInfoProvider.Instance.GetActiveFirewallRulesCount(); } } } diff --git a/FabricObserver.Extensibility/Utilities/ObserverConstants.cs b/FabricObserver.Extensibility/Utilities/ObserverConstants.cs index 4020187b..fcfd0103 100644 --- a/FabricObserver.Extensibility/Utilities/ObserverConstants.cs +++ b/FabricObserver.Extensibility/Utilities/ObserverConstants.cs @@ -10,9 +10,7 @@ public sealed class ObserverConstants // ObserverManager settings. public const string ObserverManagerName = "ObserverManager"; public const string ObserverManagerConfigurationSectionName = "ObserverManagerConfiguration"; - public const string ObserverWebApiEnabled = "ObserverWebApiEnabled"; public const string EnableCSVDataLogging = "EnableCSVDataLogging"; - public const string Fqdn = "FQDN"; public const string EnableETWProvider = "EnableETWProvider"; public const string ETWProviderName = "ETWProviderName"; public const string DefaultEventSourceProviderName = "FabricObserverETWProvider"; @@ -155,12 +153,6 @@ public sealed class ObserverConstants public const string OSObserverName = "OSObserver"; public const string EnableWindowsAutoUpdateCheck = "EnableWindowsAutoUpdateCheck"; - // SFConfigurationObserver. - public const string SFConfigurationObserverName = "SFConfigurationObserver"; - public const string SFConfigurationObserverVersionName = "InfrastructureConfigurationVersion"; - public const string SFConfigurationObserverConfigurationSectionName = "SFConfigurationObserverConfiguration"; - public const string SFConfigurationObserverRunIntervalParameterName = "RunInterval"; - // Telemetry Settings Parameters. public const string AiKey = "AppInsightsInstrumentationKey"; public const string AppInsightsConnectionString = "AppInsightsConnectionString"; diff --git a/FabricObserver.Extensibility/Utilities/ObserverHealthReporter.cs b/FabricObserver.Extensibility/Utilities/ObserverHealthReporter.cs index 82e3b9d0..6dea6260 100644 --- a/FabricObserver.Extensibility/Utilities/ObserverHealthReporter.cs +++ b/FabricObserver.Extensibility/Utilities/ObserverHealthReporter.cs @@ -15,20 +15,13 @@ namespace FabricObserver.Observers.Utilities /// /// Reports health data to Service Fabric Health Manager and logs locally (optional). /// - public class ObserverHealthReporter + /// + /// Initializes a new instance of the class. + /// + /// to this constructor as the instance is not used anywhere in this type. + public class ObserverHealthReporter(Logger logger) { - private readonly Logger logger; - - /// - /// Initializes a new instance of the class. - /// - /// File logger instance. Will throw ArgumentException if null. - /// Unused. Exists for compatibility reasons for older plugin impls. Update your plugins to not pass a FabricClient instance - /// to this constructor as the instance is not used anywhere in this type. - public ObserverHealthReporter(Logger logger, FabricClient fabricClient = null) - { - this.logger = logger; - } + private readonly Logger logger = logger; /// /// This function generates Service Fabric Health Reports that will show up in SFX. It supports generating health reports for the following SF entities: diff --git a/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/LinuxInfoProvider.cs b/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/LinuxInfoProvider.cs index 15704cd4..6d2e3307 100644 --- a/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/LinuxInfoProvider.cs +++ b/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/LinuxInfoProvider.cs @@ -7,11 +7,13 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; namespace FabricObserver.Observers.Utilities { + [SupportedOSPlatform("linux")] public class LinuxInfoProvider : OSInfoProvider { /// @@ -148,7 +150,7 @@ public override async Task GetOSInfoAsync(CancellationToken cancellation ** Example: ** Description:\tUbuntu 18.04.2 LTS */ - osInfo.Name = outputLines[0].Split(new[] { ':' }, 2)[1].Trim(); + osInfo.Name = outputLines[0].Split([':'], 2)[1].Trim(); } osInfo.Version = File.ReadAllText("/proc/version"); @@ -271,7 +273,7 @@ private static int GetPortCount(int processId, Predicate predicate, stri if (process?.ExitCode != 0) { // Try and work around the unsetting of caps issues when SF runs a cluster upgrade. - if (error.ToLower().Contains("permission denied")) + if (error.Contains("permission denied", StringComparison.CurrentCultureIgnoreCase)) { // Throwing LinuxPermissionException here will eventually take down FO (by design). The failure will be logged and telemetry will be emitted, then // the exception will be re-thrown by ObserverManager and the FO process will fail fast exit. Then, SF will create a new instance of FO on the offending node which @@ -371,9 +373,9 @@ public override int GetTotalAllocatedFileHandlesCount() RedirectStandardError = false }; - List output = new(); + List output = []; using Process process = Process.Start(startInfo); - + string line; while (process != null && (line = await process.StandardOutput.ReadLineAsync()) != null) { @@ -384,6 +386,21 @@ public override int GetTotalAllocatedFileHandlesCount() return (process.ExitCode, output); } + + public override int GetActiveFirewallRulesCount() + { + return -1; + } + + public override string GetOSHotFixes(bool generateKbUrl, CancellationToken token) + { + return null; + } + + public override float GetAverageDiskQueueLength(string instance) + { + return 0F; + } } // https://loune.net/2017/06/running-shell-bash-commands-in-net-core/ diff --git a/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/OperatingSystemInfoProvider.cs b/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/OperatingSystemInfoProvider.cs index a8dd32ef..243f48df 100644 --- a/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/OperatingSystemInfoProvider.cs +++ b/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/OperatingSystemInfoProvider.cs @@ -30,7 +30,7 @@ public static OSInfoProvider Instance { instance = new WindowsInfoProvider(); } - else + else if (OperatingSystem.IsLinux()) { instance = new LinuxInfoProvider(); } @@ -136,5 +136,26 @@ protected static Logger OSInfoLogger /// Optional: Process identifier. /// Count of current BOUND state TCP ports as an integer. public abstract int GetBoundStatePortCount(int processId = 0); + + /// + /// Gets Firewall rules in place (Linux is not supported. Will always return -1). + /// + /// + public abstract int GetActiveFirewallRulesCount(); + + /// + /// Gets latest OS hot fixes/patches installed on the machine. This is not yet implemented for Linux. + /// + /// Whether or not to generate hyperlinks to related kb articles. + /// CancellationToken instance. + /// Formatted string containing hot fix numbers, optionally containing hyperlink markup (HTML). + public abstract string GetOSHotFixes(bool generateKbUrl, CancellationToken token); + + /// + /// Gets the average disk queue length for the specified disk. Linux is not currently supported. + /// + /// Disk name. + /// Current disk queue length as a float value. + public abstract float GetAverageDiskQueueLength(string instance); } } diff --git a/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/TcpPortInfo.cs b/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/TcpPortInfo.cs index faacf14c..75c5ef34 100644 --- a/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/TcpPortInfo.cs +++ b/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/TcpPortInfo.cs @@ -52,6 +52,8 @@ public int OwningProcessId get; private set; } + private static readonly char[] separator = [' ']; + /// /// Creates a new instance of TcpPortInfo and set properties based on supplied netstat out row string. /// @@ -64,7 +66,7 @@ public TcpPortInfo(string netstatOutputLine) throw new ArgumentException("netstatOutputLine value must be a valid nestat output row"); } - string[] stats = netstatOutputLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + string[] stats = netstatOutputLine.Split(separator, StringSplitOptions.RemoveEmptyEntries); if (stats.Length != 5 || !int.TryParse(stats[4], out int pid)) { diff --git a/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/WindowsInfoProvider.cs b/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/WindowsInfoProvider.cs index 8f97f23b..9d5cfac9 100644 --- a/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/WindowsInfoProvider.cs +++ b/FabricObserver.Extensibility/Utilities/OperatingSystemInfo/WindowsInfoProvider.cs @@ -12,6 +12,8 @@ using System.IO; using System.Linq; using System.Management; +using System.Runtime.Versioning; +using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -19,7 +21,8 @@ namespace FabricObserver.Observers.Utilities { - public class WindowsInfoProvider : OSInfoProvider + [SupportedOSPlatform("windows")] + public partial class WindowsInfoProvider : OSInfoProvider { private const string TcpProtocol = "tcp"; private const int portDataMaxCacheTimeSeconds = 45; @@ -35,6 +38,22 @@ public class WindowsInfoProvider : OSInfoProvider private (int LowPort, int HighPort, int NumberOfPorts) windowsDynamicPortRange = (-1, -1, 0); private DateTime LastDynamicRangeCacheUpdate = DateTime.MinValue; private DateTime LastCacheUpdate = DateTime.MinValue; + private static PerformanceCounter _performanceCounter = null; + + private static PerformanceCounter QueueLengthCounter + { + get + { + _performanceCounter ??= new PerformanceCounter + { + CategoryName = "LogicalDisk", + CounterName = "Avg. Disk Queue Length", + ReadOnly = true + }; + + return _performanceCounter; + } + } /// /// Windows OS info provider type. @@ -56,7 +75,7 @@ public WindowsInfoProvider() } else { - win32TcpConnInfo = new List<(ushort LocalPort, int OwningProcessId, MIB_TCP_STATE State)>(); + win32TcpConnInfo = []; } } @@ -259,11 +278,7 @@ public override (int LowPort, int HighPort, int NumberOfPorts) TupleGetDynamicPo if (process.WaitForExit(60000)) { - Match match = Regex.Match( - output, - @"Start Port\s+:\s+(?\d+).+?Number of Ports\s+:\s+(?\d+)", - RegexOptions.Singleline | RegexOptions.IgnoreCase); - + Match match = PortRegex().Match(output); string startPort = match.Groups["startPort"].Value; string portCount = match.Groups["numberOfPorts"].Value; int exitStatus = process.ExitCode; @@ -711,5 +726,119 @@ private static (int LocalPort, int OwningProcessId, string State) TupleGetLocalP return (-1, -1, null); } } + + public override int GetActiveFirewallRulesCount() + { + int count = 0; + + try + { + var scope = new ManagementScope("\\\\.\\ROOT\\StandardCimv2"); + var q = new ObjectQuery("SELECT * FROM MSFT_NetFirewallRule WHERE Enabled=1"); + using (var searcher = new ManagementObjectSearcher(scope, q)) + { + using (var results = searcher.Get()) + { + count = results.Count; + } + } + } + catch (ManagementException) + { + + } + + return count; + } + + public override string GetOSHotFixes(bool generateKbUrl, CancellationToken token) + { + ManagementObject[] resultsOrdered; + string ret = string.Empty; + + token.ThrowIfCancellationRequested(); + + try + { + using var searcher = new ManagementObjectSearcher("SELECT HotFixID,InstalledOn FROM Win32_QuickFixEngineering"); + var results = searcher.Get(); + + if (results.Count < 1) + { + return string.Empty; + } + + resultsOrdered = [.. results.Cast() + .Where(obj => obj["InstalledOn"] != null && obj["InstalledOn"].ToString() != string.Empty) + .OrderByDescending(obj => DateTime.Parse(obj["InstalledOn"].ToString() ?? string.Empty))]; + + var sb = new StringBuilder(); + var baseUrl = "https://support.microsoft.com/help/"; + + for (int i = 0; i < resultsOrdered.Length; ++i) + { + token.ThrowIfCancellationRequested(); + + ManagementObject obj = resultsOrdered[i]; + + try + { + _ = generateKbUrl ? sb.AppendLine( + $"{obj["HotFixID"]} " + + $"{obj["InstalledOn"]}") : sb.AppendLine($"{obj["HotFixID"]}"); + } + catch (ArgumentException) + { + + } + finally + { + obj?.Dispose(); + obj = null; + } + } + + resultsOrdered = null; + ret = sb.ToString().Trim(); + _ = sb.Clear(); + sb = null; + + } + catch (Exception e) when (e is not OutOfMemoryException) + { + OSInfoLogger.LogWarning($"Unhandled Exception processing Windows hotpatch information: {e.Message}"); + } + + return ret; + } + + public override float GetAverageDiskQueueLength(string instance) + { + try + { + QueueLengthCounter.InstanceName = instance; + + // Warm up counter. + _ = QueueLengthCounter.RawValue; + + return QueueLengthCounter.NextValue(); + } + catch (Exception e) + { + if (e is ArgumentNullException or PlatformNotSupportedException or Win32Exception or UnauthorizedAccessException) + { + OSInfoLogger.LogWarning($"{QueueLengthCounter.CategoryName} {QueueLengthCounter.CounterName} PerfCounter handled exception: " + e.Message); + + // Don't throw. + return 0F; + } + + OSInfoLogger.LogError($"{QueueLengthCounter.CategoryName} {QueueLengthCounter.CounterName} PerfCounter unhandled exception: " + e.Message); + throw; + } + } + + [GeneratedRegex(@"Start Port\s+:\s+(?\d+).+?Number of Ports\s+:\s+(?\d+)", RegexOptions.IgnoreCase | RegexOptions.Singleline)] + private static partial Regex PortRegex(); } } \ No newline at end of file diff --git a/FabricObserver.Extensibility/Utilities/ProcessInfo/LinuxProcessInfoProvider.cs b/FabricObserver.Extensibility/Utilities/ProcessInfo/LinuxProcessInfoProvider.cs index d7edeac2..226dca36 100644 --- a/FabricObserver.Extensibility/Utilities/ProcessInfo/LinuxProcessInfoProvider.cs +++ b/FabricObserver.Extensibility/Utilities/ProcessInfo/LinuxProcessInfoProvider.cs @@ -7,10 +7,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.Versioning; using System.Threading; namespace FabricObserver.Observers.Utilities { + [SupportedOSPlatform("linux")] public class LinuxProcessInfoProvider : ProcessInfoProvider { private const int MaxDescendants = 50; @@ -82,7 +84,7 @@ public override float GetProcessAllocatedHandles(int processId, string configPat ProcessInfoLogger.LogWarning($"elevated_proc_fd exited with: {process.ExitCode}"); // Try and work around the unsetting of caps issues when SF runs a cluster upgrade. - if (error.ToLower().Contains("permission denied")) + if (error.Contains("permission denied", StringComparison.CurrentCultureIgnoreCase)) { // Throwing LinuxPermissionException here will eventually take down FO (by design). The failure will be logged and telemetry will be emitted, then // the exception will be re-thrown by ObserverManager and the FO process will fail fast exit. Then, SF will create a new instance of FO on the offending node which diff --git a/FabricObserver.Extensibility/Utilities/ProcessInfo/ProcessInfoProvider.cs b/FabricObserver.Extensibility/Utilities/ProcessInfo/ProcessInfoProvider.cs index c2cdb1eb..2aabc35d 100644 --- a/FabricObserver.Extensibility/Utilities/ProcessInfo/ProcessInfoProvider.cs +++ b/FabricObserver.Extensibility/Utilities/ProcessInfo/ProcessInfoProvider.cs @@ -31,7 +31,7 @@ public static IProcessInfoProvider Instance { instance = new WindowsProcessInfoProvider(); } - else + else if (OperatingSystem.IsLinux()) { instance = new LinuxProcessInfoProvider(); } diff --git a/FabricObserver.Extensibility/Utilities/ProcessInfo/WindowsProcessInfoProvider.cs b/FabricObserver.Extensibility/Utilities/ProcessInfo/WindowsProcessInfoProvider.cs index 3e160855..d2a3df52 100644 --- a/FabricObserver.Extensibility/Utilities/ProcessInfo/WindowsProcessInfoProvider.cs +++ b/FabricObserver.Extensibility/Utilities/ProcessInfo/WindowsProcessInfoProvider.cs @@ -12,11 +12,13 @@ using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; namespace FabricObserver.Observers.Utilities { + [SupportedOSPlatform("windows")] public class WindowsProcessInfoProvider : ProcessInfoProvider { private const int MaxDescendants = 50; @@ -55,10 +57,7 @@ private static PerformanceCounterCategory PerfCounterProcessCategory { lock (lockObj) { - if (performanceCounterCategory == null) - { - performanceCounterCategory = new(ProcessCategoryName); - } + performanceCounterCategory ??= new(ProcessCategoryName); } } return performanceCounterCategory; diff --git a/FabricObserver.Extensibility/Utilities/RetryableException.cs b/FabricObserver.Extensibility/Utilities/RetryableException.cs index 532d1cc3..9170e710 100644 --- a/FabricObserver.Extensibility/Utilities/RetryableException.cs +++ b/FabricObserver.Extensibility/Utilities/RetryableException.cs @@ -4,7 +4,6 @@ // ------------------------------------------------------------ using System; -using System.Runtime.Serialization; namespace FabricObserver.Observers.Utilities { @@ -22,9 +21,5 @@ public RetryableException(string message) : base(message) public RetryableException(string message, Exception innerException) : base(message, innerException) { } - - protected RetryableException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } } } \ No newline at end of file diff --git a/FabricObserver.Extensibility/Utilities/ServiceFabric/FabricClientUtilities.cs b/FabricObserver.Extensibility/Utilities/ServiceFabric/FabricClientUtilities.cs index a615f999..c0196ab6 100644 --- a/FabricObserver.Extensibility/Utilities/ServiceFabric/FabricClientUtilities.cs +++ b/FabricObserver.Extensibility/Utilities/ServiceFabric/FabricClientUtilities.cs @@ -176,7 +176,7 @@ public async Task> GetAllDeployedAppsAsync(Cancellatio /// A List of ReplicaOrInstanceMonitoringInfo objects representing all replicas in any status (consumer should filter Status per need) on the local (or specified) node. public async Task> GetAllDeployedReplicasOrInstancesAsync(bool includeChildProcesses, CancellationToken token, string nodeName = null) { - List repList = new(); + List repList = []; List appList = await GetAllDeployedAppsAsync(token); if (isWindows && !NativeMethods.RefreshSFUserProcessDataCache(getChildProcesses: includeChildProcesses)) @@ -204,7 +204,7 @@ public async Task> GetAllDeployedReplicasO var deployedReplicaList = await FabricClientSingleton.QueryManager.GetDeployedReplicaListAsync(nodeName ?? this.nodeName, app.ApplicationName, null, null, TimeSpan.FromSeconds(60), token); - if (deployedReplicaList == null || !deployedReplicaList.Any()) + if (deployedReplicaList == null || deployedReplicaList.Count == 0) { // Application has no deployed replicas. continue; @@ -637,7 +637,7 @@ any processes (children) that the service process (parent) created/spawned. */ /// A List of tuple (string ServiceName, string ProcName, int Pid) representing all services supplied in the ReplicaOrInstanceMonitoringInfo instance, including child processes of each service, if any. public List<(string ServiceName, string ProcName, int Pid, DateTime ProcessStartTime)> GetServiceProcessInfo(List repOrInsts) { - List<(string ServiceName, string ProcName, int Pid, DateTime ProcessStartTime)> pids = new(); + List<(string ServiceName, string ProcName, int Pid, DateTime ProcessStartTime)> pids = []; foreach (var repOrInst in repOrInsts) { @@ -761,7 +761,7 @@ public static string ParseAppParameterValue(string appParamValue, ApplicationPar } // Application parameter value specified as a Service Fabric Application Manifest variable. - if (appParamValue.StartsWith("[")) + if (appParamValue.StartsWith('[')) { appParamValue = appParamValue.Replace("[", string.Empty).Replace("]", string.Empty); @@ -782,7 +782,7 @@ public static string ParseAppParameterValue(string appParamValue, ApplicationPar public static void AddParametersIfNotExists(ApplicationParameterList toParameters, ApplicationParameterList fromParameters) { // If toParameters is passed in as null, then make it a new instance. - toParameters ??= new ApplicationParameterList(); + toParameters ??= []; if (fromParameters != null) { @@ -1209,7 +1209,7 @@ private async Task RemoveServiceHealthReportsAsync(ServiceHealthState service, b && (e.HealthInformation.SourceId.StartsWith(ObserverConstants.AppObserverName) || e.HealthInformation.SourceId.StartsWith(ObserverConstants.ContainerObserverName))).ToList(); - if (!serviceHealthEvents.Any()) + if (serviceHealthEvents.Count == 0) { return; } @@ -1262,7 +1262,7 @@ private async Task RemoveApplicationHealthReportsAsync(ApplicationHealthState ap || e.HealthInformation.SourceId.StartsWith(ObserverConstants.FabricSystemObserverName) || e.HealthInformation.SourceId.StartsWith(ObserverConstants.NetworkObserverName))).ToList(); - if (!appHealthEvents.Any()) + if (appHealthEvents.Count == 0) { return; } @@ -1319,7 +1319,7 @@ private async Task RemoveNodeHealthReportsAsync(IEnumerable nod || e.HealthInformation.SourceId.StartsWith(ObserverConstants.NodeObserverName) || e.HealthInformation.SourceId.StartsWith(ObserverConstants.OSObserverName)).ToList(); - if (!nodeHealthEvents.Any()) + if (nodeHealthEvents.Count == 0) { return; } diff --git a/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/LinuxServiceFabricConfiguration.cs b/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/LinuxServiceFabricConfiguration.cs index 61381229..a1806b4d 100644 --- a/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/LinuxServiceFabricConfiguration.cs +++ b/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/LinuxServiceFabricConfiguration.cs @@ -5,9 +5,11 @@ using System.IO; using System.Linq; +using System.Runtime.Versioning; namespace FabricObserver.Observers.Utilities { + [SupportedOSPlatform("linux")] public class LinuxServiceFabricConfiguration : ServiceFabricConfiguration { public override string FabricVersion diff --git a/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/ServiceFabricConfiguration.cs b/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/ServiceFabricConfiguration.cs index 45e54d96..644a7642 100644 --- a/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/ServiceFabricConfiguration.cs +++ b/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/ServiceFabricConfiguration.cs @@ -32,7 +32,7 @@ public static ServiceFabricConfiguration Instance { instance = new WindowsServiceFabricConfiguration(); } - else + else if (OperatingSystem.IsLinux()) { instance = new LinuxServiceFabricConfiguration(); } diff --git a/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/WindowsServiceFabricConfiguration.cs b/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/WindowsServiceFabricConfiguration.cs index dd1c0f6f..77978c4e 100644 --- a/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/WindowsServiceFabricConfiguration.cs +++ b/FabricObserver.Extensibility/Utilities/ServiceFabricConfiguration/WindowsServiceFabricConfiguration.cs @@ -6,10 +6,12 @@ using Microsoft.Win32; using System; using System.IO; +using System.Runtime.Versioning; using System.Security; namespace FabricObserver.Observers.Utilities { + [SupportedOSPlatform("windows")] public class WindowsServiceFabricConfiguration : ServiceFabricConfiguration { private const string ServiceFabricWindowsRegistryPath = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Service Fabric"; diff --git a/FabricObserver.Extensibility/Utilities/Telemetry/AppInsightsTelemetry.cs b/FabricObserver.Extensibility/Utilities/Telemetry/AppInsightsTelemetry.cs index 73cbbc2e..d271e6bc 100644 --- a/FabricObserver.Extensibility/Utilities/Telemetry/AppInsightsTelemetry.cs +++ b/FabricObserver.Extensibility/Utilities/Telemetry/AppInsightsTelemetry.cs @@ -187,7 +187,7 @@ public Task ReportHealthAsync(TelemetryDataBase telemetryData, CancellationToken if (telemetryData is ServiceTelemetryData serviceTelemData) { - properties = new Dictionary + properties = new() { { "ClusterId", serviceTelemData.ClusterId }, { "EntityType", serviceTelemData.EntityType.ToString() }, @@ -216,7 +216,7 @@ public Task ReportHealthAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is NodeTelemetryData nodeTelemData) { - properties = new Dictionary + properties = new() { { "ClusterId", nodeTelemData.ClusterId }, { "EntityType", nodeTelemData.EntityType.ToString() }, @@ -231,7 +231,7 @@ public Task ReportHealthAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is DiskTelemetryData diskTelemData) { - properties = new Dictionary + properties = new() { { "ClusterId", diskTelemData.ClusterId }, { "EntityType", diskTelemData.EntityType.ToString() }, @@ -247,7 +247,7 @@ public Task ReportHealthAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is ClusterTelemetryData clusterTelemData) { - properties = new Dictionary + properties = new() { { "ClusterId", clusterTelemData.ClusterId }, { "EntityType", EntityType.Cluster.ToString() }, @@ -261,7 +261,7 @@ public Task ReportHealthAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is SystemServiceTelemetryData systemServiceTelemData) { - properties = new Dictionary + properties = new() { { "ApplicationName", systemServiceTelemData.ApplicationName }, { "ClusterId", systemServiceTelemData.ClusterId }, @@ -279,7 +279,7 @@ public Task ReportHealthAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is NetworkTelemetryData networkTelemData) { - properties = new Dictionary + properties = new() { { "ApplicationName", networkTelemData.ApplicationName }, { "ClusterId", networkTelemData.ClusterId }, @@ -293,7 +293,7 @@ public Task ReportHealthAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is ContainerTelemetryData containerTelemData) { - properties = new Dictionary + properties = new() { { "ApplicationName", containerTelemData.ApplicationName }, { "ServiceName", containerTelemData.ServiceName }, @@ -312,7 +312,7 @@ public Task ReportHealthAsync(TelemetryDataBase telemetryData, CancellationToken } else { - properties = new Dictionary + properties = new() { { "ClusterId", telemetryData.ClusterId }, { "EntityType", telemetryData.EntityType.ToString() }, @@ -399,7 +399,7 @@ public Task ReportMetricAsync(TelemetryDataBase telemetryData, CancellationToken if (telemetryData is ServiceTelemetryData serviceTelemData) { - properties = new Dictionary + properties = new() { { "ClusterId", serviceTelemData.ClusterId }, { "EntityType", serviceTelemData.EntityType.ToString() }, @@ -428,7 +428,7 @@ public Task ReportMetricAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is NodeTelemetryData nodeTelemData) { - properties = new Dictionary + properties = new() { { "ClusterId", nodeTelemData.ClusterId }, { "EntityType", nodeTelemData.EntityType.ToString() }, @@ -443,7 +443,7 @@ public Task ReportMetricAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is DiskTelemetryData diskTelemData) { - properties = new Dictionary + properties = new() { { "ClusterId", diskTelemData.ClusterId }, { "EntityType", diskTelemData.EntityType.ToString() }, @@ -459,7 +459,7 @@ public Task ReportMetricAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is ClusterTelemetryData clusterTelemData) { - properties = new Dictionary + properties = new() { { "ClusterId", clusterTelemData.ClusterId }, { "EntityType", EntityType.Cluster.ToString() }, @@ -472,7 +472,7 @@ public Task ReportMetricAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is SystemServiceTelemetryData systemServiceTelemData) { - properties = new Dictionary + properties = new() { { "ApplicationName", systemServiceTelemData.ApplicationName }, { "ClusterId", systemServiceTelemData.ClusterId }, @@ -489,7 +489,7 @@ public Task ReportMetricAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is NetworkTelemetryData networkTelemData) { - properties = new Dictionary + properties = new() { { "ApplicationName", networkTelemData.ApplicationName }, { "ClusterId", networkTelemData.ClusterId }, @@ -503,7 +503,7 @@ public Task ReportMetricAsync(TelemetryDataBase telemetryData, CancellationToken } else if (telemetryData is ContainerTelemetryData containerTelemData) { - properties = new Dictionary + properties = new() { { "ApplicationName", containerTelemData.ApplicationName }, { "ServiceName", containerTelemData.ServiceName }, @@ -522,7 +522,7 @@ public Task ReportMetricAsync(TelemetryDataBase telemetryData, CancellationToken } else { - properties = new Dictionary + properties = new() { { "ClusterId", telemetryData.ClusterId }, { "EntityType", telemetryData.EntityType.ToString() }, @@ -704,7 +704,7 @@ public Task ReportClusterUpgradeStatusAsync(ServiceFabricUpgradeEventData eventD try { - IDictionary eventProperties = new Dictionary + Dictionary eventProperties = new() { { "EventName", "ClusterUpgradeEvent" }, { "TaskName", eventData.TaskName }, @@ -749,7 +749,7 @@ public Task ReportApplicationUpgradeStatusAsync(ServiceFabricUpgradeEventData ev try { - IDictionary eventProperties = new Dictionary + Dictionary eventProperties = new() { { "EventName", "ApplicationUpgradeEvent" }, { "TaskName", eventData.TaskName }, diff --git a/FabricObserver.Extensibility/Utilities/Telemetry/LogAnalyticsTelemetry.cs b/FabricObserver.Extensibility/Utilities/Telemetry/LogAnalyticsTelemetry.cs index 54dfef6d..68858749 100644 --- a/FabricObserver.Extensibility/Utilities/Telemetry/LogAnalyticsTelemetry.cs +++ b/FabricObserver.Extensibility/Utilities/Telemetry/LogAnalyticsTelemetry.cs @@ -21,27 +21,30 @@ namespace FabricObserver.Observers.Utilities.Telemetry { // LogAnalyticsTelemetry class is partially (SendTelemetryAsync/GetSignature) based on public sample: https://dejanstojanovic.net/aspnet/2018/february/send-data-to-azure-log-analytics-from-c-code/ - public class LogAnalyticsTelemetry : ITelemetryProvider + public class LogAnalyticsTelemetry( + string workspaceId, + string sharedKey, + string logType) : ITelemetryProvider { private const string ApiVersion = "2016-04-01"; - private readonly Logger logger; + private readonly Logger logger = new Logger("TelemetryLogger"); private string WorkspaceId { get; - } + } = workspaceId; private string LogType { get; - } + } = logType; private string TargetUri => $"https://{WorkspaceId}.ods.opinsights.azure.com/api/logs?api-version={ApiVersion}"; public string Key { get; set; - } + } = sharedKey; /// /// Sends telemetry data to Azure LogAnalytics via REST. @@ -111,17 +114,6 @@ private string GetSignature( return $"SharedKey {WorkspaceId}:{Convert.ToBase64String(encryptor.ComputeHash(bytes))}"; } - public LogAnalyticsTelemetry( - string workspaceId, - string sharedKey, - string logType) - { - WorkspaceId = workspaceId; - Key = sharedKey; - LogType = logType; - logger = new Logger("TelemetryLogger"); - } - public async Task ReportHealthAsync( string propertyName, HealthState state, diff --git a/FabricObserver.nuspec.template b/FabricObserver.nuspec.template index 88a02911..a3381d87 100644 --- a/FabricObserver.nuspec.template +++ b/FabricObserver.nuspec.template @@ -2,10 +2,11 @@ %PACKAGE_ID% - 3.2.15 + 3.3.0 -- Added support for Observer raw metric telemetry disablement via configuration. This enables you to disable raw metric telemetry for any supported observer. This is useful if you are running FabricObserver in a non-production environment and do not want to send a raw telemetry data to Application Insights or Log Analytics. This feature is disabled by default. To enable it, you must set the [Observer name]EmitRawMetricTelemetry parameter to false in ApplicationManifest.xml. -- Fixed a bug in AppObserver that can lead to FabricObserver incorrectly reporting that a target service process couldn't be monitored because it was running at a higher privilege level than FabricObserver. + - .NET 8 implementation of FabricObserver. This version is built for .NET 8 and SF Runtime >= 9.1 (Self-Contained FO builds only). If you have deployed SF Runtime version >= 10.1 Cumulative Update 3.0 (CU3), then you can deploy the framework-dependent release build for the target platform (Windows or Linux). If you are not running SF Runtime version >= 10.1 CU3, then you must deploy the Self-Contained release build for the target platform (Windows or Linux). **If you can't upgrade to .NET 8 yet, then do not upgrade to this version.** + - The FabricObserverWebAPI project has been completely removed and all related usage in observers removed. + - FabricSystemObserver no longer monitors Windows Event Logs. Setting the related configuration values will have no effect. Microsoft MIT @@ -14,12 +15,12 @@ icon.png fonuget.md en-US - This package contains the FabricObserver(FO) Application - built for .NET 6.0 and SF Runtime 9.x. FO a highly configurable and extensible resource usage watchdog service that is designed to be run in Azure Service Fabric Windows and Linux clusters. This package contains the entire application and can be used to build .NET Standard 2.0 observer plugins. NOTE: If you want to target .NET 6 for your plugins, then you must use Microsoft.ServiceFabricApps.FabricObserver.Extensibility.3.2.15 nuget package to build them. + This package contains the FabricObserver(FO) Application - built for NET8 and SF Runtime 9.x. FO a highly configurable and extensible resource usage watchdog service that is designed to be run in Azure Service Fabric Windows and Linux clusters. This package contains the entire application and can be used to build .NET Standard 2.0 or .NET 8 observer plugins. - + @@ -29,9 +30,9 @@ - - - + + + diff --git a/FabricObserver.sln b/FabricObserver.sln index 2b2a0050..b7574bb7 100644 --- a/FabricObserver.sln +++ b/FabricObserver.sln @@ -35,10 +35,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md SECURITY.md = SECURITY.md Documentation\Deployment\service-fabric-cluster-observer.json = Documentation\Deployment\service-fabric-cluster-observer.json - Documentation\Deployment\service-fabric-cluster-observer.v2.2.8.parameters.json = Documentation\Deployment\service-fabric-cluster-observer.v2.2.8.parameters.json Documentation\Deployment\service-fabric-observer.json = Documentation\Deployment\service-fabric-observer.json + Documentation\Deployment\service-fabric-cluster-observer.v2.3.0.parameters.json = Documentation\Deployment\service-fabric-cluster-observer.v2.3.0.parameters.json Documentation\Using.md = Documentation\Using.md - Documentation\Deployment\service-fabric-observer.v3.2.15.parameters.json = Documentation\Deployment\service-fabric-observer.v3.2.15.parameters.json + Documentation\Deployment\service-fabric-observer.v3.3.0.parameters.json = Documentation\Deployment\service-fabric-observer.v3.3.0.parameters.json EndProjectSection EndProject Project("{A07B5EB6-E848-4116-A8D0-A826331D98C6}") = "ClusterObserverApp", "ClusterObserverApp\ClusterObserverApp.sfproj", "{BD5D216F-5F89-4CC4-92FD-D6FDEC5A19AD}" diff --git a/FabricObserver/FabricObserver.cs b/FabricObserver/FabricObserver.cs index 6906356b..ed81be85 100644 --- a/FabricObserver/FabricObserver.cs +++ b/FabricObserver/FabricObserver.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using FabricObserver.Observers; using FabricObserver.Observers.Utilities; +using FabricObserver.Observers.Utilities.Telemetry; using FabricObserver.Utilities; using McMaster.NETCore.Plugins; using Microsoft.Extensions.DependencyInjection; @@ -22,19 +23,14 @@ namespace FabricObserver /// /// An instance of this class is created for each service instance by the Service Fabric runtime. /// - internal sealed class FabricObserverService : StatelessService + /// + /// Initializes a new instance of the type. + /// + /// StatelessServiceContext instance. + internal sealed class FabricObserverService(StatelessServiceContext context) : StatelessService(context) { private ObserverManager observerManager; - private readonly Logger logger; - - /// - /// Initializes a new instance of the type. - /// - /// StatelessServiceContext instance. - public FabricObserverService(StatelessServiceContext context) : base(context) - { - logger = new Logger("FabricObserverService"); - } + private readonly Logger logger = new("FabricObserverService"); /// /// This is the main entry point for your service instance. @@ -66,7 +62,6 @@ private void ConfigureServices(IServiceCollection services) _ = services.AddScoped(typeof(ObserverBase), s => new NetworkObserver(Context)); _ = services.AddScoped(typeof(ObserverBase), s => new NodeObserver(Context)); _ = services.AddScoped(typeof(ObserverBase), s => new OSObserver(Context)); - _ = services.AddScoped(typeof(ObserverBase), s => new SFConfigurationObserver(Context)); _ = services.AddSingleton(typeof(StatelessServiceContext), Context); LoadObserversFromPlugins(services); @@ -109,7 +104,7 @@ private void LoadObserversFromPlugins(IServiceCollection services) } PluginLoader[] pluginLoaders = new PluginLoader[pluginDlls.Length]; - Type[] sharedTypes = { typeof(FabricObserverStartupAttribute), typeof(IFabricObserverStartup), typeof(IServiceCollection) }; + Type[] sharedTypes = [typeof(FabricObserverStartupAttribute), typeof(IFabricObserverStartup), typeof(IServiceCollection)]; string dll = ""; for (int i = 0; i < pluginDlls.Length; ++i) @@ -159,7 +154,7 @@ private void LoadObserversFromPlugins(IServiceCollection services) AppName = new Uri($"{Context.CodePackageActivationContext.ApplicationName}"), EmitLogEvent = true, HealthMessage = error, - EntityType = Observers.Utilities.Telemetry.EntityType.Application, + EntityType = EntityType.Application, HealthReportTimeToLive = TimeSpan.FromMinutes(10), State = System.Fabric.Health.HealthState.Warning, Property = "FabricObserverPluginLoadError", diff --git a/FabricObserver/FabricObserver.csproj b/FabricObserver/FabricObserver.csproj index 3e8e0e4c..833e4257 100644 --- a/FabricObserver/FabricObserver.csproj +++ b/FabricObserver/FabricObserver.csproj @@ -4,22 +4,22 @@ FabricObserver FabricObserver Exe - net6.0 + net8.0 disable True win-x64;linux-x64 True - Copyright © 2022 + Copyright © 2024 FabricObserver - 3.2.15 - 3.2.15 + 3.3.0 + 3.3.0 true true FabricObserver.Program - CA1822;$(NoWarn) true x64 - CA1416 + @@ -30,8 +30,8 @@ - - + + diff --git a/FabricObserver/Observers/AppObserver.cs b/FabricObserver/Observers/AppObserver.cs index a1e0c50d..4034f39a 100644 --- a/FabricObserver/Observers/AppObserver.cs +++ b/FabricObserver/Observers/AppObserver.cs @@ -26,10 +26,14 @@ using Microsoft.Win32.SafeHandles; namespace FabricObserver.Observers -{ - // This observer monitors the behavior of user SF service processes (and their children) and signals Warning and Error based on user-supplied resource thresholds - // in AppObserver.config.json. This observer will also emit telemetry (ETW, LogAnalytics/AppInsights) if enabled in Settings.xml (ObserverManagerConfiguration) and ApplicationManifest.xml (AppObserverEnableEtw). - public sealed class AppObserver : ObserverBase +{ + // This observer monitors the behavior of user SF service processes (and their children) and signals Warning and Error based on user-supplied resource thresholds + // in AppObserver.config.json. This observer will also emit telemetry (ETW, LogAnalytics/AppInsights) if enabled in Settings.xml (ObserverManagerConfiguration) and ApplicationManifest.xml (AppObserverEnableEtw). + /// + /// Creates a new instance of the type. + /// + /// The StatelessServiceContext instance. + public sealed class AppObserver(StatelessServiceContext context) : ObserverBase(null, context) { private const double KvsLvidsWarningPercentage = 75.0; private const double MaxRGMemoryInUsePercent = 90.0; @@ -71,7 +75,7 @@ public sealed class AppObserver : ObserverBase // List is thread-safe for concurrent reads. There are no concurrent writes to this List. private List deployedApps; - private readonly Stopwatch stopwatch; + private readonly Stopwatch stopwatch = new(); private readonly object lockObj = new(); private FabricClientUtilities fabricClientUtilities; private ParallelOptions parallelOptions; @@ -173,15 +177,6 @@ public bool MonitorResourceGovernanceLimits get; set; } - /// - /// Creates a new instance of the type. - /// - /// The StatelessServiceContext instance. - public AppObserver(StatelessServiceContext context) : base(null, context) - { - stopwatch = new Stopwatch(); - } - public override async Task ObserveAsync(CancellationToken token) { ObserverLogger.LogInfo($"Started ObserveAsync."); @@ -373,9 +368,9 @@ public override Task ReportAsync(CancellationToken token) try { // CPU Time (Percent) - if (AllAppCpuData != null && AllAppCpuData.ContainsKey(id)) + if (AllAppCpuData != null && AllAppCpuData.TryGetValue(id, out FabricResourceUsageData cpuPctFrud)) { - var parentFrud = AllAppCpuData[id]; + var parentFrud = cpuPctFrud; int childProcCount = 0; if (hasChildProcs) @@ -413,9 +408,9 @@ public override Task ReportAsync(CancellationToken token) } // Working Set (MB) - if (AllAppMemDataMb != null && AllAppMemDataMb.ContainsKey(id)) + if (AllAppMemDataMb != null && AllAppMemDataMb.TryGetValue(id, out FabricResourceUsageData memMbFrud)) { - var parentFrud = AllAppMemDataMb[id]; + var parentFrud = memMbFrud; int childProcCount = 0; if (hasChildProcs) @@ -452,9 +447,9 @@ public override Task ReportAsync(CancellationToken token) } // Working Set (Percent) - if (AllAppMemDataPercent != null && AllAppMemDataPercent.ContainsKey(id)) + if (AllAppMemDataPercent != null && AllAppMemDataPercent.TryGetValue(id, out FabricResourceUsageData memPctFrud)) { - var parentFrud = AllAppMemDataPercent[id]; + var parentFrud = memPctFrud; int childProcCount = 0; if (hasChildProcs) @@ -491,11 +486,11 @@ public override Task ReportAsync(CancellationToken token) } // Private Bytes (MB) - if (AllAppPrivateBytesDataMb != null && AllAppPrivateBytesDataMb.ContainsKey(id)) + if (AllAppPrivateBytesDataMb != null && AllAppPrivateBytesDataMb.TryGetValue(id, out FabricResourceUsageData privateBytesMbFrud)) { if (app.WarningPrivateBytesMb > 0 || app.ErrorPrivateBytesMb > 0) { - var parentFrud = AllAppPrivateBytesDataMb[id]; + var parentFrud = privateBytesMbFrud; int childProcCount = 0; if (hasChildProcs) @@ -533,11 +528,11 @@ public override Task ReportAsync(CancellationToken token) } // Private Bytes (Percent) - if (AllAppPrivateBytesDataPercent != null && AllAppPrivateBytesDataPercent.ContainsKey(id)) + if (AllAppPrivateBytesDataPercent != null && AllAppPrivateBytesDataPercent.TryGetValue(id, out FabricResourceUsageData privateBytesPctFrud)) { if (app.WarningPrivateBytesPercent > 0 || app.ErrorPrivateBytesPercent > 0) { - var parentFrud = AllAppPrivateBytesDataPercent[id]; + var parentFrud = privateBytesPctFrud; int childProcCount = 0; if (hasChildProcs) @@ -578,9 +573,9 @@ public override Task ReportAsync(CancellationToken token) if (MonitorResourceGovernanceLimits) { // RG Memory Monitoring (Private Bytes Percent) - if (repOrInst.RGMemoryEnabled && AllAppRGMemoryUsagePercent != null && AllAppRGMemoryUsagePercent.ContainsKey(id)) + if (repOrInst.RGMemoryEnabled && AllAppRGMemoryUsagePercent != null && AllAppRGMemoryUsagePercent.TryGetValue(id, out FabricResourceUsageData rgMemPctFrud)) { - var parentFrud = AllAppRGMemoryUsagePercent[id]; + var parentFrud = rgMemPctFrud; int childProcCount = 0; if (hasChildProcs) @@ -617,9 +612,9 @@ public override Task ReportAsync(CancellationToken token) } // RG CPU Monitoring (CPU Time Percent) - if (repOrInst.RGCpuEnabled && AllAppRGCpuUsagePercent != null && AllAppRGCpuUsagePercent.ContainsKey(id)) + if (repOrInst.RGCpuEnabled && AllAppRGCpuUsagePercent != null && AllAppRGCpuUsagePercent.TryGetValue(id, out FabricResourceUsageData rgCpuPctFrud)) { - var parentFrud = AllAppRGCpuUsagePercent[id]; + var parentFrud = rgCpuPctFrud; int childProcCount = 0; if (hasChildProcs) @@ -657,9 +652,9 @@ public override Task ReportAsync(CancellationToken token) } // TCP Ports - Active - if (AllAppTotalActivePortsData != null && AllAppTotalActivePortsData.ContainsKey(id)) + if (AllAppTotalActivePortsData != null && AllAppTotalActivePortsData.TryGetValue(id, out FabricResourceUsageData tcpPortsFrud)) { - var parentFrud = AllAppTotalActivePortsData[id]; + var parentFrud = tcpPortsFrud; int childProcCount = 0; if (hasChildProcs) @@ -696,9 +691,9 @@ public override Task ReportAsync(CancellationToken token) } // TCP Ports Total - Ephemeral (port numbers fall in the dynamic range) - if (AllAppEphemeralPortsData != null && AllAppEphemeralPortsData.ContainsKey(id)) + if (AllAppEphemeralPortsData != null && AllAppEphemeralPortsData.TryGetValue(id, out FabricResourceUsageData ePortsRawFrud)) { - var parentFrud = AllAppEphemeralPortsData[id]; + var parentFrud = ePortsRawFrud; int childProcCount = 0; if (hasChildProcs) @@ -735,9 +730,9 @@ public override Task ReportAsync(CancellationToken token) } // TCP Ports Percentage - Ephemeral (port numbers fall in the dynamic range) - if (AllAppEphemeralPortsDataPercent != null && AllAppEphemeralPortsDataPercent.ContainsKey(id)) + if (AllAppEphemeralPortsDataPercent != null && AllAppEphemeralPortsDataPercent.TryGetValue(id, out FabricResourceUsageData ePortsPctFrud)) { - var parentFrud = AllAppEphemeralPortsDataPercent[id]; + var parentFrud = ePortsPctFrud; int childProcCount = 0; if (hasChildProcs) @@ -774,9 +769,9 @@ public override Task ReportAsync(CancellationToken token) } // Handles - if (AllAppHandlesData != null && AllAppHandlesData.ContainsKey(id)) + if (AllAppHandlesData != null && AllAppHandlesData.TryGetValue(id, out FabricResourceUsageData handlesFrud)) { - var parentFrud = AllAppHandlesData[id]; + var parentFrud = handlesFrud; int childProcCount = 0; if (hasChildProcs) @@ -813,9 +808,9 @@ public override Task ReportAsync(CancellationToken token) } // Threads - if (AllAppThreadsData != null && AllAppThreadsData.ContainsKey(id)) + if (AllAppThreadsData != null && AllAppThreadsData.TryGetValue(id, out FabricResourceUsageData threadsFrud)) { - var parentFrud = AllAppThreadsData[id]; + var parentFrud = threadsFrud; int childProcCount = 0; if (hasChildProcs) @@ -852,9 +847,9 @@ public override Task ReportAsync(CancellationToken token) } // KVS LVIDs - Windows-only (EnableKvsLvidMonitoring will always be false otherwise) - if (EnableKvsLvidMonitoring && AllAppKvsLvidsData != null && AllAppKvsLvidsData.ContainsKey(id)) + if (EnableKvsLvidMonitoring && AllAppKvsLvidsData != null && AllAppKvsLvidsData.TryGetValue(id, out FabricResourceUsageData lvidsFrud)) { - var parentFrud = AllAppKvsLvidsData[id]; + var parentFrud = lvidsFrud; int childProcCount = 0; if (hasChildProcs) @@ -903,7 +898,7 @@ public override Task ReportAsync(CancellationToken token) if (IsTelemetryEnabled && EmitRawMetricTelemetry) { - _ = TelemetryClient?.ReportMetricAsync(childProcessTelemetryDataList.ToList(), token); + _ = TelemetryClient?.ReportMetricAsync([.. childProcessTelemetryDataList], token); } } } @@ -925,9 +920,9 @@ public override Task ReportAsync(CancellationToken token) public async Task InitializeAsync() { ObserverLogger.LogInfo($"Initializing AppObserver."); - ReplicaOrInstanceList = new List(); - userTargetList = new List(); - deployedTargetList = new List(); + ReplicaOrInstanceList = []; + userTargetList = []; + deployedTargetList = []; // NodeName is passed here to not break unit tests, which include a mock service fabric context. fabricClientUtilities = new FabricClientUtilities(NodeName); @@ -1187,7 +1182,7 @@ private bool PopulateAppInfoWithAppManifestThresholds(DeployedApplication deploy } } - ApplicationParameterList parameters = new(); + ApplicationParameterList parameters = []; FabricClientUtilities.AddParametersIfNotExists(parameters, appParameters); FabricClientUtilities.AddParametersIfNotExists(parameters, defaultParameters); @@ -1683,7 +1678,7 @@ private void FilterTargetAppFormat() continue; } - if (target.TargetApp == "*" || target.TargetApp.ToLower() == "all") + if (target.TargetApp == "*" || target.TargetApp.Equals("all", StringComparison.CurrentCultureIgnoreCase)) { continue; } @@ -1702,7 +1697,7 @@ private void FilterTargetAppFormat() target.TargetApp = target.TargetApp.Replace("://", ":/"); } - if (target.TargetApp.Contains(" ")) + if (target.TargetApp.Contains(' ')) { target.TargetApp = target.TargetApp.Replace(" ", string.Empty); } @@ -2103,7 +2098,7 @@ private int ProcessChildProcs( PartitionId = repOrInst.PartitionId.ToString(), ReplicaId = repOrInst.ReplicaOrInstanceId, ChildProcessCount = childProcs.Count, - ChildProcessInfo = new List() + ChildProcessInfo = [] }; string appNameOrType = GetAppNameOrType(repOrInst); @@ -2466,9 +2461,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppCpuData.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.CpuTime, id, capacity, UseCircularBuffer, EnableConcurrentMonitoring)); } - if (AllAppCpuData != null && AllAppCpuData.ContainsKey(id)) - { - AllAppCpuData[id].ClearData(); + if (AllAppCpuData != null && AllAppCpuData.TryGetValue(id, out FabricResourceUsageData cpuDataFrud)) + { + cpuDataFrud.ClearData(); checkCpu = true; } @@ -2478,9 +2473,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppMemDataMb.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.MemoryConsumptionMb, id, capacity, UseCircularBuffer, EnableConcurrentMonitoring)); } - if (AllAppMemDataMb != null && AllAppMemDataMb.ContainsKey(id)) - { - AllAppMemDataMb[id].ClearData(); + if (AllAppMemDataMb != null && AllAppMemDataMb.TryGetValue(id, out FabricResourceUsageData memDataMbFrud)) + { + memDataMbFrud.ClearData(); checkMemMb = true; } @@ -2490,9 +2485,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppMemDataPercent.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.MemoryConsumptionPercentage, id, capacity, UseCircularBuffer, EnableConcurrentMonitoring)); } - if (AllAppMemDataPercent != null && AllAppMemDataPercent.ContainsKey(id)) - { - AllAppMemDataPercent[id].ClearData(); + if (AllAppMemDataPercent != null && AllAppMemDataPercent.TryGetValue(id, out FabricResourceUsageData memDataPctFrud)) + { + memDataPctFrud.ClearData(); checkMemPct = true; } @@ -2502,9 +2497,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppPrivateBytesDataMb.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.PrivateBytesMb, id, 1, false, EnableConcurrentMonitoring)); } - if (IsWindows && AllAppPrivateBytesDataMb != null && AllAppPrivateBytesDataMb.ContainsKey(id)) - { - AllAppPrivateBytesDataMb[id].ClearData(); + if (IsWindows && AllAppPrivateBytesDataMb != null && AllAppPrivateBytesDataMb.TryGetValue(id, out FabricResourceUsageData prBytesMbFrud)) + { + prBytesMbFrud.ClearData(); checkMemPrivateBytes = true; } @@ -2514,9 +2509,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppPrivateBytesDataPercent.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.PrivateBytesPercent, id, 1, false, EnableConcurrentMonitoring)); } - if (IsWindows && AllAppPrivateBytesDataPercent != null && AllAppPrivateBytesDataPercent.ContainsKey(id)) - { - AllAppPrivateBytesDataPercent[id].ClearData(); + if (IsWindows && AllAppPrivateBytesDataPercent != null && AllAppPrivateBytesDataPercent.TryGetValue(id, out FabricResourceUsageData prBytesPctFrud)) + { + prBytesPctFrud.ClearData(); checkMemPrivateBytesPct = true; } @@ -2526,7 +2521,7 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppRGMemoryUsagePercent.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.RGMemoryUsagePercent, id, 1, false, EnableConcurrentMonitoring)); } - if (IsWindows && AllAppRGMemoryUsagePercent != null && AllAppRGMemoryUsagePercent.ContainsKey(id)) + if (IsWindows && AllAppRGMemoryUsagePercent != null && AllAppRGMemoryUsagePercent.TryGetValue(id, out FabricResourceUsageData rgMemPctFrud)) { rgMemoryPercentThreshold = application.WarningRGMemoryLimitPercent; @@ -2540,9 +2535,9 @@ any processes that the service process (parent) created/spawned (children). */ else { rgMemoryPercentThreshold = MaxRGMemoryInUsePercent; // Default: 90%. - } - - AllAppRGMemoryUsagePercent[id].ClearData(); + } + + rgMemPctFrud.ClearData(); } // CPU - RG monitoring. Windows-only for now. @@ -2551,7 +2546,7 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppRGCpuUsagePercent.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.RGCpuUsagePercent, id, 1, false, EnableConcurrentMonitoring)); } - if (IsWindows && AllAppRGCpuUsagePercent != null && AllAppRGCpuUsagePercent.ContainsKey(id)) + if (IsWindows && AllAppRGCpuUsagePercent != null && AllAppRGCpuUsagePercent.TryGetValue(id, out FabricResourceUsageData rgCpuPctDataFrud)) { rgCpuPercentThreshold = application.WarningRGCpuLimitPercent; @@ -2565,7 +2560,9 @@ any processes that the service process (parent) created/spawned (children). */ else { rgCpuPercentThreshold = MaxRGCpuInUsePercent; // Default: 90%. - } + } + + rgCpuPctDataFrud.ClearData(); } // Active TCP Ports @@ -2574,9 +2571,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppTotalActivePortsData.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.ActiveTcpPorts, id, 1, false, EnableConcurrentMonitoring)); } - if (AllAppTotalActivePortsData != null && AllAppTotalActivePortsData.ContainsKey(id)) - { - AllAppTotalActivePortsData[id].ClearData(); + if (AllAppTotalActivePortsData != null && AllAppTotalActivePortsData.TryGetValue(id, out FabricResourceUsageData totalActivePortsDataFrud)) + { + totalActivePortsDataFrud.ClearData(); checkAllPorts = true; } @@ -2586,9 +2583,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppEphemeralPortsData.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.ActiveEphemeralPorts, id, 1, false, EnableConcurrentMonitoring)); } - if (AllAppEphemeralPortsData != null && AllAppEphemeralPortsData.ContainsKey(id)) - { - AllAppEphemeralPortsData[id].ClearData(); + if (AllAppEphemeralPortsData != null && AllAppEphemeralPortsData.TryGetValue(id, out FabricResourceUsageData ePortsDataFrud)) + { + ePortsDataFrud.ClearData(); checkEphemeralPorts = true; } @@ -2598,9 +2595,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppEphemeralPortsDataPercent.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.ActiveEphemeralPortsPercentage, id, 1, false, EnableConcurrentMonitoring)); } - if (AllAppEphemeralPortsDataPercent != null && AllAppEphemeralPortsDataPercent.ContainsKey(id)) - { - AllAppEphemeralPortsDataPercent[id].ClearData(); + if (AllAppEphemeralPortsDataPercent != null && AllAppEphemeralPortsDataPercent.TryGetValue(id, out FabricResourceUsageData ePortsPctDataFrud)) + { + ePortsPctDataFrud.ClearData(); checkPercentageEphemeralPorts = true; } @@ -2611,9 +2608,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppHandlesData.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.HandleCount, id, 1, false, EnableConcurrentMonitoring)); } - if (AllAppHandlesData != null && AllAppHandlesData.ContainsKey(id)) - { - AllAppHandlesData[id].ClearData(); + if (AllAppHandlesData != null && AllAppHandlesData.TryGetValue(id, out FabricResourceUsageData handlesDataFrud)) + { + handlesDataFrud.ClearData(); checkHandles = true; } @@ -2623,9 +2620,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppThreadsData.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.ThreadCount, id, 1, false, EnableConcurrentMonitoring)); } - if (AllAppThreadsData != null && AllAppThreadsData.ContainsKey(id)) - { - AllAppThreadsData[id].ClearData(); + if (AllAppThreadsData != null && AllAppThreadsData.TryGetValue(id, out FabricResourceUsageData threadsDataFrud)) + { + threadsDataFrud.ClearData(); checkThreads = true; } @@ -2636,9 +2633,9 @@ any processes that the service process (parent) created/spawned (children). */ _ = AllAppKvsLvidsData.TryAdd(id, new FabricResourceUsageData(ErrorWarningProperty.KvsLvidsPercent, id, 1, false, EnableConcurrentMonitoring)); } - if (AllAppKvsLvidsData != null && AllAppKvsLvidsData.ContainsKey(id)) - { - AllAppKvsLvidsData[id].ClearData(); + if (AllAppKvsLvidsData != null && AllAppKvsLvidsData.TryGetValue(id, out FabricResourceUsageData lvidsDataFrud)) + { + lvidsDataFrud.ClearData(); checkLvids = true; } @@ -3326,7 +3323,7 @@ private async Task> GetDeployedReplicasAsy ConfigurationSettings.AsyncTimeout, Token); - if (deployedReplicaList == null || !deployedReplicaList.Any()) + if (deployedReplicaList == null || deployedReplicaList.Count == 0) { return null; } @@ -3408,7 +3405,7 @@ private void SetInstanceOrReplicaMonitoringList( { if (filterList != null && filterType != ServiceFilterType.None) { - bool isInFilterList = filterList.Any(s => statefulReplica.ServiceName.OriginalString.ToLower().Contains(s.ToLower())); + bool isInFilterList = filterList.Any(s => statefulReplica.ServiceName.OriginalString.Contains(s, StringComparison.CurrentCultureIgnoreCase)); switch (filterType) { @@ -3464,7 +3461,7 @@ any processes (children) that the service process (parent) created/spawned. */ { if (filterList != null && filterType != ServiceFilterType.None) { - bool isInFilterList = filterList.Any(s => statelessInstance.ServiceName.OriginalString.ToLower().Contains(s.ToLower())); + bool isInFilterList = filterList.Any(s => statelessInstance.ServiceName.OriginalString.Contains(s, StringComparison.CurrentCultureIgnoreCase)); switch (filterType) { @@ -3723,7 +3720,7 @@ private void ProcessServiceConfiguration(string appTypeName, string codepackageN if (!string.IsNullOrWhiteSpace(appManifest) && appManifest.Contains($"<{ObserverConstants.RGPolicyNodeName} ") || appManifest.Contains($"<{ObserverConstants.RGSvcPkgPolicyNodeName} ")) { - ApplicationParameterList parameters = new(); + ApplicationParameterList parameters = []; FabricClientUtilities.AddParametersIfNotExists(parameters, appParameters); FabricClientUtilities.AddParametersIfNotExists(parameters, defaultParameters); @@ -3968,45 +3965,45 @@ private void LogAllAppResourceDataToCsv(string key) // Memory - Working set \\ - if (AllAppMemDataMb != null && AllAppMemDataMb.ContainsKey(key)) + if (AllAppMemDataMb != null && AllAppMemDataMb.TryGetValue(key, out FabricResourceUsageData memDataMbLogFrud)) { CsvFileLogger.LogData( fileName, key, ErrorWarningProperty.MemoryConsumptionMb, "Average", - AllAppMemDataMb[key].AverageDataValue); + memDataMbLogFrud.AverageDataValue); CsvFileLogger.LogData( fileName, key, ErrorWarningProperty.MemoryConsumptionMb, "Peak", - AllAppMemDataMb[key].MaxDataValue); + memDataMbLogFrud.MaxDataValue); } - if (AllAppMemDataPercent != null && AllAppMemDataPercent.ContainsKey(key)) + if (AllAppMemDataPercent != null && AllAppMemDataPercent.TryGetValue(key, out FabricResourceUsageData memDataPctLogFrud)) { CsvFileLogger.LogData( fileName, key, ErrorWarningProperty.MemoryConsumptionPercentage, "Average", - AllAppMemDataPercent[key].AverageDataValue); + memDataPctLogFrud.AverageDataValue); CsvFileLogger.LogData( fileName, key, ErrorWarningProperty.MemoryConsumptionPercentage, "Peak", - AllAppMemDataPercent[key].MaxDataValue); + memDataPctLogFrud.MaxDataValue); } // Memory - Private Bytes \\ if (IsWindows) { - if (AllAppPrivateBytesDataMb != null && AllAppPrivateBytesDataMb.ContainsKey(key)) + if (AllAppPrivateBytesDataMb != null && AllAppPrivateBytesDataMb.TryGetValue(key, out FabricResourceUsageData privBytesMbDataLogFrud)) { if (AllAppPrivateBytesDataMb.Any(x => x.Key == key)) { @@ -4015,18 +4012,18 @@ private void LogAllAppResourceDataToCsv(string key) key, ErrorWarningProperty.PrivateBytesMb, "Average", - AllAppPrivateBytesDataMb[key].AverageDataValue); + privBytesMbDataLogFrud.AverageDataValue); CsvFileLogger.LogData( fileName, key, ErrorWarningProperty.PrivateBytesMb, "Peak", - AllAppPrivateBytesDataMb[key].MaxDataValue); + privBytesMbDataLogFrud.MaxDataValue); } } - if (AllAppPrivateBytesDataPercent != null && AllAppPrivateBytesDataPercent.ContainsKey(key)) + if (AllAppPrivateBytesDataPercent != null && AllAppPrivateBytesDataPercent.TryGetValue(key, out FabricResourceUsageData privBytesPctLogFrud)) { if (AllAppPrivateBytesDataPercent.Any(x => x.Key == key)) { @@ -4035,60 +4032,60 @@ private void LogAllAppResourceDataToCsv(string key) key, ErrorWarningProperty.PrivateBytesPercent, "Average", - AllAppPrivateBytesDataPercent[key].AverageDataValue); + privBytesPctLogFrud.AverageDataValue); CsvFileLogger.LogData( fileName, key, ErrorWarningProperty.PrivateBytesPercent, "Peak", - AllAppPrivateBytesDataPercent[key].MaxDataValue); + privBytesPctLogFrud.MaxDataValue); } } } // Ports \\ - if (AllAppTotalActivePortsData != null && AllAppTotalActivePortsData.ContainsKey(key)) + if (AllAppTotalActivePortsData != null && AllAppTotalActivePortsData.TryGetValue(key, out FabricResourceUsageData activePortsLogFrod)) { CsvFileLogger.LogData( fileName, key, ErrorWarningProperty.ActiveTcpPorts, "Total", - AllAppTotalActivePortsData[key].MaxDataValue); + activePortsLogFrod.MaxDataValue); } - if (AllAppEphemeralPortsData != null && AllAppEphemeralPortsData.ContainsKey(key)) + if (AllAppEphemeralPortsData != null && AllAppEphemeralPortsData.TryGetValue(key, out FabricResourceUsageData ePortsLogFrud)) { CsvFileLogger.LogData( fileName, key, ErrorWarningProperty.ActiveEphemeralPorts, "Total", - AllAppEphemeralPortsData[key].MaxDataValue); + ePortsLogFrud.MaxDataValue); } // Handles - if (AllAppHandlesData != null && AllAppHandlesData.ContainsKey(key)) + if (AllAppHandlesData != null && AllAppHandlesData.TryGetValue(key, out FabricResourceUsageData handlesLogFrud)) { CsvFileLogger.LogData( fileName, key, ErrorWarningProperty.AllocatedFileHandles, "Total", - AllAppHandlesData[key].MaxDataValue); + handlesLogFrud.MaxDataValue); } // Threads - if (AllAppThreadsData != null && AllAppThreadsData.ContainsKey(key)) + if (AllAppThreadsData != null && AllAppThreadsData.TryGetValue(key, out FabricResourceUsageData threadsLogFrud)) { CsvFileLogger.LogData( fileName, key, ErrorWarningProperty.ThreadCount, "Total", - AllAppHandlesData[key].MaxDataValue); + threadsLogFrud.MaxDataValue); } } catch (Exception e) when (e is ArgumentException or KeyNotFoundException or InvalidOperationException) diff --git a/FabricObserver/Observers/AzureStorageUploadObserver.cs b/FabricObserver/Observers/AzureStorageUploadObserver.cs index 449d2ecc..96090519 100644 --- a/FabricObserver/Observers/AzureStorageUploadObserver.cs +++ b/FabricObserver/Observers/AzureStorageUploadObserver.cs @@ -23,9 +23,13 @@ namespace FabricObserver.Observers // configured to do so - assuming correctly encrypted and specified ConnectionString for an Azure Storage Account, a container name, and other basic settings. // Since only Windows is supported for dumping service processes today by FO, this observer is not useful for Liunx in this version. // So, if you are deploying FO to Linux servers, then don't enable this observer (it won't do anything if it is enabled, so no need have it resident in memory). - public sealed class AzureStorageUploadObserver : ObserverBase + /// + /// Creates a new instance of the type. + /// + /// The StatelessServiceContext instance. + public sealed class AzureStorageUploadObserver(StatelessServiceContext context) : ObserverBase(null, context) { - private readonly Stopwatch stopwatch; + private readonly Stopwatch stopwatch = new(); // Only AppObserver is supported today. No other observers generate dmp files. private const string AppObserverDumpFolder = "MemoryDumps"; @@ -61,15 +65,6 @@ private CompressionLevel ZipCompressionLevel get; set; } = CompressionLevel.Optimal; - /// - /// Creates a new instance of the type. - /// - /// The StatelessServiceContext instance. - public AzureStorageUploadObserver(StatelessServiceContext context) : base(null, context) - { - stopwatch = new Stopwatch(); - } - public override async Task ObserveAsync(CancellationToken token) { // Since there is currently only support for Windows process dumps (by AppObserver only), there is no need to run this Observer on Linux (today..). @@ -105,7 +100,7 @@ public override async Task ObserveAsync(CancellationToken token) } // In case upload failed, also try and upload any zip files that remained in target local directory. - await ProcessFilesAsync(appObsDumpFolderPath, new[] { "*.zip", "*.dmp" }, token); + await ProcessFilesAsync(appObsDumpFolderPath, ["*.zip", "*.dmp"], token); await ReportAsync(token); CleanUp(); diff --git a/FabricObserver/Observers/CertificateObserver.cs b/FabricObserver/Observers/CertificateObserver.cs index 1eedf6b1..5820e441 100644 --- a/FabricObserver/Observers/CertificateObserver.cs +++ b/FabricObserver/Observers/CertificateObserver.cs @@ -21,7 +21,11 @@ namespace FabricObserver.Observers { - public sealed class CertificateObserver : ObserverBase + /// + /// Creates a new instance of the type. + /// + /// The StatelessServiceContext instance. + public sealed class CertificateObserver(StatelessServiceContext context) : ObserverBase(null, context) { private const string HowToUpdateCnCertsSfLinkHtml = "Click here to learn how to update expiring/expired certificates."; @@ -49,15 +53,6 @@ private List ExpiringWarnings get; set; } - /// - /// Creates a new instance of the type. - /// - /// The StatelessServiceContext instance. - public CertificateObserver(StatelessServiceContext context) : base (null, context) - { - - } - public int DaysUntilClusterExpireWarningThreshold { get; set; @@ -101,9 +96,9 @@ public override async Task ObserveAsync(CancellationToken token) await Initialize(token); - ExpiredWarnings = new List(); - ExpiringWarnings = new List(); - NotFoundWarnings = new List(); + ExpiredWarnings = []; + ExpiringWarnings = []; + NotFoundWarnings = []; // Unix LocalMachine X509Store is limited to the Root and CertificateAuthority stores. var store = new X509Store(IsWindows ? StoreName.My : StoreName.Root, StoreLocation.LocalMachine); @@ -122,7 +117,7 @@ public override async Task ObserveAsync(CancellationToken token) CheckByThumbprint(store, SecurityConfiguration.ClusterCertThumbprintOrCommonName, DaysUntilClusterExpireWarningThreshold); } - if (AppCertificateCommonNamesToObserve != null && AppCertificateCommonNamesToObserve.Any()) + if (AppCertificateCommonNamesToObserve != null && AppCertificateCommonNamesToObserve.Count != 0) { // App certificates foreach (string commonName in AppCertificateCommonNamesToObserve) @@ -132,7 +127,7 @@ public override async Task ObserveAsync(CancellationToken token) } } - if (AppCertificateThumbprintsToObserve != null && AppCertificateThumbprintsToObserve.Any()) + if (AppCertificateThumbprintsToObserve != null && AppCertificateThumbprintsToObserve.Count != 0) { // App certificates foreach (string thumbprint in AppCertificateThumbprintsToObserve) @@ -325,7 +320,7 @@ private async Task Initialize(CancellationToken token) ConfigurationSectionName, ObserverConstants.CertificateObserverAppCertificateThumbprints); - AppCertificateThumbprintsToObserve = !string.IsNullOrEmpty(appThumbprintsToObserve) ? JsonHelper.ConvertFromString>(appThumbprintsToObserve) : new List(); + AppCertificateThumbprintsToObserve = !string.IsNullOrEmpty(appThumbprintsToObserve) ? JsonHelper.ConvertFromString>(appThumbprintsToObserve) : []; } if (AppCertificateCommonNamesToObserve == null) @@ -334,7 +329,7 @@ private async Task Initialize(CancellationToken token) ConfigurationSectionName, ObserverConstants.CertificateObserverAppCertificateCommonNames); - AppCertificateCommonNamesToObserve = !string.IsNullOrEmpty(appCommonNamesToObserve) ? JsonHelper.ConvertFromString>(appCommonNamesToObserve) : new List(); + AppCertificateCommonNamesToObserve = !string.IsNullOrEmpty(appCommonNamesToObserve) ? JsonHelper.ConvertFromString>(appCommonNamesToObserve) : []; } await GetSecurityTypes(token); @@ -362,15 +357,15 @@ private async Task GetSecurityTypes(CancellationToken token) var certificateNode = xdoc.SelectNodes($"//sf:NodeType[@Name='{NodeType}']//sf:Certificates", nsmgr); - if (certificateNode != null ? certificateNode.Count == 0 : false) + if (certificateNode != null && certificateNode.Count == 0) { SecurityConfiguration.SecurityType = SecurityType.None; } else { - var clusterCertificateNode = certificateNode != null ? certificateNode.Item(0) != null ? certificateNode.Item(0).ChildNodes.Item(0) : null : null; + var clusterCertificateNode = certificateNode?.Item(0)?.ChildNodes.Item(0); - var commonNameAttribute = clusterCertificateNode != null ? clusterCertificateNode.Attributes != null ? clusterCertificateNode.Attributes.GetNamedItem("X509FindType") : null : null; + var commonNameAttribute = clusterCertificateNode?.Attributes?.GetNamedItem("X509FindType"); if (commonNameAttribute != null) { if (commonNameAttribute.Value == "FindBySubjectName") @@ -387,7 +382,7 @@ private async Task GetSecurityTypes(CancellationToken token) SecurityConfiguration.SecurityType = SecurityType.Thumbprint; SecurityConfiguration.ClusterCertThumbprintOrCommonName = clusterCertificateNode != null ? clusterCertificateNode.Attributes != null ? clusterCertificateNode.Attributes.GetNamedItem("X509FindValue").Value : null : null; - var secondaryThumbprintAttribute = clusterCertificateNode != null ? clusterCertificateNode.Attributes != null ? clusterCertificateNode.Attributes.GetNamedItem("X509FindValueSecondary") : null : null; + var secondaryThumbprintAttribute = clusterCertificateNode != null ? clusterCertificateNode.Attributes?.GetNamedItem("X509FindValueSecondary") : null; if (secondaryThumbprintAttribute != null) { diff --git a/FabricObserver/Observers/ContainerObserver.cs b/FabricObserver/Observers/ContainerObserver.cs index 70e8abfe..ac59cff5 100644 --- a/FabricObserver/Observers/ContainerObserver.cs +++ b/FabricObserver/Observers/ContainerObserver.cs @@ -22,7 +22,11 @@ namespace FabricObserver.Observers { - public sealed class ContainerObserver : ObserverBase + /// + /// Creates a new instance of the type. + /// + /// The StatelessServiceContext instance. + public sealed class ContainerObserver(StatelessServiceContext context) : ObserverBase(null, context) { private const int MaxProcessExitWaitTimeMS = 60000; private ConcurrentDictionary> allCpuDataPercentage; @@ -48,14 +52,7 @@ public ParallelOptions ParallelOptions get; private set; } - /// - /// Creates a new instance of the type. - /// - /// The StatelessServiceContext instance. - public ContainerObserver(StatelessServiceContext context) : base(null, context) - { - - } + private static readonly char[] separator = new[] { ' ' }; // OsbserverManager passes in a special token to ObserveAsync and ReportAsync that enables it to stop this observer outside of // of the SF runtime, but this token will also cancel when the runtime cancels the main token. @@ -225,7 +222,7 @@ private async Task InitializeAsync(CancellationToken token) TaskScheduler = TaskScheduler.Default }; - userTargetList = new List(); + userTargetList = []; deployedTargetList = new ConcurrentQueue(); ReplicaOrInstanceList = new ConcurrentQueue(); @@ -512,7 +509,7 @@ fed0da6f7bad sf-243-723d5795-01c7-477f-950e-45a400000000_2cc293c0-929c-5c40-bc } else { - if (error.ToLower().Contains("permission denied")) + if (error.Contains("permission denied", StringComparison.CurrentCultureIgnoreCase)) { msg += "elevated_docker_stats Capabilities may have been removed. " + "This is most likely due to an SF runtime upgrade, which unsets Linux caps because SF re-acls (so, touches) all binaries in application Code folders as part of the upgrade process. " + @@ -566,7 +563,7 @@ fed0da6f7bad sf-243-723d5795-01c7-477f-950e-45a400000000_2cc293c0-929c-5c40-bc } // Linux: Try and work around the unsetting of caps issues when SF runs a cluster upgrade. - if (!IsWindows && error.ToLower().Contains("permission denied")) + if (!IsWindows && error.Contains("permission denied", StringComparison.CurrentCultureIgnoreCase)) { // Throwing LinuxPermissionException here will eventually take down FO (by design). The failure will be logged and telemetry will be emitted, then // the exception will be re-thrown by ObserverManager and the FO process will fail fast exit. Then, SF will create a new instance of FO on the offending node which @@ -613,7 +610,7 @@ fed0da6f7bad sf-243-723d5795-01c7-477f-950e-45a400000000_2cc293c0-929c-5c40-bc continue; } - string[] stats = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + string[] stats = line.Split(separator, StringSplitOptions.RemoveEmptyEntries); // Something went wrong if the collection size is less than 4 given the supplied output table format: // {{.Container}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}} @@ -734,7 +731,7 @@ private async Task SetInstanceOrReplicaMonitoringList( { if (filterList != null && filterType != ServiceFilterType.None) { - bool isInFilterList = filterList.Any(s => statefulReplica.ServiceName.OriginalString.ToLower().Contains(s.ToLower())); + bool isInFilterList = filterList.Any(s => statefulReplica.ServiceName.OriginalString.Contains(s, StringComparison.CurrentCultureIgnoreCase)); switch (filterType) { @@ -765,7 +762,7 @@ private async Task SetInstanceOrReplicaMonitoringList( { if (filterList != null && filterType != ServiceFilterType.None) { - bool isInFilterList = filterList.Any(s => statelessInstance.ServiceName.OriginalString.ToLower().Contains(s.ToLower())); + bool isInFilterList = filterList.Any(s => statelessInstance.ServiceName.OriginalString.Contains(s, StringComparison.CurrentCultureIgnoreCase)); switch (filterType) { diff --git a/FabricObserver/Observers/DiskObserver.cs b/FabricObserver/Observers/DiskObserver.cs index f0c84078..2f2821ec 100644 --- a/FabricObserver/Observers/DiskObserver.cs +++ b/FabricObserver/Observers/DiskObserver.cs @@ -22,7 +22,11 @@ namespace FabricObserver.Observers { // DiskObserver monitors logical disk states (space consumption, queue length and folder sizes) and creates Service Fabric // Warning or Error Node-level health reports based on settings in ApplicationManifest.xml. - public sealed class DiskObserver : ObserverBase + /// + /// Creates a new instance of the type. + /// + /// The StatelessServiceContext instance. + public sealed partial class DiskObserver(StatelessServiceContext context) : ObserverBase(null, context) { // Data storage containers for post run analysis. private List> DiskAverageQueueLengthData; @@ -30,8 +34,7 @@ public sealed class DiskObserver : ObserverBase private List> DiskSpaceAvailableMbData; private List> DiskSpaceTotalMbData; private List> FolderSizeDataMb; - private readonly Stopwatch stopWatch; - private StringBuilder diskInfo = new(); + private readonly Stopwatch stopWatch = new(); public int DiskSpacePercentErrorThreshold { @@ -70,15 +73,6 @@ public Dictionary FolderSizeConfigDataWarning get; set; } - /// - /// Creates a new instance of the type. - /// - /// The StatelessServiceContext instance. - public DiskObserver(StatelessServiceContext context) : base(null, context) - { - stopWatch = new Stopwatch(); - } - public override async Task ObserveAsync(CancellationToken token) { // If set, this observer will only run during the supplied interval. @@ -95,15 +89,10 @@ public override async Task ObserveAsync(CancellationToken token) DriveInfo[] allDrives = DriveInfo.GetDrives(); int driveCount = allDrives.Length; - if (IsObserverWebApiAppDeployed) - { - diskInfo = new StringBuilder(); - } - DiskSpaceUsagePercentageData ??= new List>(driveCount); DiskSpaceAvailableMbData ??= new List>(driveCount); DiskSpaceTotalMbData ??= new List>(driveCount); - FolderSizeDataMb ??= new List>(); + FolderSizeDataMb ??= []; if (IsWindows) { @@ -121,22 +110,6 @@ public override async Task ObserveAsync(CancellationToken token) continue; } - // This section only needs to run if you have the FabricObserverWebApi app installed. - if (IsObserverWebApiAppDeployed) - { - _ = diskInfo.AppendFormat("\n\nDrive Name: {0}\n", d.Name); - - // Logging. - _ = diskInfo.AppendFormat("Drive Type: {0}\n", d.DriveType); - _ = diskInfo.AppendFormat(" Volume Label : {0}\n", d.VolumeLabel); - _ = diskInfo.AppendFormat(" Filesystem : {0}\n", d.DriveFormat); - _ = diskInfo.AppendFormat(" Total Disk Size: {0} GB\n", d.TotalSize / 1024 / 1024 / 1024); - _ = diskInfo.AppendFormat(" Root Directory : {0}\n", d.RootDirectory); - _ = diskInfo.AppendFormat(" Free User : {0} GB\n", d.AvailableFreeSpace / 1024 / 1024 / 1024); - _ = diskInfo.AppendFormat(" Free Total: {0} GB\n", d.TotalFreeSpace / 1024 / 1024 / 1024); - _ = diskInfo.AppendFormat(" % Used : {0}%\n", DiskUsage.GetCurrentDiskSpaceUsedPercent(d.Name)); - } - string id = d.Name; if (IsWindows) @@ -180,7 +153,7 @@ public override async Task ObserveAsync(CancellationToken token) // Also, this feature is not supported for Linux yet. if (IsWindows && (AverageQueueLengthErrorThreshold > 0 || AverageQueueLengthWarningThreshold > 0)) { - DiskAverageQueueLengthData.Find(x => x.Id == id)?.AddData(DiskUsage.GetAverageDiskQueueLength(d.Name[..2])); + DiskAverageQueueLengthData.Find(x => x.Id == id)?.AddData(OSInfoProvider.Instance.GetAverageDiskQueueLength(d.Name[..2])); } if (DiskSpacePercentErrorThreshold > 0 || DiskSpacePercentWarningThreshold > 0) @@ -190,20 +163,6 @@ public override async Task ObserveAsync(CancellationToken token) DiskSpaceAvailableMbData.Find(x => x.Id == id)?.AddData(DiskUsage.GetAvailableDiskSpace(id, SizeUnit.Megabytes)); DiskSpaceTotalMbData.Find(x => x.Id == id)?.AddData(DiskUsage.GetTotalDiskSpace(id, SizeUnit.Megabytes)); - - // This section only needs to run if you have the FabricObserverWebApi app installed. - if (!IsObserverWebApiAppDeployed || !IsWindows) - { - continue; - } - - token.ThrowIfCancellationRequested(); - - _ = diskInfo.AppendFormat( - "{0}", - GetWindowsPerfCounterDetailsText(DiskAverageQueueLengthData.FirstOrDefault( - x => d.Name.Length > 0 && x.Id == d.Name[..1])?.Data, - "Avg. Disk Queue Length")); } /* Process Folder size data. */ @@ -251,7 +210,7 @@ private void ProcessFolderSizeConfig() if (!string.IsNullOrWhiteSpace(FolderPathsErrorThresholdPairs)) { - FolderSizeConfigDataError ??= new Dictionary(); + FolderSizeConfigDataError ??= []; AddFolderSizeConfigData(FolderPathsErrorThresholdPairs, false); } @@ -259,7 +218,7 @@ private void ProcessFolderSizeConfig() if (!string.IsNullOrWhiteSpace(FolderPathsWarningThresholdPairs)) { - FolderSizeConfigDataWarning ??= new Dictionary(); + FolderSizeConfigDataWarning ??= []; AddFolderSizeConfigData(FolderPathsWarningThresholdPairs, true); } } @@ -293,7 +252,7 @@ private void AddFolderSizeConfigData(string folderSizeConfig, bool isWarningThre // Contains env variable(s)? if (path.Contains('%')) { - if (Regex.Match(path, @"^%[a-zA-Z0-9_]+%").Success) + if (EnvRegex().Match(path).Success) { path = Environment.ExpandEnvironmentVariables(pairs[0]); } @@ -313,11 +272,11 @@ private void AddFolderSizeConfigData(string folderSizeConfig, bool isWarningThre { if (FolderSizeConfigDataWarning != null) { - if (!FolderSizeConfigDataWarning.ContainsKey(path)) + if (!FolderSizeConfigDataWarning.TryGetValue(path, out double folderSizeWarningThreshold)) { FolderSizeConfigDataWarning.Add(path, threshold); } - else if (FolderSizeConfigDataWarning[path] != threshold) // App Parameter upgrade? + else if (folderSizeWarningThreshold != threshold) // App Parameter upgrade? { FolderSizeConfigDataWarning[path] = threshold; } @@ -325,11 +284,11 @@ private void AddFolderSizeConfigData(string folderSizeConfig, bool isWarningThre } else if (FolderSizeConfigDataError != null) { - if (!FolderSizeConfigDataError.ContainsKey(path)) + if (!FolderSizeConfigDataError.TryGetValue(path, out double folderSizeErrorThreshold)) { FolderSizeConfigDataError.Add(path, threshold); } - else if (FolderSizeConfigDataError[path] != threshold) // App Parameter upgrade? + else if (folderSizeErrorThreshold != threshold) // App Parameter upgrade? { FolderSizeConfigDataError[path] = threshold; } @@ -440,7 +399,7 @@ private void CheckFolderSizeUsage(IDictionary data) // Contains Windows env variable(s)? if (IsWindows && path.Contains('%')) { - if (Regex.Match(path, @"^%[a-zA-Z0-9_]+%").Success) + if (EnvRegex().Match(path).Success) { path = Environment.ExpandEnvironmentVariables(item.Key); } @@ -534,14 +493,14 @@ public override Task ReportAsync(CancellationToken token) double errorThreshold = 0.0; double warningThreshold = 0.0; - if (FolderSizeConfigDataError?.Count > 0 && FolderSizeConfigDataError.ContainsKey(data.Id)) + if (FolderSizeConfigDataError?.Count > 0 && FolderSizeConfigDataError.TryGetValue(data.Id, out double fsDataSizeError)) { - errorThreshold = FolderSizeConfigDataError[data.Id]; + errorThreshold = fsDataSizeError; } - if (FolderSizeConfigDataWarning?.Count > 0 && FolderSizeConfigDataWarning.ContainsKey(data.Id)) + if (FolderSizeConfigDataWarning?.Count > 0 && FolderSizeConfigDataWarning.TryGetValue(data.Id, out double fsDataSizeWarning)) { - warningThreshold = FolderSizeConfigDataWarning[data.Id]; + warningThreshold = fsDataSizeWarning; } ProcessResourceDataReportHealth( @@ -597,16 +556,6 @@ in FabricObserver.Extensibility project. */ } token.ThrowIfCancellationRequested(); - - // This section only needs to run if you have the FabricObserverWebApi app installed. - if (!IsObserverWebApiAppDeployed) - { - return Task.CompletedTask; - } - - var diskInfoPath = Path.Combine(ObserverLogger.LogFolderBasePath, "disks.txt"); - _ = ObserverLogger.TryWriteLogFile(diskInfoPath, diskInfo.ToString()); - _ = diskInfo.Clear(); } catch (Exception e) when (e is not (OperationCanceledException or TaskCanceledException)) { @@ -741,5 +690,8 @@ private void CleanUp() } ObserverLogger.LogInfo("Completed CleanUp..."); } + + [GeneratedRegex(@"^%[a-zA-Z0-9_]+%")] + private static partial Regex EnvRegex(); } } \ No newline at end of file diff --git a/FabricObserver/Observers/FabricSystemObserver.cs b/FabricObserver/Observers/FabricSystemObserver.cs index cdb4ec44..4aa861b9 100644 --- a/FabricObserver/Observers/FabricSystemObserver.cs +++ b/FabricObserver/Observers/FabricSystemObserver.cs @@ -7,10 +7,8 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using System.Diagnostics.Eventing.Reader; using System.Fabric; using System.Fabric.Health; -using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -32,7 +30,7 @@ public sealed class FabricSystemObserver : ObserverBase private readonly string[] processNameWatchList; private Stopwatch stopwatch; private bool checkPrivateWorkingSet; - private List<(string procName, int procId)> fabricSystemProcInfo = new(); + private List<(string procName, int procId)> fabricSystemProcInfo = []; // Health Report data container - For use in analysis to determine health state. private Dictionary> allCpuData; @@ -43,10 +41,6 @@ public sealed class FabricSystemObserver : ObserverBase private Dictionary> allThreadsData; private Dictionary> allAppKvsLvidsData; - // Windows only. (EventLog). - private List evtRecordList = null; - private bool monitorWinEventLog; - /// /// Creates a new instance of the type. /// @@ -56,8 +50,8 @@ public FabricSystemObserver(StatelessServiceContext context) : base(null, contex // Linux if (!IsWindows) { - processNameWatchList = new[] - { + processNameWatchList = + [ "Fabric", "FabricDCA.dll", "FabricDnsService", @@ -68,13 +62,13 @@ public FabricSystemObserver(StatelessServiceContext context) : base(null, contex "FabricIS.dll", "FabricRM.exe", "FabricUS.dll" - }; + ]; } else { // Windows - processNameWatchList = new[] - { + processNameWatchList = + [ "EventStore.Service", "Fabric", "FabricApplicationGateway", @@ -87,7 +81,7 @@ public FabricSystemObserver(StatelessServiceContext context) : base(null, contex "FabricHost", "FabricIS", "FabricRM" - }; + ]; } } @@ -204,11 +198,6 @@ public override async Task ObserveAsync(CancellationToken token) throw; } - if (IsWindows && IsObserverWebApiAppDeployed && monitorWinEventLog) - { - ReadServiceFabricWindowsEventLog(); - } - await ReportAsync(token); // The time it took to run this observer to completion. @@ -356,81 +345,6 @@ public override Task ReportAsync(CancellationToken token) { return Task.CompletedTask; } - - // OBSOLETE: Windows Event Log - if (IsWindows && IsObserverWebApiAppDeployed && monitorWinEventLog) - { - // SF Eventlog Errors? - // Write this out to a new file, for use by the web front end log viewer. - // Format = HTML. - int count = evtRecordList.Count; - var logPath = Path.Combine(ObserverLogger.LogFolderBasePath, "EventVwrErrors.txt"); - - // Remove existing file. - if (File.Exists(logPath)) - { - try - { - File.Delete(logPath); - } - catch (Exception e) when (e is ArgumentException or IOException or UnauthorizedAccessException) - { - - } - } - - if (count >= 10) - { - var sb = new StringBuilder(); - - _ = sb.AppendLine("
"); - - _ = sb.AppendLine("
"); - - foreach (var evt in evtRecordList.Distinct()) - { - token.ThrowIfCancellationRequested(); - - try - { - // Access event properties: - _ = sb.AppendLine("
" + evt.LogName + "
"); - _ = sb.AppendLine("
" + evt.LevelDisplayName + "
"); - if (evt.TimeCreated.HasValue) - { - _ = sb.AppendLine("
" + evt.TimeCreated.Value.ToShortDateString() + "
"); - } - - foreach (var prop in evt.Properties) - { - if (prop.Value != null && Convert.ToString(prop.Value).Length > 0) - { - _ = sb.AppendLine("
" + prop.Value + "
"); - } - } - } - catch (EventLogException) - { - - } - } - - _ = sb.AppendLine("
"); - - _ = ObserverLogger.TryWriteLogFile(logPath, sb.ToString()); - _ = sb.Clear(); - } - - // Clean up. - if (count > 0) - { - evtRecordList.Clear(); - } - } } catch (Exception e) when (e is not (OperationCanceledException or TaskCanceledException)) { @@ -443,82 +357,6 @@ public override Task ReportAsync(CancellationToken token) return Task.CompletedTask; } - private void ReadServiceFabricWindowsEventLog() - { - if (!IsWindows) - { - return; - } - - string sfOperationalLogSource = "Microsoft-ServiceFabric/Operational"; - string sfAdminLogSource = "Microsoft-ServiceFabric/Admin"; - string systemLogSource = "System"; - string sfLeaseAdminLogSource = "Microsoft-ServiceFabric-Lease/Admin"; - string sfLeaseOperationalLogSource = "Microsoft-ServiceFabric-Lease/Operational"; - - var range2Days = DateTime.UtcNow.AddDays(-1); - var format = range2Days.ToString("yyyy-MM-ddTHH:mm:ss.fffffff00K", CultureInfo.InvariantCulture); - var datexQuery = $"*[System/TimeCreated/@SystemTime >='{format}']"; - - // Critical and Errors only. - string xQuery = "*[System/Level <= 2] and " + datexQuery; - - // SF Admin Event Store. - var evtLogQuery = new EventLogQuery(sfAdminLogSource, PathType.LogName, xQuery); - using (var evtLogReader = new EventLogReader(evtLogQuery)) - { - for (var eventInstance = evtLogReader.ReadEvent(); eventInstance != null; eventInstance = evtLogReader.ReadEvent()) - { - Token.ThrowIfCancellationRequested(); - evtRecordList.Add(eventInstance); - } - } - - // SF Operational Event Store. - evtLogQuery = new EventLogQuery(sfOperationalLogSource, PathType.LogName, xQuery); - using (var evtLogReader = new EventLogReader(evtLogQuery)) - { - for (var eventInstance = evtLogReader.ReadEvent(); eventInstance != null; eventInstance = evtLogReader.ReadEvent()) - { - Token.ThrowIfCancellationRequested(); - evtRecordList.Add(eventInstance); - } - } - - // SF Lease Admin Event Store. - evtLogQuery = new EventLogQuery(sfLeaseAdminLogSource, PathType.LogName, xQuery); - using (var evtLogReader = new EventLogReader(evtLogQuery)) - { - for (var eventInstance = evtLogReader.ReadEvent(); eventInstance != null; eventInstance = evtLogReader.ReadEvent()) - { - Token.ThrowIfCancellationRequested(); - evtRecordList.Add(eventInstance); - } - } - - // SF Lease Operational Event Store. - evtLogQuery = new EventLogQuery(sfLeaseOperationalLogSource, PathType.LogName, xQuery); - using (var evtLogReader = new EventLogReader(evtLogQuery)) - { - for (var eventInstance = evtLogReader.ReadEvent(); eventInstance != null; eventInstance = evtLogReader.ReadEvent()) - { - Token.ThrowIfCancellationRequested(); - evtRecordList.Add(eventInstance); - } - } - - // System Event Store. - evtLogQuery = new EventLogQuery(systemLogSource, PathType.LogName, xQuery); - using (var evtLogReader = new EventLogReader(evtLogQuery)) - { - for (var eventInstance = evtLogReader.ReadEvent(); eventInstance != null; eventInstance = evtLogReader.ReadEvent()) - { - Token.ThrowIfCancellationRequested(); - evtRecordList.Add(eventInstance); - } - } - } - private Process[] GetDotnetLinuxProcessesByFirstArgument(string argument) { if (IsWindows) @@ -545,7 +383,7 @@ private Process[] GetDotnetLinuxProcessesByFirstArgument(string argument) if (cmdline.Contains(sfAppDir)) { - string bin = cmdline[(cmdline.LastIndexOf("/", StringComparison.Ordinal) + 1)..]; + string bin = cmdline[(cmdline.LastIndexOf('/') + 1)..]; if (string.Equals(argument, bin, StringComparison.InvariantCulture)) { @@ -602,7 +440,7 @@ private void Initialize() // CPU data if (allCpuData == null && (CpuErrorUsageThresholdPct > 0 || CpuWarnUsageThresholdPct > 0)) { - allCpuData = new Dictionary>(); + allCpuData = []; foreach (var proc in processNameWatchList) { @@ -614,7 +452,7 @@ private void Initialize() // Memory data if (allMemData == null && (MemErrorUsageThresholdMb > 0 || MemWarnUsageThresholdMb > 0)) { - allMemData = new Dictionary>(); + allMemData = []; foreach (var proc in processNameWatchList) { @@ -626,7 +464,7 @@ private void Initialize() // Ports if (allActiveTcpPortData == null && (ActiveTcpPortCountError > 0 || ActiveTcpPortCountWarning > 0)) { - allActiveTcpPortData = new Dictionary>(); + allActiveTcpPortData = []; foreach (var proc in processNameWatchList) { @@ -637,7 +475,7 @@ private void Initialize() if (allEphemeralTcpPortData == null && (ActiveEphemeralPortCountError > 0 || ActiveEphemeralPortCountWarning > 0)) { - allEphemeralTcpPortData = new Dictionary>(); + allEphemeralTcpPortData = []; foreach (var proc in processNameWatchList) { @@ -649,7 +487,7 @@ private void Initialize() // Handles if (allHandlesData == null && (AllocatedHandlesError > 0 || AllocatedHandlesWarning > 0)) { - allHandlesData = new Dictionary>(); + allHandlesData = []; foreach (var proc in processNameWatchList) { @@ -661,7 +499,7 @@ private void Initialize() // Threads if (allThreadsData == null && (ThreadCountError > 0 || ThreadCountWarning > 0)) { - allThreadsData = new Dictionary>(); + allThreadsData = []; foreach (var proc in processNameWatchList) { @@ -673,7 +511,7 @@ private void Initialize() // KVS LVIDs - Windows-only (EnableKvsLvidMonitoring will always be false otherwise) if (EnableKvsLvidMonitoring && allAppKvsLvidsData == null) { - allAppKvsLvidsData = new Dictionary>(); + allAppKvsLvidsData = []; foreach (var proc in processNameWatchList) { @@ -688,11 +526,6 @@ private void Initialize() proc, new FabricResourceUsageData(ErrorWarningProperty.KvsLvidsPercent, proc, frudCapacity, UseCircularBuffer)); } } - - if (IsWindows && monitorWinEventLog && evtRecordList == null) - { - evtRecordList = new List(); - } } private void SetThresholdSFromConfiguration() @@ -849,20 +682,6 @@ private void SetThresholdSFromConfiguration() // Observers that monitor LVIDs should ensure the static ObserverManager.CanInstallLvidCounter is true before attempting to monitor LVID usage. EnableKvsLvidMonitoring = enableLvidMonitoring && ObserverManager.IsLvidCounterEnabled; } - - // Monitor Windows event log for SF and System Error/Critical events? - // This can be noisy. Use wisely. Return if running on Linux. - if (!IsWindows) - { - return; - } - - var watchEvtLog = GetSettingParameterValue(ConfigurationSectionName, ObserverConstants.FabricSystemObserverMonitorWindowsEventLog); - - if (!string.IsNullOrWhiteSpace(watchEvtLog) && bool.TryParse(watchEvtLog, out bool watchEl)) - { - monitorWinEventLog = watchEl; - } } private async Task GetProcessInfoAsync(string procName, CancellationToken token) @@ -937,9 +756,9 @@ private async Task GetProcessInfoAsync(string procName, CancellationToken token) if (ActiveTcpPortCountError > 0 || ActiveTcpPortCountWarning > 0) { - if (allActiveTcpPortData.ContainsKey(dotnetArg)) + if (allActiveTcpPortData.TryGetValue(dotnetArg, out FabricResourceUsageData tcpPortsFrud)) { - allActiveTcpPortData[dotnetArg].AddData(activePortCount); + tcpPortsFrud.AddData(activePortCount); } } @@ -949,9 +768,9 @@ private async Task GetProcessInfoAsync(string procName, CancellationToken token) if (ActiveEphemeralPortCountError > 0 || ActiveEphemeralPortCountWarning > 0) { - if (allEphemeralTcpPortData.ContainsKey(dotnetArg)) + if (allEphemeralTcpPortData.TryGetValue(dotnetArg, out FabricResourceUsageData ePortsFrud)) { - allEphemeralTcpPortData[dotnetArg].AddData(activeEphemeralPortCount); + ePortsFrud.AddData(activeEphemeralPortCount); } } @@ -994,18 +813,18 @@ private async Task GetProcessInfoAsync(string procName, CancellationToken token) // Handles/FDs if (AllocatedHandlesError > 0 || AllocatedHandlesWarning > 0) { - if (allHandlesData.ContainsKey(dotnetArg)) + if (allHandlesData.TryGetValue(dotnetArg, out FabricResourceUsageData handlesFrud)) { - allHandlesData[dotnetArg].AddData(handles); + handlesFrud.AddData(handles); } } // Threads if (ThreadCountError > 0 || ThreadCountWarning > 0) { - if (allThreadsData.ContainsKey(dotnetArg)) + if (allThreadsData.TryGetValue(dotnetArg, out FabricResourceUsageData threadsFrud)) { - allThreadsData[dotnetArg].AddData(threads); + threadsFrud.AddData(threads); } } @@ -1017,9 +836,9 @@ private async Task GetProcessInfoAsync(string procName, CancellationToken token) // GetProcessKvsLvidsUsedPercentage internally handles exceptions and will always return -1 when it fails. if (lvidPct > -1) { - if (allAppKvsLvidsData.ContainsKey(dotnetArg)) + if (allAppKvsLvidsData.TryGetValue(dotnetArg, out FabricResourceUsageData lvidsFrud)) { - allAppKvsLvidsData[dotnetArg].AddData(lvidPct); + lvidsFrud.AddData(lvidPct); } } } @@ -1029,9 +848,9 @@ private async Task GetProcessInfoAsync(string procName, CancellationToken token) { float processMem = ProcessInfoProvider.Instance.GetProcessWorkingSetMb(procId, dotnetArg, token, checkPrivateWorkingSet); - if (allMemData.ContainsKey(dotnetArg)) + if (allMemData.TryGetValue(dotnetArg, out FabricResourceUsageData memMbFrud)) { - allMemData[dotnetArg].AddData(processMem); + memMbFrud.AddData(processMem); } } @@ -1073,9 +892,9 @@ private async Task GetProcessInfoAsync(string procName, CancellationToken token) // process is no longer running if cpu == -1. if (cpu >= 0) { - if (allCpuData.ContainsKey(dotnetArg)) + if (allCpuData.TryGetValue(dotnetArg, out FabricResourceUsageData cpuFrud)) { - allCpuData[dotnetArg].AddData(cpu); + cpuFrud.AddData(cpu); } } diff --git a/FabricObserver/Observers/NetworkObserver.cs b/FabricObserver/Observers/NetworkObserver.cs index b9a52676..bc267d46 100644 --- a/FabricObserver/Observers/NetworkObserver.cs +++ b/FabricObserver/Observers/NetworkObserver.cs @@ -12,9 +12,7 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Net.NetworkInformation; using System.Net.Sockets; -using System.Text; using System.Threading; using System.Threading.Tasks; using FabricObserver.Observers.MachineInfoModel; @@ -31,16 +29,20 @@ namespace FabricObserver.Observers /// The output (a local file) is used by the API service and the HTML frontend (https://[domain:[port]]/api/ObserverManager). /// Health Report processor will also emit diagnostic telemetry if configured in Settings.xml. ///
- public sealed class NetworkObserver : ObserverBase + /// + /// Creates a new instance of the type. + /// + /// The StatelessServiceContext instance. + public sealed class NetworkObserver(StatelessServiceContext context) : ObserverBase(null, context) { private const int MaxTcpConnTestRetries = 5; - private readonly List defaultConfig = new() - { + private readonly List defaultConfig = + [ new NetworkObserverConfig { TargetApp = "fabric:/test", - Endpoints = new List - { + Endpoints = + [ new() { HostName = "www.microsoft.com", Port = 443 @@ -53,26 +55,16 @@ public sealed class NetworkObserver : ObserverBase HostName = "www.google.com", Port = 443 } - } + ] } - }; - private readonly List userConfig = new(); - private readonly List connectionStatus = new(); - private readonly Dictionary connEndpointTestResults = new(); - private readonly Stopwatch stopwatch; + ]; + private readonly List userConfig = []; + private readonly List connectionStatus = []; + private readonly Dictionary connEndpointTestResults = []; + private readonly Stopwatch stopwatch = new(); private HealthState healthState = HealthState.Ok; - private bool hasRun; private int tcpConnTestRetried; - /// - /// Creates a new instance of the type. - /// - /// The StatelessServiceContext instance. - public NetworkObserver(StatelessServiceContext context) : base(null, context) - { - stopwatch = new Stopwatch(); - } - public override async Task ObserveAsync(CancellationToken token) { // If set, this observer will only run during the supplied interval. @@ -113,7 +105,6 @@ public override async Task ObserveAsync(CancellationToken token) stopwatch.Reset(); LastRunDateTime = DateTime.Now; - hasRun = true; } public override Task ReportAsync(CancellationToken token) @@ -168,7 +159,7 @@ public override Task ReportAsync(CancellationToken token) AppName = new Uri(conn.TargetApp), Code = FOErrorWarningCodes.AppWarningNetworkEndpointUnreachable, EntityType = EntityType.Application, - EmitLogEvent = EnableVerboseLogging || IsObserverWebApiAppDeployed, + EmitLogEvent = EnableVerboseLogging, HealthData = telemetryData, HealthMessage = healthMessage, HealthReportTimeToLive = timeToLiveWarning, @@ -201,7 +192,7 @@ public override Task ReportAsync(CancellationToken token) { AppName = new Uri(conn.TargetApp), Code = FOErrorWarningCodes.AppWarningNetworkEndpointUnreachable, - EmitLogEvent = EnableVerboseLogging || IsObserverWebApiAppDeployed, + EmitLogEvent = EnableVerboseLogging, HealthMessage = healthMessage, HealthReportTimeToLive = default, SourceId = $"{ObserverConstants.NetworkObserverName}({FOErrorWarningCodes.AppWarningNetworkEndpointUnreachable})", @@ -255,74 +246,10 @@ public override Task ReportAsync(CancellationToken token) return Task.CompletedTask; } - private static string GetNetworkInterfaceInfo(CancellationToken token) - { - try - { - var iPGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); - var nics = NetworkInterface.GetAllNetworkInterfaces(); - - if (nics.Length < 1) - { - return string.Empty; - } - - var interfaceInfo = new StringBuilder($"Network Interface information for {iPGlobalProperties.HostName}:{Environment.NewLine} "); - - foreach (var nic in nics) - { - token.ThrowIfCancellationRequested(); - - _ = interfaceInfo.Append($"{Environment.NewLine}{nic.Description}{Environment.NewLine}"); - _ = interfaceInfo.AppendFormat($" Interface type : {0}{Environment.NewLine}", nic.NetworkInterfaceType); - _ = interfaceInfo.AppendFormat($" Operational status: {0}{Environment.NewLine}", nic.OperationalStatus); - - // Traffic. - if (nic.OperationalStatus != OperationalStatus.Up) - { - continue; - } - - _ = interfaceInfo.AppendLine(" Traffic Info:"); - - var stats = nic.GetIPv4Statistics(); - - _ = interfaceInfo.AppendFormat($" Bytes received: {0}{Environment.NewLine}", stats.BytesReceived); - _ = interfaceInfo.AppendFormat($" Bytes sent: {0}{Environment.NewLine}", stats.BytesSent); - _ = interfaceInfo.AppendFormat($" Incoming Packets With Errors: {0}{Environment.NewLine}", stats.IncomingPacketsWithErrors); - _ = interfaceInfo.AppendFormat($" Outgoing Packets With Errors: {0}{Environment.NewLine}", stats.OutgoingPacketsWithErrors); - _ = interfaceInfo.AppendLine(); - } - - var s = interfaceInfo.ToString(); - _ = interfaceInfo.Clear(); - - return s; - } - catch (NetworkInformationException) - { - return string.Empty; - } - } - private async Task InitializeAsync() { Token.ThrowIfCancellationRequested(); - // This only needs to be logged once. - // This file is used by the ObserverWebApi application. - if (IsObserverWebApiAppDeployed && !hasRun) - { - var logPath = Path.Combine(ObserverLogger.LogFolderBasePath, "NetInfo.txt"); - - Console.WriteLine($"logPath: {logPath}"); - - if (!ObserverLogger.TryWriteLogFile(logPath, GetNetworkInterfaceInfo(Token))) - { - ObserverLogger.LogWarning("Unable to create NetInfo.txt file."); - } - } - var networkObserverConfigFileName = Path.Combine(ConfigPackage.Path, GetSettingParameterValue(ConfigurationSectionName, ObserverConstants.ConfigurationFileNameParameter)); @@ -389,9 +316,9 @@ private void InternetConnectionStateIsConnected() } // Don't re-test endpoint if it has already been tested for a different targetApp. - if (connEndpointTestResults.ContainsKey(endpoint.HostName)) + if (connEndpointTestResults.TryGetValue(endpoint.HostName, out bool value)) { - SetHealthState(endpoint, config.TargetApp, connEndpointTestResults[endpoint.HostName]); + SetHealthState(endpoint, config.TargetApp, value); continue; } @@ -453,11 +380,7 @@ private void InternetConnectionStateIsConnected() } SetHealthState(endpoint, config.TargetApp, passed); - - if (!connEndpointTestResults.ContainsKey(endpoint.HostName)) - { - connEndpointTestResults.Add(endpoint.HostName, passed); - } + _ = connEndpointTestResults.TryAdd(endpoint.HostName, passed); } } } diff --git a/FabricObserver/Observers/NodeObserver.cs b/FabricObserver/Observers/NodeObserver.cs index d44587f0..bf1b3586 100644 --- a/FabricObserver/Observers/NodeObserver.cs +++ b/FabricObserver/Observers/NodeObserver.cs @@ -16,9 +16,13 @@ namespace FabricObserver.Observers { // This observer monitors machine level resource usage across CPU, Memory, TCP ports, (Linux) File handles, and (Windows) firewall rules. - public sealed class NodeObserver : ObserverBase + /// + /// Creates a new instance of the type. + /// + /// The StatelessServiceContext instance. + public sealed class NodeObserver(StatelessServiceContext context) : ObserverBase(null, context) { - private readonly Stopwatch stopwatch; + private readonly Stopwatch stopwatch = new(); // These are public properties because they are used in unit tests. public FabricResourceUsageData MemDataInUse; @@ -132,15 +136,6 @@ public bool EnableNodeSnapshots get; set; } - /// - /// Creates a new instance of the type. - /// - /// The StatelessServiceContext instance. - public NodeObserver(StatelessServiceContext context) : base(null, context) - { - stopwatch = new Stopwatch(); - } - public override async Task ObserveAsync(CancellationToken token) { // If set, this observer will only run during the supplied interval. @@ -331,7 +326,7 @@ public override async Task ReportAsync(CancellationToken token) } // Windows Firewall Rules - Total number of rules in use. - if (FirewallData != null && IsWindows && (FirewallRulesErrorThreshold > 0 || FirewallRulesWarningThreshold > 0)) + if (IsWindows && FirewallData != null && (FirewallRulesErrorThreshold > 0 || FirewallRulesWarningThreshold > 0)) { ProcessResourceDataReportHealth( FirewallData, @@ -452,7 +447,7 @@ private void InitializeDataContainers() ErrorWarningProperty.MemoryConsumptionPercentage, ErrorWarningProperty.MemoryConsumptionPercentage.Replace(" ", string.Empty), frudCapacity, UseCircularBuffer); } - if (FirewallData == null && (FirewallRulesErrorThreshold > 0 || FirewallRulesWarningThreshold > 0)) + if (IsWindows && FirewallData == null && (FirewallRulesErrorThreshold > 0 || FirewallRulesWarningThreshold > 0)) { FirewallData = new FabricResourceUsageData( ErrorWarningProperty.ActiveFirewallRules, ErrorWarningProperty.ActiveFirewallRules.Replace(" ", string.Empty), 1); @@ -688,10 +683,10 @@ private async Task ComputeMachineResourceUsage(CancellationToken token) // Firewall rules. if (IsWindows && FirewallData != null) { - int firewalls = NetworkUsage.GetActiveFirewallRulesCount(); + int firewalls = OSInfoProvider.Instance.GetActiveFirewallRulesCount(); FirewallData.AddData(firewalls); } - + // OS-level file handle monitoring only makes sense for Linux, where the Maximum system-wide number of handles the kernel will allocate is a user-configurable setting. // Windows does not have a configurable setting for Max Handles as the number of handles available to the system is dynamic (even if the max per process is not). // As such, for Windows, GetMaximumConfiguredFileHandlesCount always return -1, by design. Also, GetTotalAllocatedFileHandlesCount is not implemented for Windows (just returns -1). diff --git a/FabricObserver/Observers/OSObserver.cs b/FabricObserver/Observers/OSObserver.cs index d2140f16..639241d4 100644 --- a/FabricObserver/Observers/OSObserver.cs +++ b/FabricObserver/Observers/OSObserver.cs @@ -10,8 +10,6 @@ using System.Fabric.Health; using System.Fabric.Query; using System.IO; -using System.Linq; -using System.Management; using System.Runtime.InteropServices; using System.Security; using System.Text; @@ -26,8 +24,6 @@ namespace FabricObserver.Observers { // This observer monitors OS health state and provides static and dynamic OS level information. // It will signal Ok Health Reports that will show up under node details in SFX as well as emit ETW events. - // If FabricObserverWebApi is installed, the output includes a local file that is used - // by the API service and returns Hardware/OS info as HTML (http://localhost:5000/api/ObserverManager). public sealed class OSObserver : ObserverBase { private const string AuStateUnknownMessage = "Unable to determine Windows AutoUpdate state."; @@ -166,17 +162,6 @@ public override async Task ReportAsync(CancellationToken token) HasActiveFabricErrorOrWarning = false; } - if (IsObserverWebApiAppDeployed) - { - var logPath = Path.Combine(ObserverLogger.LogFolderBasePath, "SysInfo.txt"); - - // This file is used by the web application (log reader.). - if (!ObserverLogger.TryWriteLogFile(logPath, $"Last updated on {DateTime.UtcNow:M/d/yyyy HH:mm:ss} UTC
{osReport}")) - { - ObserverLogger.LogWarning("Unable to create SysInfo.txt file."); - } - } - var report = new HealthReport { Observer = ObserverName, @@ -313,71 +298,7 @@ private bool IsAutoUpdateDownloadCheckEnabled() return !string.IsNullOrEmpty(checkAU) && bool.TryParse(checkAU, out bool auChk) && auChk; } - private string GetWindowsHotFixes(bool generateKbUrl, CancellationToken token) - { - if (!IsWindows) - { - return null; - } - - ManagementObject[] resultsOrdered; - string ret = string.Empty; - - token.ThrowIfCancellationRequested(); - - try - { - using var searcher = new ManagementObjectSearcher("SELECT HotFixID,InstalledOn FROM Win32_QuickFixEngineering"); - var results = searcher.Get(); - - if (results.Count < 1) - { - return string.Empty; - } - - resultsOrdered = results.Cast() - .Where(obj => obj["InstalledOn"] != null && obj["InstalledOn"].ToString() != string.Empty) - .OrderByDescending(obj => DateTime.Parse(obj["InstalledOn"].ToString() ?? string.Empty)).ToArray(); - - var sb = new StringBuilder(); - var baseUrl = "https://support.microsoft.com/help/"; - - for (int i = 0; i < resultsOrdered.Length; ++i) - { - token.ThrowIfCancellationRequested(); - - ManagementObject obj = resultsOrdered[i]; - - try - { - _ = generateKbUrl ? sb.AppendLine( - $"{obj["HotFixID"]} " + - $"{obj["InstalledOn"]}") : sb.AppendLine($"{obj["HotFixID"]}"); - } - catch (ArgumentException) - { - - } - finally - { - obj?.Dispose(); - obj = null; - } - } - - resultsOrdered = null; - ret = sb.ToString().Trim(); - _ = sb.Clear(); - sb = null; - - } - catch (Exception e) when (e is not OutOfMemoryException) - { - ObserverLogger.LogWarning($"Unhandled Exception processing OS information: {e.Message}"); - } - - return ret; - } + private bool CheckWuAutoDownloadEnabled() { @@ -428,8 +349,8 @@ private async Task GetComputerInfoAsync(CancellationToken token) : await FabricClientInstance.ClusterManager.GetClusterManifestAsync(AsyncClusterOperationTimeoutSeconds, Token); (int lowPortApp, int highPortApp) = NetworkUsage.TupleGetFabricApplicationPortRangeForNodeType(NodeType, clusterManifestXml); - int firewalls = NetworkUsage.GetActiveFirewallRulesCount(); - + int firewalls = OSInfoProvider.Instance.GetActiveFirewallRulesCount(); + // OS info. _ = sb.AppendLine($"OS Information:{Environment.NewLine}"); _ = sb.AppendLine($"Name: {osInfo.Name}"); @@ -552,7 +473,7 @@ private async Task GetComputerInfoAsync(CancellationToken token) if (IsWindows) { - osHotFixes = GetWindowsHotFixes(true, token); + osHotFixes = OSInfoProvider.Instance.GetOSHotFixes(true, token); if (!string.IsNullOrWhiteSpace(osHotFixes)) { @@ -567,7 +488,7 @@ private async Task GetComputerInfoAsync(CancellationToken token) if (IsWindows) { - kbOnlyHotFixes = GetWindowsHotFixes(false, token)?.Replace($"{Environment.NewLine}", ", ").TrimEnd(','); + kbOnlyHotFixes = OSInfoProvider.Instance.GetOSHotFixes(false, token)?.Replace($"{Environment.NewLine}", ", ").TrimEnd(','); } var machineTelemetry = new MachineTelemetryData diff --git a/FabricObserver/Observers/ObserverManager.cs b/FabricObserver/Observers/ObserverManager.cs index 9587a5f8..10da8bd9 100644 --- a/FabricObserver/Observers/ObserverManager.cs +++ b/FabricObserver/Observers/ObserverManager.cs @@ -23,6 +23,7 @@ using System.Runtime; using FabricObserver.Utilities.ServiceFabric; using ConfigurationSettings = System.Fabric.Description.ConfigurationSettings; +using System.Runtime.Versioning; namespace FabricObserver.Observers { @@ -45,7 +46,6 @@ private List Observers private readonly TimeSpan NewReleaseCheckInterval = TimeSpan.FromDays(7); private readonly CancellationToken runAsyncToken; private readonly string sfVersion; - private readonly bool isWindows; private readonly ConfigurationPackage configurationPackage; private volatile bool shutdownSignaled; private DateTime StartDateTime; @@ -54,7 +54,7 @@ private List Observers private CancellationTokenSource linkedSFRuntimeObserverTokenSource; // Folks often use their own version numbers. This is for internal diagnostic telemetry. - private const string InternalVersionNumber = "3.2.15"; + private const string InternalVersionNumber = "3.3.0"; private static FabricClient FabricClientInstance => FabricClientUtilities.FabricClientSingleton; @@ -76,11 +76,6 @@ private ObserverHealthReporter HealthReporter get; } - private string Fqdn - { - get; set; - } - private Logger Logger { get; @@ -116,11 +111,6 @@ public static bool IsLvidCounterEnabled get; set; } - public static bool ObserverWebAppDeployed - { - get; set; - } - public static bool EtwEnabled { get; set; @@ -155,7 +145,6 @@ public ObserverManager(IServiceProvider serviceProvider, CancellationToken token linkedSFRuntimeObserverTokenSource.Token.Register(() => Logger.LogWarning("linkedSFRuntimeObserverTokenSource.Token token cancellation signalled.")); #endif nodeName = FabricServiceContext.NodeContext.NodeName; - isWindows = OperatingSystem.IsWindows(); sfVersion = GetServiceFabricRuntimeVersion(); configurationPackage = FabricServiceContext.CodePackageActivationContext.GetConfigurationPackageObject("Config"); @@ -697,27 +686,6 @@ private async Task RemoveObserverManagerHealthReportsAsync() } } - private bool IsObserverWebApiAppInstalled() - { - try - { - var deployedObsWebApps = - FabricClientInstance.QueryManager.GetDeployedApplicationListAsync( - nodeName, - new Uri("fabric:/FabricObserverWebApi"), - TimeSpan.FromSeconds(30), - runAsyncToken).GetAwaiter().GetResult(); - - return deployedObsWebApps?.Count > 0; - } - catch (Exception e) when (e is FabricException or TaskCanceledException or TimeoutException) - { - - } - - return false; - } - private string GetConfigSettingValue(string parameterName, ConfigurationSettings settings, string sectionName = null) { try @@ -812,8 +780,8 @@ private Dictionary GetObserverData() { var observerData = new Dictionary(); var enabledObs = Observers.Where(o => o.IsEnabled); - string[] builtInObservers = new string[] - { + string[] builtInObservers = + [ ObserverConstants.AppObserverName, ObserverConstants.AzureStorageUploadObserverName, ObserverConstants.CertificateObserverName, @@ -822,9 +790,8 @@ private Dictionary GetObserverData() ObserverConstants.FabricSystemObserverName, ObserverConstants.NetworkObserverName, ObserverConstants.NodeObserverName, - ObserverConstants.OSObserverName, - ObserverConstants.SFConfigurationObserverName - }; + ObserverConstants.OSObserverName + ]; foreach (var obs in enabledObs) { @@ -840,7 +807,7 @@ ObserverConstants.ContainerObserverName or ObserverConstants.NetworkObserverName or ObserverConstants.FabricSystemObserverName) { - if (!observerData.ContainsKey(obs.ObserverName)) + if (!observerData.TryGetValue(obs.ObserverName, out ObserverData value)) { _ = observerData.TryAdd( obs.ObserverName, @@ -857,9 +824,9 @@ ObserverConstants.NetworkObserverName or } else { - observerData[obs.ObserverName].ErrorCount = obs.CurrentErrorCount; - observerData[obs.ObserverName].WarningCount = obs.CurrentWarningCount; - observerData[obs.ObserverName].ServiceData = + value.ErrorCount = obs.CurrentErrorCount; + value.WarningCount = obs.CurrentWarningCount; + value.ServiceData = new ServiceData { MonitoredAppCount = obs.MonitoredAppCount, @@ -917,7 +884,7 @@ private async void CodePackageActivationContext_ConfigurationPackageModifiedEven // For Linux, we need to restart the FO process due to the Linux Capabilities impl that enables us to run docker and netstat commands as elevated user (FO Linux should always be run as standard user on Linux). // During an upgrade event, SF touches the cap binaries which removes the cap settings so we need to run the FO app setup script again to reset them. - if (!isWindows) + if (OperatingSystem.IsLinux()) { // Graceful stop. await StopObserversAsync(true, true).ConfigureAwait(false); @@ -980,8 +947,8 @@ private void SetPropertiesFromConfigurationParameters(ConfigurationSettings sett { ApplicationName = FabricServiceContext.CodePackageActivationContext.ApplicationName; - // LVID monitoring. - if (isWindows) + // LVID monitoring. Windows-only feature. + if (OperatingSystem.IsWindows()) { IsLvidCounterEnabled = IsLVIDPerfCounterEnabled(settings); } @@ -1029,24 +996,12 @@ private void SetPropertiesFromConfigurationParameters(ConfigurationSettings sett ObserverExecutionLoopSleepSeconds = execFrequency; } - // FQDN for use in warning or error hyperlinks in HTML output - // This only makes sense when you have the FabricObserverWebApi app installed. - string fqdn = GetConfigSettingValue(ObserverConstants.Fqdn, settings); - - if (!string.IsNullOrWhiteSpace(fqdn)) - { - Fqdn = fqdn; - } - // FabricObserver operational telemetry (No PII) - Override if (bool.TryParse(GetConfigSettingValue(ObserverConstants.EnableFabricObserverOperationalTelemetry, settings), out bool foTelemEnabled)) { FabricObserverOperationalTelemetryEnabled = foTelemEnabled; } - // ObserverWebApi. - ObserverWebAppDeployed = bool.TryParse(GetConfigSettingValue(ObserverConstants.ObserverWebApiEnabled, settings), out bool obsWeb) && obsWeb && IsObserverWebApiAppInstalled(); - // ObserverFailure HealthState Level - Override \\ string state = GetConfigSettingValue(ObserverConstants.ObserverFailureHealthStateLevelParameter, settings); @@ -1252,38 +1207,6 @@ private async Task RunObserversAsync() } Logger.LogInfo($"Successfully ran {observer.ObserverName}."); - - if (!ObserverWebAppDeployed) - { - continue; - } - - if (observer.HasActiveFabricErrorOrWarning) - { - var errWarnMsg = !string.IsNullOrEmpty(Fqdn) ? $"One or more errors or warnings detected." : $"One or more errors or warnings detected. Check {observer.ObserverName} logs for details."; - Logger.LogWarning($"{observer.ObserverName}: " + errWarnMsg); - } - else - { - // Delete the observer's instance log (local file with Warn/Error details per run).. - _ = observer.ObserverLogger.TryDeleteInstanceLogFile(); - - try - { - if (File.Exists(Logger.FilePath)) - { - // Replace the ObserverManager.log text that doesn't contain the observer Warn/Error line(s). - await File.WriteAllLinesAsync( - Logger.FilePath, - File.ReadLines(Logger.FilePath) - .Where(line => !line.Contains(observer.ObserverName)).ToList(), runAsyncToken); - } - } - catch (IOException) - { - - } - } } catch (AggregateException ae) { @@ -1419,13 +1342,9 @@ private async Task CheckGithubForNewVersionAsync() } } + [SupportedOSPlatform("windows")] private bool IsLVIDPerfCounterEnabled(ConfigurationSettings settings = null) { - if (!isWindows) - { - return false; - } - // We already figured this out the first time this function ran. if (IsLvidCounterEnabled) { @@ -1440,7 +1359,7 @@ private bool IsLVIDPerfCounterEnabled(ConfigurationSettings settings = null) _ = bool.TryParse( GetConfigSettingValue(ObserverConstants.EnableKvsLvidMonitoringParameter, settings, ObserverConstants.FabricSystemObserverConfigurationName), out bool isLvidEnabledFSO); - + // If neither AO nor FSO are configured to monitor LVID usage, then do not proceed; it doesn't matter and this check is not cheap. if (!isLvidEnabledAO && !isLvidEnabledFSO) { @@ -1454,7 +1373,7 @@ private bool IsLVIDPerfCounterEnabled(ConfigurationSettings settings = null) string categoryName = "Windows Fabric Database"; if (sfVersion.StartsWith('1')) - { + { categoryName = "MSExchange Database"; } diff --git a/FabricObserver/Observers/SFConfigurationObserver.cs b/FabricObserver/Observers/SFConfigurationObserver.cs deleted file mode 100644 index 4fb5ca45..00000000 --- a/FabricObserver/Observers/SFConfigurationObserver.cs +++ /dev/null @@ -1,375 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using System.Fabric; -using System.Fabric.Query; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using FabricObserver.Observers.Utilities; - -namespace FabricObserver.Observers -{ - /* NOTE: SFConfigurationObserver observer is Deprecated: it is only useful if you deploy the now-obsolete (and unsupported) FabricObserverWebApi application. - SFConfigurationObserver may change in the future to be more useful, so it has not been deleted for that reason - and for backwards-compatibility. */ - - // SFConfigurationObserver provides information about the currently installed Service Fabric runtime environment, apps, and services. - // The output (a local file) is used by the FO API service to render an HTML page (http://localhost:5000/api/ObserverManager). - public sealed class SFConfigurationObserver : ObserverBase - { - private string SFVersion; - private string SFBinRoot; - private string SFCodePath; - private string SFDataRoot; - private string SFLogRoot; - private string SFNodeLastBootTime; - private string SFCompatibilityJsonPath; - private bool? SFVolumeDiskServiceEnabled; - private bool? unsupportedPreviewFeaturesEnabled; - private bool? SFEnableCircularTraceSession; - - public string SFRootDir - { - get; private set; - } - - public string ClusterManifestPath - { - get; set; - } - - /// - /// Creates a new instance of the type. - /// - /// The StatelessServiceContext instance. - public SFConfigurationObserver(StatelessServiceContext context) : base(null, context) - { - - } - - public override async Task ObserveAsync(CancellationToken token) - { - if (!IsObserverWebApiAppDeployed || (RunInterval > TimeSpan.Zero && DateTime.Now.Subtract(LastRunDateTime) < RunInterval)) - { - ObserverLogger.LogInfo($"ObserveAsync: RunInterval ({RunInterval}) has not elapsed. Exiting."); - return; - } - - if (token.IsCancellationRequested) - { - return; - } - - Token = token; - - try - { - var config = ServiceFabricConfiguration.Instance; - SFVersion = config.FabricVersion; - SFBinRoot = config.FabricBinRoot; - SFCompatibilityJsonPath = config.CompatibilityJsonPath; - SFCodePath = config.FabricCodePath; - SFDataRoot = config.FabricDataRoot; - SFLogRoot = config.FabricLogRoot; - SFRootDir = config.FabricRoot; - SFEnableCircularTraceSession = config.EnableCircularTraceSession; - SFVolumeDiskServiceEnabled = config.IsSFVolumeDiskServiceEnabled; - unsupportedPreviewFeaturesEnabled = config.EnableUnsupportedPreviewFeatures; - SFNodeLastBootTime = config.NodeLastBootUpTime; - - await ReportAsync(token); - } - catch (Exception e) when (e is ArgumentException or IOException) - { - - } - catch (Exception e) when (e is not (OperationCanceledException or TaskCanceledException)) - { - ObserverLogger.LogWarning($"Unhandled Exception in ObserveAsync:{Environment.NewLine}{e}"); - - // Fix the bug.. - throw; - } - - LastRunDateTime = DateTime.Now; - } - - public override async Task ReportAsync(CancellationToken token) - { - token.ThrowIfCancellationRequested(); - - var sb = new StringBuilder(); - _ = sb.AppendLine($"{Environment.NewLine}Service Fabric information:{Environment.NewLine}"); - - if (!string.IsNullOrEmpty(SFVersion)) - { - _ = sb.AppendLine("Runtime Version: " + SFVersion); - } - - if (SFBinRoot != null) - { - _ = sb.AppendLine("Fabric Bin root directory: " + SFBinRoot); - } - - if (SFCodePath != null) - { - _ = sb.AppendLine("Fabric Code Path: " + SFCodePath); - } - - if (!string.IsNullOrEmpty(SFDataRoot)) - { - _ = sb.AppendLine("Data root directory: " + SFDataRoot); - } - - if (!string.IsNullOrEmpty(SFLogRoot)) - { - _ = sb.AppendLine("Log root directory: " + SFLogRoot); - } - - if (SFVolumeDiskServiceEnabled != null) - { - _ = sb.AppendLine("Volume Disk Service Enabled: " + SFVolumeDiskServiceEnabled); - } - - if (unsupportedPreviewFeaturesEnabled != null) - { - _ = sb.AppendLine("Unsupported Preview Features Enabled: " + unsupportedPreviewFeaturesEnabled); - } - - if (SFCompatibilityJsonPath != null) - { - _ = sb.AppendLine("Compatibility Json path: " + SFCompatibilityJsonPath); - } - - if (SFEnableCircularTraceSession != null) - { - _ = sb.AppendLine("Enable Circular trace session: " + SFEnableCircularTraceSession); - } - - _ = sb.Append(await GetDeployedAppsInfoAsync(token)); - _ = sb.AppendLine(); - - token.ThrowIfCancellationRequested(); - - var logPath = Path.Combine(ObserverLogger.LogFolderBasePath, "SFInfraInfo.txt"); - - // This file is used by the web application (ObserverWebApi). - if (!ObserverLogger.TryWriteLogFile(logPath, sb.ToString())) - { - ObserverLogger.LogWarning("Unable to create SFInfraInfo.txt file."); - } - - _ = sb.Clear(); - } - - private async Task GetDeployedAppsInfoAsync(CancellationToken token) - { - token.ThrowIfCancellationRequested(); - - ApplicationList appList = null; - var sb = new StringBuilder(); - string clusterManifestXml = null; - - try - { - appList = await FabricClientInstance.QueryManager.GetApplicationListAsync(); - - if (!string.IsNullOrWhiteSpace(ClusterManifestPath)) - { - clusterManifestXml = await File.ReadAllTextAsync(ClusterManifestPath, token); - } - else - { - clusterManifestXml = await FabricClientInstance.ClusterManager.GetClusterManifestAsync(AsyncClusterOperationTimeoutSeconds, Token); - } - } - catch (Exception e) when (e is FabricException or TimeoutException) - { - - } - - token.ThrowIfCancellationRequested(); - - XmlReader xreader = null; - XmlDocument xdoc = null; - XmlNamespaceManager nsmgr = null; - StringReader sreader = null; - string ret; - - try - { - if (clusterManifestXml != null) - { - // Safe XML pattern - *Do not use LoadXml*. - xdoc = new XmlDocument { XmlResolver = null }; - sreader = new StringReader(clusterManifestXml); - xreader = XmlReader.Create(sreader, new XmlReaderSettings { XmlResolver = null }); - xdoc.Load(xreader); - - // Cluster Information. - nsmgr = new XmlNamespaceManager(xdoc.NameTable); - nsmgr.AddNamespace("sf", "http://schemas.microsoft.com/2011/01/fabric"); - - // Failover Manager. - var fMparameterNodes = xdoc.SelectNodes("//sf:Section[@Name='FailoverManager']//sf:Parameter", nsmgr); - _ = sb.AppendLine("\nCluster Information:\n"); - - if (fMparameterNodes != null) - { - foreach (XmlNode node in fMparameterNodes) - { - token.ThrowIfCancellationRequested(); - - _ = sb.AppendLine(node?.Attributes?.Item(0).Value + ": " + node?.Attributes?.Item(1).Value); - } - } - } - - token.ThrowIfCancellationRequested(); - - // Node Information. - _ = sb.AppendLine($"{Environment.NewLine}Node Info:{Environment.NewLine}"); - _ = sb.AppendLine($"Node Name: {NodeName}"); - _ = sb.AppendLine($"Node Type: {NodeType}"); - var (lowPort, highPort) = NetworkUsage.TupleGetFabricApplicationPortRangeForNodeType(NodeType, clusterManifestXml); - - if (lowPort > -1) - { - _ = sb.AppendLine($"Application Port Range: {lowPort} - {highPort}"); - } - - var infraNode = xdoc?.SelectSingleNode("//sf:Node", nsmgr); - - if (infraNode != null) - { - _ = sb.AppendLine("Is Seed Node: " + infraNode.Attributes?["IsSeedNode"]?.Value); - _ = sb.AppendLine("Fault Domain: " + infraNode.Attributes?["FaultDomain"]?.Value); - _ = sb.AppendLine("Upgrade Domain: " + infraNode.Attributes?["UpgradeDomain"]?.Value); - } - - token.ThrowIfCancellationRequested(); - - if (!string.IsNullOrEmpty(SFNodeLastBootTime)) - { - _ = sb.AppendLine("Last Rebooted: " + SFNodeLastBootTime); - } - - // Application Info. - if (appList != null) - { - _ = sb.AppendLine($"{Environment.NewLine}Deployed Apps:{Environment.NewLine}"); - - foreach (var app in appList) - { - token.ThrowIfCancellationRequested(); - - var appName = app.ApplicationName.OriginalString; - var appType = app.ApplicationTypeName; - var appVersion = app.ApplicationTypeVersion; - var healthState = app.HealthState.ToString(); - var status = app.ApplicationStatus.ToString(); - - _ = sb.AppendLine("Application Name: " + appName); - _ = sb.AppendLine("Type: " + appType); - _ = sb.AppendLine("Version: " + appVersion); - _ = sb.AppendLine("Health state: " + healthState); - _ = sb.AppendLine("Status: " + status); - - // Service(s). - _ = sb.AppendLine($"{Environment.NewLine}\tServices:"); - var serviceList = await FabricClientInstance.QueryManager.GetServiceListAsync(app.ApplicationName); - var replicaList = await FabricClientInstance.QueryManager.GetDeployedReplicaListAsync(NodeName, app.ApplicationName); - - foreach (var service in serviceList) - { - var kind = service.ServiceKind.ToString(); - var type = service.ServiceTypeName; - var serviceManifestVersion = service.ServiceManifestVersion; - var serviceName = service.ServiceName; - var serviceDescription = await FabricClientInstance.ServiceManager.GetServiceDescriptionAsync(serviceName); - var processModel = serviceDescription.ServicePackageActivationMode.ToString(); - - foreach (var rep in replicaList) - { - if (service.ServiceName != rep.ServiceName) - { - continue; - } - - // Get established port count per service. - int procId = (int)rep.HostProcessId; - int ports = 0, ephemeralPorts = 0; - - if (procId > 0) - { - ports = OSInfoProvider.Instance.GetActiveTcpPortCount(procId, OperatingSystem.IsLinux() ? ConfigPackage.Path : null); - ephemeralPorts = OSInfoProvider.Instance.GetActiveEphemeralPortCount(procId, OperatingSystem.IsLinux() ? ConfigPackage.Path : null); - } - - _ = sb.AppendLine("\tService Name: " + serviceName.OriginalString); - _ = sb.AppendLine("\tTypeName: " + type); - _ = sb.AppendLine("\tKind: " + kind); - _ = sb.AppendLine("\tProcessModel: " + processModel); - _ = sb.AppendLine("\tServiceManifest Version: " + serviceManifestVersion); - - if (ports > 0) - { - _ = sb.AppendLine("\tActive Ports: " + ports); - } - - if (ephemeralPorts > 0) - { - _ = sb.AppendLine("\tActive Ephemeral Ports: " + ephemeralPorts); - } - - _ = sb.AppendLine(); - - // ETW. - if (IsEtwEnabled) - { - ObserverLogger.LogEtw( - ObserverConstants.FabricObserverETWEventName, - new - { - Level = 0, // Info - Node = NodeName, - Observer = ObserverName, - AppName = appName, - AppType = appType, - AppVersion = appVersion, - AppHealthState = healthState, - AppStatus = status, - ServiceName = serviceName.OriginalString, - ServiceTypeName = type, - Kind = kind, - ProcessModel = processModel, - ServiceManifestVersion = serviceManifestVersion, - ActivePorts = ports, - EphemeralPorts = ephemeralPorts - }); - } - - break; - } - } - } - } - - ret = sb.ToString(); - _ = sb.Clear(); - } - finally - { - sreader?.Dispose(); - xreader?.Dispose(); - } - - return ret; - } - } -} \ No newline at end of file diff --git a/FabricObserver/PackageRoot/Config/Settings.xml b/FabricObserver/PackageRoot/Config/Settings.xml index e7e30e94..145e6145 100644 --- a/FabricObserver/PackageRoot/Config/Settings.xml +++ b/FabricObserver/PackageRoot/Config/Settings.xml @@ -87,11 +87,7 @@ **NOTE: For Linux runtime target, just supply the directory name(s)/path (not a path with drive letter like you do for Windows).** --> - + - - -
+ will be locally logged. This is the recommended setting. --> - + @@ -363,14 +352,6 @@ -
- - - - - - -
@@ -419,7 +400,6 @@
- diff --git a/FabricObserver/PackageRoot/Data/Plugins/Readme.txt b/FabricObserver/PackageRoot/Data/Plugins/Readme.txt index b087d9aa..83c64d3a 100644 --- a/FabricObserver/PackageRoot/Data/Plugins/Readme.txt +++ b/FabricObserver/PackageRoot/Data/Plugins/Readme.txt @@ -7,8 +7,8 @@ Note that the observer API lives in its own library, FabricObserver.Extensibilit 1. Create a new .NET 6 Library project. 2. Install the same version of the Microsoft.ServiceFabricApps.FabricObserver.Extensibility nupkg from https://www.nuget.org/profiles/ServiceFabricApps as the version of FabricObserver you are deploying. - E.g., 3.2.15 if you are going to deploy FO 3.2.15. - NOTE: You can also consume the entire FabricObserver 3.2.15 nupkg to build your plugin. Please see the SampleObserverPlugin project's csproj file for more information. + E.g., 3.3.0 if you are going to deploy FO 3.3.0. + NOTE: You can also consume the entire FabricObserver 3.3.0 nupkg to build your plugin. Please see the SampleObserverPlugin project's csproj file for more information. 3. Write an observer! @@ -68,5 +68,5 @@ cd C:\Users\me\source\repos\service-fabric-observer ./Build-FabricObserver ./Build-NugetPackages -The output from the above commands contains FabricObserver platform-specific nupkgs and a package you have to use for plugin authoring named Microsoft.ServiceFabricApps.FabricObserver.Extensibility.3.2.15.nupkg. Nupkg files from above command would be located in +The output from the above commands contains FabricObserver platform-specific nupkgs and a package you have to use for plugin authoring named Microsoft.ServiceFabricApps.FabricObserver.Extensibility.3.3.0.nupkg. Nupkg files from above command would be located in C:\Users\me\source\repos\service-fabric-observer\bin\release\FabricObserver\Nugets. \ No newline at end of file diff --git a/FabricObserver/PackageRoot/ServiceManifest.xml b/FabricObserver/PackageRoot/ServiceManifest.xml index ced20ed0..51a8c823 100644 --- a/FabricObserver/PackageRoot/ServiceManifest.xml +++ b/FabricObserver/PackageRoot/ServiceManifest.xml @@ -1,6 +1,6 @@  @@ -9,7 +9,7 @@ This name must match the string used in RegisterServiceType call in Program.cs. --> - + FabricObserver @@ -19,10 +19,10 @@ - + - + \ No newline at end of file diff --git a/FabricObserver/PackageRoot/ServiceManifest_linux.xml b/FabricObserver/PackageRoot/ServiceManifest_linux.xml index 93a813d0..b712593c 100644 --- a/FabricObserver/PackageRoot/ServiceManifest_linux.xml +++ b/FabricObserver/PackageRoot/ServiceManifest_linux.xml @@ -1,6 +1,6 @@  @@ -11,7 +11,7 @@ - + setcaps.sh @@ -27,10 +27,10 @@ - + - + \ No newline at end of file diff --git a/FabricObserverApp/ApplicationPackageRoot/ApplicationManifest.xml b/FabricObserverApp/ApplicationPackageRoot/ApplicationManifest.xml index 59862877..ef5e4e73 100644 --- a/FabricObserverApp/ApplicationPackageRoot/ApplicationManifest.xml +++ b/FabricObserverApp/ApplicationPackageRoot/ApplicationManifest.xml @@ -1,6 +1,6 @@  - + @@ -45,8 +45,6 @@ - - @@ -57,8 +55,7 @@ - - @@ -76,7 +73,6 @@ - @@ -92,7 +88,6 @@ - @@ -203,7 +197,7 @@ - + @@ -255,7 +249,7 @@ should match the Name and Version attributes of the ServiceManifest element defined in the ServiceManifest.xml file. --> - + @@ -331,9 +325,7 @@ - + @@ -397,13 +389,6 @@
-
- - - - - -
diff --git a/FabricObserverTests/FabricObserverTests.csproj b/FabricObserverTests/FabricObserverTests.csproj index 3badb7b7..d47cf55a 100644 --- a/FabricObserverTests/FabricObserverTests.csproj +++ b/FabricObserverTests/FabricObserverTests.csproj @@ -2,23 +2,22 @@ false FabricObserverTests - net6.0 + net8.0 disable win-x64 - CS0414 FabricObserverTests FabricObserverTests x64 - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/FabricObserverTests/HealthReportNotFoundException.cs b/FabricObserverTests/HealthReportNotFoundException.cs index db0a75e7..79a95998 100644 --- a/FabricObserverTests/HealthReportNotFoundException.cs +++ b/FabricObserverTests/HealthReportNotFoundException.cs @@ -4,7 +4,6 @@ // ------------------------------------------------------------ using System; -using System.Runtime.Serialization; namespace FabricObserverTests { @@ -37,14 +36,5 @@ public HealthReportNotFoundException(string message) : base(message) public HealthReportNotFoundException(string message, Exception innerException) : base(message, innerException) { } - - /// - /// Creates an instance of HealthReportNotFoundException. - /// - /// SerializationInfo - /// StreamingContext - protected HealthReportNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } } } \ No newline at end of file diff --git a/FabricObserverTests/ObserverTests.cs b/FabricObserverTests/ObserverTests.cs index 0dfc7f6d..4ccada77 100644 --- a/FabricObserverTests/ObserverTests.cs +++ b/FabricObserverTests/ObserverTests.cs @@ -39,6 +39,7 @@ public class ObserverTests { // Change this to suit your test env. These tests must be run on a 1-node dev cluster. private const string NodeName = "_Node_0"; + private const string NodeType = "NodeType0"; private static readonly Uri TestServiceName = new("fabric:/app/service"); private static readonly CancellationToken Token = new(); private static ICodePackageActivationContext CodePackageContext = null; @@ -52,6 +53,7 @@ public class ObserverTests private static FabricClient FabricClientSingleton => FabricClientUtilities.FabricClientSingleton; [ClassInitialize] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Noise..")] public static async Task TestClassStartUp(TestContext testContext) { if (!IsLocalSFRuntimePresent()) @@ -112,11 +114,11 @@ public static async Task TestClassStartUp(TestContext testContext) private static async Task DeployTestAppsAppsAsync() { - await DeployHealthMetricsAppAsync(); - await DeployTestApp42Async(); - await DeployVotingAppAsync(); - await DeployCpuStressAppAsync(); - await DeployPortTestAppAsync(); + await DeployHealthMetricsAppAsync().ConfigureAwait(false); + await DeployTestApp42Async().ConfigureAwait(false); + await DeployVotingAppAsync().ConfigureAwait(false); + await DeployCpuStressAppAsync().ConfigureAwait(false); + await DeployPortTestAppAsync().ConfigureAwait(false); // Wait a little extra time for apps to be fully in ready state. await Task.Delay(5000); @@ -479,7 +481,7 @@ private static async Task DeployPortTestAppAsync() StatelessServiceDescription serviceDescription = new() { - ServiceName = new Uri(appName + "/PortTestService"), + ServiceName = new Uri($"{appName}/PortTestService"), ServiceTypeName = "PortTestServiceType", PartitionSchemeDescription = new SingletonPartitionSchemeDescription(), ApplicationName = new Uri(appName), @@ -488,8 +490,7 @@ private static async Task DeployPortTestAppAsync() }; await FabricClientSingleton.ServiceManager.CreateServiceAsync(serviceDescription); - - await Task.Delay(TimeSpan.FromSeconds(15)); + await Task.Delay(TimeSpan.FromSeconds(30)); } catch (FabricException fe) { @@ -781,11 +782,6 @@ public void ContainerObserver_Constructor_test() [TestMethod] public void DiskObserver_Constructor_Test() { - - - - ObserverManager.ObserverWebAppDeployed = true; - using var obs = new DiskObserver(TestServiceContext); Assert.IsTrue(obs.ObserverLogger != null); @@ -810,11 +806,6 @@ public void FabricSystemObserver_Constructor_Test() [TestMethod] public void NetworkObserver_Constructor_Test() { - - - - ObserverManager.ObserverWebAppDeployed = true; - using var obs = new NetworkObserver(TestServiceContext); Assert.IsTrue(obs.ObserverLogger != null); @@ -850,25 +841,6 @@ public void OSObserver_Constructor_Test() Assert.IsTrue(obs.ObserverName == ObserverConstants.OSObserverName); } - [TestMethod] - public void SFConfigurationObserver_Constructor_Test() - { - using var client = new FabricClient(); - - - - - - ObserverManager.ObserverWebAppDeployed = true; - - using var obs = new SFConfigurationObserver(TestServiceContext); - - // These are set in derived ObserverBase. - Assert.IsTrue(obs.ObserverLogger != null); - Assert.IsTrue(obs.HealthReporter != null); - Assert.IsTrue(obs.ObserverName == ObserverConstants.SFConfigurationObserverName); - } - /* End Simple Tests */ /* AppObserver Initialization */ @@ -876,10 +848,6 @@ public void SFConfigurationObserver_Constructor_Test() [TestMethod] public async Task AppObserver_InitializeAsync_MalformedTargetAppValue_GeneratesWarning() { - - - - using var obs = new AppObserver(TestServiceContext) { JsonConfigPath = Path.Combine(Environment.CurrentDirectory, "PackageRoot", "Config", "AppObserver.config.targetAppMalformed.json") @@ -894,10 +862,6 @@ public async Task AppObserver_InitializeAsync_MalformedTargetAppValue_GeneratesW [TestMethod] public async Task AppObserver_InitializeAsync_InvalidJson_GeneratesWarning() { - - - - using var obs = new AppObserver(TestServiceContext) { JsonConfigPath = Path.Combine(Environment.CurrentDirectory, "PackageRoot", "Config", "AppObserver.config.invalid.json") @@ -912,10 +876,6 @@ public async Task AppObserver_InitializeAsync_InvalidJson_GeneratesWarning() [TestMethod] public async Task AppObserver_InitializeAsync_NoConfigFound_GeneratesWarning() { - - - - using var obs = new AppObserver(TestServiceContext) { JsonConfigPath = Path.Combine(Environment.CurrentDirectory, "PackageRoot", "Config", "AppObserver.config.empty.json") @@ -932,10 +892,6 @@ public async Task AppObserver_InitializeAsync_NoConfigFound_GeneratesWarning() [TestMethod] public async Task AppObserver_InitializeAsync_TargetAppType_ServiceExcludeList_EnsureExcluded() { - - - - using var obs = new AppObserver(TestServiceContext) { JsonConfigPath = Path.Combine(Environment.CurrentDirectory, "PackageRoot", "Config", "AppObserver.config.apptype.exclude.json") @@ -943,17 +899,13 @@ public async Task AppObserver_InitializeAsync_TargetAppType_ServiceExcludeList_E await obs.InitializeAsync(); var deployedTargets = obs.ReplicaOrInstanceList; - Assert.IsTrue(deployedTargets.Any()); + Assert.IsTrue(deployedTargets.Count != 0); Assert.IsFalse(deployedTargets.Any(t => t.ServiceName.OriginalString.Contains("DoctorActorService"))); } [TestMethod] public async Task AppObserver_InitializeAsync_TargetApp_ServiceExcludeList_EnsureExcluded() { - - - - using var obs = new AppObserver(TestServiceContext) { JsonConfigPath = Path.Combine(Environment.CurrentDirectory, "PackageRoot", "Config", "AppObserver.config.app.exclude.json") @@ -961,7 +913,7 @@ public async Task AppObserver_InitializeAsync_TargetApp_ServiceExcludeList_Ensur await obs.InitializeAsync(); var deployedTargets = obs.ReplicaOrInstanceList; - Assert.IsTrue(deployedTargets.Any()); + Assert.IsTrue(deployedTargets.Count != 0); Assert.IsFalse(deployedTargets.Any(t => t.ServiceName.OriginalString.Contains("DoctorActorServiceType"))); } @@ -975,7 +927,7 @@ public async Task AppObserver_InitializeAsync_TargetAppType_ServiceIncludeList_E await obs.InitializeAsync(); var deployedTargets = obs.ReplicaOrInstanceList; - Assert.IsTrue(deployedTargets.Any()); + Assert.IsTrue(deployedTargets.Count != 0); Assert.IsTrue(deployedTargets.All(t => t.ServiceName.OriginalString.Contains("DoctorActorService"))); } @@ -989,7 +941,7 @@ public async Task AppObserver_InitializeAsync_TargetApp_ServiceIncludeList_Ensur await obs.InitializeAsync(); var deployedTargets = obs.ReplicaOrInstanceList; - Assert.IsTrue(deployedTargets.Any()); + Assert.IsTrue(deployedTargets.Count != 0); Assert.IsTrue(deployedTargets.All(t => t.ServiceName.OriginalString.Contains("DoctorActorService"))); } @@ -1005,7 +957,7 @@ public async Task AppObserver_InitializeAsync_TargetAppType_MultiServiceExcludeL await obs.InitializeAsync(); var serviceReplicas = obs.ReplicaOrInstanceList; - Assert.IsTrue(serviceReplicas.Any()); + Assert.IsTrue(serviceReplicas.Count != 0); // You can't supply multiple Exclude lists for the same target app/type. None of the target services will be excluded.. Assert.IsTrue( @@ -1024,7 +976,7 @@ public async Task AppObserver_InitializeAsync_TargetApp_MultiServiceExcludeList_ await obs.InitializeAsync(); var serviceReplicas = obs.ReplicaOrInstanceList; - Assert.IsTrue(serviceReplicas.Any()); + Assert.IsTrue(serviceReplicas.Count != 0); // You can't supply multiple Exclude lists for the same target app/type. None of the target services will be excluded.. Assert.IsTrue( @@ -1043,7 +995,7 @@ public async Task AppObserver_InitializeAsync_TargetAppType_MultiServiceIncludeL await obs.InitializeAsync(); var serviceReplicas = obs.ReplicaOrInstanceList; - Assert.IsTrue(serviceReplicas.Any()); + Assert.IsTrue(serviceReplicas.Count != 0); Assert.IsTrue(serviceReplicas.Count == 2); } @@ -1057,11 +1009,11 @@ public async Task AppObserver_InitializeAsync_TargetApp_MultiServiceIncludeList_ await obs.InitializeAsync(); var deployedTargets = obs.ReplicaOrInstanceList; - Assert.IsTrue(deployedTargets.Any()); + Assert.IsTrue(deployedTargets.Count != 0); await obs.InitializeAsync(); var serviceReplicas = obs.ReplicaOrInstanceList; - Assert.IsTrue(serviceReplicas.Any()); + Assert.IsTrue(serviceReplicas.Count != 0); Assert.IsTrue(serviceReplicas.Count == 2); } @@ -1345,7 +1297,7 @@ await FabricClientSingleton.ApplicationManager.UpgradeApplicationAsync( var defaultParameters = applicationTypeList.First(a => a.ApplicationTypeVersion == "1.0.0").DefaultParameters; Assert.IsTrue(defaultParameters.Any()); - ApplicationParameterList parameters = new(); + ApplicationParameterList parameters = []; // Fill parameter list with app and default parameters. The position of these matters (first add app parameters. Then, add default parameters). FabricClientUtilities.AddParametersIfNotExists(parameters, appParameters); @@ -1428,7 +1380,7 @@ public async Task Ensure_ConcurrentQueue_Collection_Has_Data_CPU_Win32Impl() FabricClientUtilities fabricClientUtilities = new(NodeName); var services = await fabricClientUtilities.GetAllDeployedReplicasOrInstancesAsync(true, Token); - Assert.IsTrue(services.Any()); + Assert.IsTrue(services.Count != 0); ConcurrentDictionary> AllAppCpuData = new(); ConcurrentQueue serviceProcs = new(); @@ -1470,7 +1422,7 @@ public async Task Ensure_ConcurrentQueue_Collection_Has_Data_CPU_Win32Impl() } }); - Assert.IsTrue(AllAppCpuData.Any() && serviceProcs.Any()); + Assert.IsTrue(!AllAppCpuData.IsEmpty && !serviceProcs.IsEmpty); Assert.IsTrue(serviceProcs.Count == AllAppCpuData.Count); TimeSpan duration = TimeSpan.FromSeconds(3); @@ -1501,12 +1453,12 @@ public async Task Ensure_ConcurrentQueue_Collection_Has_Data_CPU_Win32Impl() } [TestMethod] - public async Task Ensure_ConcurrentQueue_Collection_Has_Data_CPU_NET6ProcessImpl() + public async Task Ensure_ConcurrentQueue_Collection_Has_Data_CPU_NET8ProcessImpl() { FabricClientUtilities fabricClientUtilities = new(NodeName); var services = await fabricClientUtilities.GetAllDeployedReplicasOrInstancesAsync(true, Token); - Assert.IsTrue(services.Any()); + Assert.IsTrue(services.Count != 0); ConcurrentDictionary> AllAppCpuData = new(); ConcurrentQueue serviceProcs = new(); @@ -1548,7 +1500,7 @@ public async Task Ensure_ConcurrentQueue_Collection_Has_Data_CPU_NET6ProcessImpl } }); - Assert.IsTrue(AllAppCpuData.Any() && serviceProcs.Any()); + Assert.IsTrue(!AllAppCpuData.IsEmpty && !serviceProcs.IsEmpty); Assert.IsTrue(serviceProcs.Count == AllAppCpuData.Count); TimeSpan duration = TimeSpan.FromSeconds(3); @@ -1584,7 +1536,7 @@ public async Task Ensure_CircularBuffer_Collection_Has_Data_CPU_Win32Impl() FabricClientUtilities fabricClientUtilities = new(NodeName); var services = await fabricClientUtilities.GetAllDeployedReplicasOrInstancesAsync(true, Token); - Assert.IsTrue(services.Any()); + Assert.IsTrue(services.Count != 0); ConcurrentDictionary> AllAppCpuData = new(); ConcurrentQueue serviceProcs = new(); @@ -1626,7 +1578,7 @@ public async Task Ensure_CircularBuffer_Collection_Has_Data_CPU_Win32Impl() } }); - Assert.IsTrue(AllAppCpuData.Any() && serviceProcs.Any()); + Assert.IsTrue(!AllAppCpuData.IsEmpty && !serviceProcs.IsEmpty); Assert.IsTrue(serviceProcs.Count == AllAppCpuData.Count); TimeSpan duration = TimeSpan.FromSeconds(3); @@ -1657,12 +1609,12 @@ public async Task Ensure_CircularBuffer_Collection_Has_Data_CPU_Win32Impl() } [TestMethod] - public async Task Ensure_CircularBuffer_Collection_Has_Data_CPU_NET6ProcessImpl() + public async Task Ensure_CircularBuffer_Collection_Has_Data_CPU_NET8ProcessImpl() { FabricClientUtilities fabricClientUtilities = new(NodeName); var services = await fabricClientUtilities.GetAllDeployedReplicasOrInstancesAsync(true, Token); - Assert.IsTrue(services.Any()); + Assert.IsTrue(services.Count != 0); ConcurrentDictionary> AllAppCpuData = new(); ConcurrentQueue serviceProcs = new(); @@ -1704,7 +1656,7 @@ public async Task Ensure_CircularBuffer_Collection_Has_Data_CPU_NET6ProcessImpl( } }); - Assert.IsTrue(AllAppCpuData.Any() && serviceProcs.Any()); + Assert.IsTrue(!AllAppCpuData.IsEmpty && !serviceProcs.IsEmpty); Assert.IsTrue(serviceProcs.Count == AllAppCpuData.Count); TimeSpan duration = TimeSpan.FromSeconds(3); @@ -1754,7 +1706,7 @@ public async Task AppObserver_DumpProcessOnWarning_SuccessfulDumpCreation() var dmps = Directory.GetFiles(obs.DumpsPath, "*.dmp"); - Assert.IsTrue(dmps != null && dmps.Any()); + Assert.IsTrue(dmps != null && dmps.Length != 0); // VotingData service, and two helper codepackage binaries. Assert.IsTrue(dmps.All(d => d.Contains("VotingData") || d.Contains("ConsoleApp6") || d.Contains("ConsoleApp7"))); @@ -1790,7 +1742,7 @@ public async Task AppObserver_DumpProcessOnError_SuccessfulDumpCreation() var dmps = Directory.GetFiles(obs.DumpsPath, "*.dmp"); - Assert.IsTrue(dmps != null && dmps.Any()); + Assert.IsTrue(dmps != null && dmps.Length != 0); // VotingData service, and two helper codepackage binaries. Assert.IsTrue(dmps.All(d => d.Contains("VotingData") || d.Contains("ConsoleApp6") || d.Contains("ConsoleApp7"))); @@ -1843,6 +1795,7 @@ public async Task ClusterObserver_ObserveAsync_AppMonitor_Successful_IsHealthy_D EntityType = EntityType.Service, Metric = ErrorWarningProperty.PrivateBytesMb, NodeName = NodeName, + NodeType = NodeType, HealthState = HealthState.Warning, ObserverName = ObserverConstants.AppObserverName, Property = "ClusterObserver_App", @@ -1906,6 +1859,7 @@ public async Task ClusterObserver_ObserveAsync_AppMonitor_Successful_IsHealthy_D Assert.IsTrue(data.Description == "Service Test warning for CO test."); Assert.IsTrue(data.Metric == ErrorWarningProperty.PrivateBytesMb); Assert.IsTrue(data.NodeName == NodeName); + Assert.IsTrue(data.NodeType == NodeType); Assert.IsTrue(data.ObserverName == ObserverConstants.AppObserverName); Assert.IsTrue(data.Source == "FOTest"); Assert.IsTrue(data.Value == 1024); @@ -1925,6 +1879,7 @@ public async Task ClusterObserver_ObserveAsync_NodeMonitor_Successful_IsHealthy_ EntityType = EntityType.Machine, Metric = ErrorWarningProperty.MemoryConsumptionPercentage, NodeName = NodeName, + NodeType = NodeType, ObserverName = ObserverConstants.NodeObserverName, HealthState = HealthState.Warning, Property = "ClusterObserver_Node", @@ -1977,7 +1932,7 @@ public async Task ClusterObserver_ObserveAsync_NodeMonitor_Successful_IsHealthy_ Assert.IsNotNull(nodeTelemData); Assert.IsTrue(nodeTelemData.Count > 0); - foreach (var data in nodeTelemData.Where(d => d.Property == "NodeObserver_App")) + foreach (var data in nodeTelemData.Where(d => d.Property == "ClusterObserver_Node")) { Assert.IsTrue(data.EntityType == EntityType.Machine); Assert.IsTrue(data.HealthState == HealthState.Warning); @@ -1985,6 +1940,7 @@ public async Task ClusterObserver_ObserveAsync_NodeMonitor_Successful_IsHealthy_ Assert.IsTrue(data.Description.Contains("Machine Test warning for CO test.")); Assert.IsTrue(data.Metric == ErrorWarningProperty.MemoryConsumptionPercentage); Assert.IsTrue(data.NodeName == NodeName); + Assert.IsTrue(data.NodeType == NodeType); Assert.IsTrue(data.ObserverName == ObserverConstants.NodeObserverName); Assert.IsTrue(data.Property == "ClusterObserver_Node"); Assert.IsTrue(data.Source == "FOTest"); @@ -2182,7 +2138,6 @@ public async Task OSObserver_ObserveAsync_Successful_IsHealthy_NoWarningsOrError using var obs = new OSObserver(TestServiceContext) { ClusterManifestPath = Path.Combine(Environment.CurrentDirectory, "clusterManifest.xml"), - IsObserverWebApiAppDeployed = true, IsEtwProviderEnabled = true }; @@ -2198,16 +2153,6 @@ public async Task OSObserver_ObserveAsync_Successful_IsHealthy_NoWarningsOrError // observer did not have any internal errors during run. Assert.IsFalse(obs.IsUnhealthy); - - var outputFilePath = Path.Combine(Environment.CurrentDirectory, "fabric_observer_logs", "SysInfo.txt"); - - // Output log file was created successfully during test. - Assert.IsTrue(File.Exists(outputFilePath) - && File.GetLastWriteTime(outputFilePath) > startDateTime - && File.GetLastWriteTime(outputFilePath) < obs.LastRunDateTime); - - // Output file is not empty. - Assert.IsTrue((await File.ReadAllLinesAsync(outputFilePath)).Length > 0); } [TestMethod] @@ -2217,7 +2162,6 @@ public async Task OSObserver_IsWindowsDevCluster_True() using var obs = new OSObserver(TestServiceContext) { ClusterManifestPath = Path.Combine(Environment.CurrentDirectory, "clusterManifest.xml"), - IsObserverWebApiAppDeployed = false, IsEtwProviderEnabled = false }; @@ -2248,8 +2192,6 @@ public async Task DiskObserver_ObserveAsync_Successful_IsHealthy_NoWarningsOrErr using var obs = new DiskObserver(TestServiceContext) { - // This is required since output files are only created if fo api app is also deployed to cluster.. - IsObserverWebApiAppDeployed = true, CpuMonitorDuration = TimeSpan.FromSeconds(1), FolderSizeMonitoringEnabled = true, FolderSizeConfigDataWarning = warningDictionary, @@ -2266,16 +2208,6 @@ public async Task DiskObserver_ObserveAsync_Successful_IsHealthy_NoWarningsOrErr // observer did not have any internal errors during run. Assert.IsFalse(obs.IsUnhealthy); - - var outputFilePath = Path.Combine(Environment.CurrentDirectory, "fabric_observer_logs", "disks.txt"); - - // Output log file was created successfully during test. - Assert.IsTrue(File.Exists(outputFilePath) - && File.GetLastWriteTime(outputFilePath) > startDateTime - && File.GetLastWriteTime(outputFilePath) < obs.LastRunDateTime); - - // Output file is not empty. - Assert.IsTrue((await File.ReadAllLinesAsync(outputFilePath)).Length > 0); } [TestMethod] @@ -2300,10 +2232,7 @@ public async Task DiskObserver_ObserveAsync_Successful_IsHealthy_WarningsOrError // Folder size monitoring. This will most likely generate a warning. FolderSizeConfigDataWarning = warningDictionary, - - // This is required since output files are only created if fo api app is also deployed to cluster.. - IsObserverWebApiAppDeployed = true, - IsEtwProviderEnabled = true, + IsEtwProviderEnabled = true }; IServiceCollection services = new ServiceCollection(); @@ -2329,16 +2258,6 @@ public async Task DiskObserver_ObserveAsync_Successful_IsHealthy_WarningsOrError // observer did not have any internal errors during run. Assert.IsFalse(obs.IsUnhealthy); - var outputFilePath = Path.Combine(Environment.CurrentDirectory, "fabric_observer_logs", "disks.txt"); - - // Output log file was created successfully during test. - Assert.IsTrue(File.Exists(outputFilePath) - && File.GetLastWriteTime(outputFilePath) > startDateTime - && File.GetLastWriteTime(outputFilePath) < obs.LastRunDateTime); - - // Output file is not empty. - Assert.IsTrue((await File.ReadAllLinesAsync(outputFilePath)).Length > 0); - // Stop clears health warning await obsMgr.StopObserversAsync(); Assert.IsFalse(obs.HasActiveFabricErrorOrWarning); @@ -2359,33 +2278,6 @@ public async Task NetworkObserver_ObserveAsync_Successful_Warnings() Assert.IsTrue(obs.HasActiveFabricErrorOrWarning); } - [TestMethod] - public async Task NetworkObserver_ObserveAsync_Successful_WritesLocalFile_ObsWebDeployed() - { - var startDateTime = DateTime.Now; - using var obs = new NetworkObserver(TestServiceContext) - { - // This is required since output files are only created if fo api app is also deployed to cluster.. - IsObserverWebApiAppDeployed = true - }; - - await obs.ObserveAsync(Token); - - // Observer ran to completion with no errors. - // The supplied config does not include deployed app network configs, so - // ObserveAsync will return in milliseconds. - Assert.IsTrue(obs.LastRunDateTime > startDateTime); - var outputFilePath = Path.Combine(_logger.LogFolderBasePath, "NetInfo.txt"); - - // Output log file was created successfully during test. - Assert.IsTrue(File.Exists(outputFilePath) - && File.GetLastWriteTime(outputFilePath) > startDateTime - && File.GetLastWriteTime(outputFilePath) < obs.LastRunDateTime); - - // Output file is not empty. - Assert.IsTrue((await File.ReadAllLinesAsync(outputFilePath)).Length > 0); - } - [TestMethod] public async Task NodeObserver_ObserveAsync_Successful_IsHealthy_NoWarningsOrErrorsDetected() { @@ -2439,41 +2331,6 @@ public async Task NodeObserver_ObserveAsync_Successful_IsHealthy_WarningsOrError Assert.IsFalse(obs.IsUnhealthy); } - [TestMethod] - public async Task SFConfigurationObserver_ObserveAsync_Successful_IsHealthy() - { - var startDateTime = DateTime.Now; - using var obs = new SFConfigurationObserver(TestServiceContext) - { - IsEnabled = true, - - // This is required since output files are only created if fo api app is also deployed to cluster.. - IsObserverWebApiAppDeployed = true, - ClusterManifestPath = Path.Combine(Environment.CurrentDirectory, "clusterManifest.xml") - }; - - await obs.ObserveAsync(Token); - - // observer ran to completion with no errors. - Assert.IsTrue(obs.LastRunDateTime > startDateTime); - - // observer detected no error conditions. - Assert.IsFalse(obs.HasActiveFabricErrorOrWarning); - - // observer did not have any internal errors during run. - Assert.IsFalse(obs.IsUnhealthy); - - var outputFilePath = Path.Combine(Environment.CurrentDirectory, "fabric_observer_logs", "SFInfraInfo.txt"); - - // Output log file was created successfully during test. - Assert.IsTrue(File.Exists(outputFilePath) - && File.GetLastWriteTime(outputFilePath) > startDateTime - && File.GetLastWriteTime(outputFilePath) < obs.LastRunDateTime); - - // Output file is not empty. - Assert.IsTrue((await File.ReadAllLinesAsync(outputFilePath)).Length > 0); - } - [TestMethod] public async Task FabricSystemObserver_ObserveAsync_Successful_IsHealthy_NoWarningsOrErrors() { @@ -2837,7 +2694,7 @@ public async Task AppObserver_ETW_EventData_RG_ValuesAreNonZero() Assert.IsTrue(telemData.Count > 0); telemData = telemData.Where(t => t.ApplicationName == "fabric:/Voting").ToList(); - Assert.IsTrue(telemData.Any()); + Assert.IsTrue(telemData.Count != 0); foreach (var data in telemData) { @@ -2897,7 +2754,7 @@ public async Task AppObserver_ETW_PrivateBytes_Multiple_CodePackages_ValuesAreNo t => t.ApplicationName == "fabric:/Voting" && t.HealthState == HealthState.Warning).ToList(); // 2 service code packages + 2 helper code packages (VotingData) * 2 metrics = 8 warnings... - Assert.IsTrue(telemData.Any() && telemData.Count == 8); + Assert.IsTrue(telemData.Count != 0 && telemData.Count == 8); } // Private Bytes @@ -3018,7 +2875,7 @@ public async Task DiskObserver_ETW_EventData_IsTelemetryData_Warnings() Assert.IsTrue(telemData.Count > 0); telemData = telemData.Where(d => d.HealthState == HealthState.Warning).ToList(); - Assert.IsTrue(telemData.Any()); + Assert.IsTrue(telemData.Count != 0); foreach (var data in telemData) { @@ -3097,7 +2954,7 @@ public async Task FabricSystemObserver_ETW_EventData_Is_SystemServiceTelemetryDa Assert.IsTrue(telemData.Count > 0); telemData = telemData.Where(d => d.HealthState == HealthState.Warning).ToList(); - Assert.IsTrue(telemData.Any()); + Assert.IsTrue(telemData.Count != 0); foreach (var data in telemData) { @@ -3202,7 +3059,7 @@ public async Task NodeObserver_ETW_EventData_IsNodeTelemetryData_Warnings() Assert.IsTrue(telemData.Count > 0); telemData = telemData.Where(d => d.HealthState == HealthState.Warning).ToList(); - Assert.IsTrue(telemData.Any()); + Assert.IsTrue(telemData.Count != 0); foreach (var data in telemData) { diff --git a/FabricObserverTests/PackageRoot/Config/Settings.xml b/FabricObserverTests/PackageRoot/Config/Settings.xml index 9c5ef125..4cf7fffa 100644 --- a/FabricObserverTests/PackageRoot/Config/Settings.xml +++ b/FabricObserverTests/PackageRoot/Config/Settings.xml @@ -13,7 +13,6 @@ - @@ -22,7 +21,6 @@ -
@@ -90,7 +88,6 @@ - diff --git a/FabricObserverTests/VotingApp.zip b/FabricObserverTests/VotingApp.zip index 2f0ea101..33d2446d 100644 Binary files a/FabricObserverTests/VotingApp.zip and b/FabricObserverTests/VotingApp.zip differ diff --git a/FabricObserverWeb/ReadMe.md b/FabricObserverWeb/ReadMe.md index 4ff61c9d..43f401a0 100644 --- a/FabricObserverWeb/ReadMe.md +++ b/FabricObserverWeb/ReadMe.md @@ -1,51 +1,51 @@ -## This project is obsolete and no longer supported. Use at your own risk. Leaving it here for those who do use this. - -This is the FabricObserver API App (ASP.NET Core v3.1), used for "communicating" with Observers from within a node (but you can choose to expose this service to the Internet if you want to. By default, if you deploy FOWebApi as is, then only a service running on the same node can call into its REST API.). - -Each observer writes out to their own log file (**noisy** when EnableVerboseLogging is set to true in Settings.xml for whatever observer you choose to enable). If you deploy FabricObserverWebApp, you **really should only log Warnings and Errors**). The REST API reads from log files, which are kept up to date by ObserverManager (so, if AppObserver detects warning or error conditions, for example, this information will only live as long as it remains in this state. If the next iteration of the observer in warning/error reports Ok, then its log file will no longer contain prior health information and the API will no longer report error or warning (with details) when called. - -Why? - -By design, you can't communicate with an observer. Use the web api to get current information about app service and node health states that an observer monitors and reports on. - - -Example web API calls: - -Check if an Observer has detected any Error or Warning conditions on local node: - -http://localhost:5000/api/ObserverLog/[Observer name] - -e.g., - -http://localhost:5000/api/ObserverLog/NodeObserver - -will return a JSON string like this, for example: - -{"date":"08-29-2019 21:07:11.6257","healthState":"Ok","message":""} - -when current state of the local node is healthy. In the case when it is not healthy, you will see healthState:Warning/Error with the message field containing the details, including an [FOxxx error code](/Documentation/ErrorCodes.md): - -A DiskObserver example: - -[{"date":"2019-11-05 19:09:19.1445","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"}] - -Note that this is a JSON array as you can have multiple warnings (each time the observer detects a warning state, the observer writes it to its log.), so you will see things like this: - -[{"date":"2019-11-05 19:09:19.1445","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"},{"date":"2019-11-05 19:09:57.3587","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"},{"date":"2019-11-05 19:10:37.4418","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"},{"date":"2019-11-05 19:11:16.6540","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"},{"date":"2019-11-05 19:11:56.0752","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"}] - -When the observer determines that the health state has returned to normal, you'll again see: - -{"date":"11-05-2019 19:40:33.9957","healthState":"Ok","message":""} - - -This API also supports html output for "pretty" printing on a web page. For example, - -http://localhost:5000/api/observermanager - -will display a bunch of very useful information about the node: - -![alt text](/Documentation/Images/Page1.png "") -![alt text](/Documentation/Images/Page2.png "") -![alt text](/Documentation/Images/Page3.png "") - -If you decide to expose this api over the Internet on a secure channel (SSL), and provide an FQDN (including port) setting in Settings.xml, then new features will become available like a node menu where you can navigate around the cluster to view node states. Also, you will be able to query for observer data on any node by supplying a node name on the API URI: e.g., https://[FQDN:Port]/api/ObserverLog/NodeObserver/[NodeName] +## This project is obsolete and no longer supported. As of FO Version 3.2.16, any observer that had special code to support this service has been modified to no longer do so. All related configuration settings have been removed. + +This is the FabricObserver API App (ASP.NET Core v3.1), used for "communicating" with Observers from within a node (but you can choose to expose this service to the Internet if you want to. By default, if you deploy FOWebApi as is, then only a service running on the same node can call into its REST API.). + +Each observer writes out to their own log file (**noisy** when EnableVerboseLogging is set to true in Settings.xml for whatever observer you choose to enable). If you deploy FabricObserverWebApp, you **really should only log Warnings and Errors**). The REST API reads from log files, which are kept up to date by ObserverManager (so, if AppObserver detects warning or error conditions, for example, this information will only live as long as it remains in this state. If the next iteration of the observer in warning/error reports Ok, then its log file will no longer contain prior health information and the API will no longer report error or warning (with details) when called. + +Why? + +By design, you can't communicate with an observer. Use the web api to get current information about app service and node health states that an observer monitors and reports on. + + +Example web API calls: + +Check if an Observer has detected any Error or Warning conditions on local node: + +http://localhost:5000/api/ObserverLog/[Observer name] + +e.g., + +http://localhost:5000/api/ObserverLog/NodeObserver + +will return a JSON string like this, for example: + +{"date":"08-29-2019 21:07:11.6257","healthState":"Ok","message":""} + +when current state of the local node is healthy. In the case when it is not healthy, you will see healthState:Warning/Error with the message field containing the details, including an [FOxxx error code](/Documentation/ErrorCodes.md): + +A DiskObserver example: + +[{"date":"2019-11-05 19:09:19.1445","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"}] + +Note that this is a JSON array as you can have multiple warnings (each time the observer detects a warning state, the observer writes it to its log.), so you will see things like this: + +[{"date":"2019-11-05 19:09:19.1445","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"},{"date":"2019-11-05 19:09:57.3587","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"},{"date":"2019-11-05 19:10:37.4418","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"},{"date":"2019-11-05 19:11:16.6540","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"},{"date":"2019-11-05 19:11:56.0752","healthState":"WARN","message":"_Node_0: FO009: Disk Space Consumption % is at or above the specified Minimum limit (80%) - Average Disk Space Consumption %: 92%"}] + +When the observer determines that the health state has returned to normal, you'll again see: + +{"date":"11-05-2019 19:40:33.9957","healthState":"Ok","message":""} + + +This API also supports html output for "pretty" printing on a web page. For example, + +http://localhost:5000/api/observermanager + +will display a bunch of very useful information about the node: + +![alt text](/Documentation/Images/Page1.png "") +![alt text](/Documentation/Images/Page2.png "") +![alt text](/Documentation/Images/Page3.png "") + +If you decide to expose this api over the Internet on a secure channel (SSL), and provide an FQDN (including port) setting in Settings.xml, then new features will become available like a node menu where you can navigate around the cluster to view node states. Also, you will be able to query for observer data on any node by supplying a node name on the API URI: e.g., https://[FQDN:Port]/api/ObserverLog/NodeObserver/[NodeName] diff --git a/README.md b/README.md index c4023b06..bb223cf6 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -## FabricObserver 3.2.15 +## FabricObserver 3.3.0 (.NET 8) [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2Fservice-fabric-observer%2Fmain%2FDocumentation%2FDeployment%2Fservice-fabric-observer.json) -[**FabricObserver (FO)**](https://github.com/microsoft/service-fabric-observer/releases) is a production-ready watchdog service with an easy-to-use extensibility model, written as a stateless, singleton Service Fabric **.NET 6** application that by default +[**FabricObserver (FO)**](https://github.com/microsoft/service-fabric-observer/releases) is a production-ready watchdog service with an easy-to-use extensibility model, written as a stateless, singleton Service Fabric **.NET 8** application that by default 1. Monitors a broad range of physical machine resources that tend to be very important to all Service Fabric services and maps these metrics to the related Service Fabric entities. 2. Runs on multiple versions of Windows Server and Ubuntu. @@ -10,7 +10,7 @@ 4. Supports [Configuration Setting Application Updates](/Documentation/Using.md#parameterUpdates) for any observer for any supported setting. 5. Is actively developed in the open. -> FabricObserver targets SF runtime versions 9 and higher. +> FabricObserver targets SF runtime versions 9.1 and higher. Starting with version 3.3.0, you must deploy the self-contained release package unless you are deploying to a cluster running SF Version 10.1 CU3 or higher, then you can deploy framework-dependent release. FO is a Stateless Service Fabric Application composed of a single service that runs on every node in your cluster, so it can be deployed and run alongside your applications without any changes to them. Each FO service instance knows nothing about other FO instances in the cluster, by design. @@ -75,10 +75,14 @@ For more information about **the design of FabricObserver**, please see the [Des ***Note: By default, FO runs as NetworkUser on Windows and sfappsuser on Linux. If you want to monitor SF service processes that run as elevated (System) on Windows, then you must also run FO as System on Windows. There is no reason to run as root on Linux under any circumstances (see the Capabilities binaries implementations, which allow for FO to run as sfappsuser and successfully execute specific commands that require elevated privilege).*** -For Linux deployments, we have ensured that FO will work as expected as normal user (non-root user). In order for us to do this, we had to implement a setup script that sets [Capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html) on three proxy binaries which can only run specific commands as root. -If you deploy from VS, then you will need to use FabricObserver/PackageRoot/ServiceManifest.linux.xml (just copy its contents into ServiceManifest.xml or add the new piece which is simply a SetupEntryPoint section). +**For Linux deployments**, we have ensured that FO will work as expected as normal user (non-root user). In order for us to do this, we had to implement a setup script that sets [Capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html) on three proxy binaries which can only run specific commands as root. +If you deploy from VS, then you will need to use FabricObserver/PackageRoot/ServiceManifest.linux.xml (just copy its contents into ServiceManifest.xml or add the new piece which is simply a SetupEntryPoint section). Further, you must uncomment the RunAsPolicy element in ApplicationManifest.xml so that Linux Capabilities can be set by a setup bash script that must run as root: -If you use the FO [build script](https://github.com/microsoft/service-fabric-observer/blob/main/Build-FabricObserver.ps1), then it will take care of any configuration modifications automatically for linux build output. +```xml + +``` + +If you use the FO [build script](https://github.com/microsoft/service-fabric-observer/blob/main/Build-FabricObserver.ps1), then it will take care of any configuration modifications automatically for linux build output, but you will still need to modify ApplicationManifest.xml as described above. The build scripts include code build, sfpkg generation, and nupkg generation. They are all located in the top level directory of this repo. @@ -87,7 +91,7 @@ see [FOAzurePipeline.yaml](/FOAzurePipeline.yaml) for msazure devops build tasks .net6 installed (if you deploy VM images from Azure gallery, then they will not have .net6 installed), then you must deploy the SelfContained package. ### Deploy FabricObserver -**Note: You must deploy this version (3.2.15) to clusters that are running SF 9.0 and above. This version also requires .NET 6.** +**Note: You must deploy this version (3.3.0) to clusters that are running SF 9.0 and above. This version also requires .NET 6.** You can deploy FabricObserver (and ClusterObserver) using Visual Studio (if you build the sources yourself), PowerShell or ARM. Please note that this version of FabricObserver no longer supports the DefaultServices node in ApplicationManifest.xml. This means that should you deploy using PowerShell, you must create an instance of the service as the last command in your script. This was done to support ARM deployment, specifically. The StartupServices.xml file you see in the FabricHealerApp project now contains the service information once held in ApplicationManifest's DefaultServices node. Note that this information is primarily useful for deploying from Visual Studio. @@ -127,15 +131,15 @@ Connect-ServiceFabricCluster -ConnectionEndpoint @('sf-win-cluster.westus2.cloud #Copy $path contents (FO app package) to server: -Copy-ServiceFabricApplicationPackage -ApplicationPackagePath $path -CompressPackage -ApplicationPackagePathInImageStore FO3215 -TimeoutSec 1800 +Copy-ServiceFabricApplicationPackage -ApplicationPackagePath $path -CompressPackage -ApplicationPackagePathInImageStore FO330 -TimeoutSec 1800 #Register FO ApplicationType: -Register-ServiceFabricApplicationType -ApplicationPathInImageStore FO3215 +Register-ServiceFabricApplicationType -ApplicationPathInImageStore FO330 #Create FO application (if not already deployed at lesser version): -New-ServiceFabricApplication -ApplicationName fabric:/FabricObserver -ApplicationTypeName FabricObserverType -ApplicationTypeVersion 3.2.15 +New-ServiceFabricApplication -ApplicationName fabric:/FabricObserver -ApplicationTypeName FabricObserverType -ApplicationTypeVersion 3.3.0 #Create the Service instances (-1 means all nodes, which is what is required for FO): @@ -143,13 +147,15 @@ New-ServiceFabricService -Stateless -PartitionSchemeSingleton -ApplicationName f #OR if updating existing version: -Start-ServiceFabricApplicationUpgrade -ApplicationName fabric:/FabricObserver -ApplicationTypeVersion 3.2.15 -Monitored -FailureAction rollback +Start-ServiceFabricApplicationUpgrade -ApplicationName fabric:/FabricObserver -ApplicationTypeVersion 3.3.0 -Monitored -FailureAction rollback ``` ## Observer Model FO is composed of Observer objects (instance types) that are designed to observe, record, and report on several machine-level environmental conditions inside a Windows or Linux (Ubuntu) VM hosting a Service Fabric node. +**NOTE:** ```SFConfigurationObserver```, which has been deprecated for several releases has been completely removed in 3.3.0. Further, all related settings have been removed from Settings.xml and ApplicationManifest.xml. + Here are the current observers and what they monitor: | Resource | Observer | @@ -163,7 +169,6 @@ Here are the current observers and what they monitor: | Networking - general health and monitoring of availability of user-specified, per-app endpoints | NetworkObserver | | CPU/Memory/File Handles(Linux)/Firewalls(Windows)/TCP Ports usage at machine level | NodeObserver | | OS/Hardware - OS install date, OS health status, list of hot fixes, hardware configuration, AutoUpdate configuration, Ephemeral TCP port range, TCP ports in use, memory and disk space usage | OSObserver | -| Service Fabric Configuration information | SFConfigurationObserver | | **Another resource you find important** | **Observer [that you implement](./Documentation/Plugins.md)** | To learn more about the current Observers and their configuration, please see the [Observers readme](./Documentation/Observers.md). diff --git a/SampleObserverPlugin/SampleNewObserver.cs b/SampleObserverPlugin/SampleNewObserver.cs index 394d707c..01fe842a 100644 --- a/SampleObserverPlugin/SampleNewObserver.cs +++ b/SampleObserverPlugin/SampleNewObserver.cs @@ -24,34 +24,23 @@ namespace FabricObserver.Observers /// /// An observer plugin muse derive from ObserverBase and implement 2 abstract functions, ObserveAsync and ReportAsync. /// - public class SampleNewObserver : ObserverBase + /// + /// + /// + /// FabricClient instance. This is managed by FabricObserver. + /// StatelessServiceContext instance. This is managed by FabricObserver. + public class SampleNewObserver(FabricClient fabricClient, StatelessServiceContext context) : ObserverBase(fabricClient, context) { - private readonly StringBuilder message; - private readonly AsyncRetryPolicy retryPolicy; - - /// - /// - /// - /// FabricClient instance. This is managed by FabricObserver. - /// StatelessServiceContext instance. This is managed by FabricObserver. - public SampleNewObserver(FabricClient fabricClient, StatelessServiceContext context) : base(fabricClient, context) - { - message = new StringBuilder(); - - // Polly retry policy for when FabricException or TimeoutException is thrown by its Execute predicate. - // The purpose of using Polly here is to show how you deal with external (to FO) dependencies (put them all in the same folder where your - // plugin dll lives, including the dependencies, if any, of the primary dependencies you employ). - retryPolicy = Policy.Handle() + private readonly StringBuilder message = new(); + private readonly AsyncRetryPolicy retryPolicy = Policy.Handle() .Or() .WaitAndRetryAsync( - new[] - { + [ TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), - }); - } + ]); public override async Task ObserveAsync(CancellationToken token) { @@ -118,7 +107,7 @@ await retryPolicy.ExecuteAsync( ConfigurationSettings.AsyncTimeout, Token)); - apps.AddRange(appList.ToList()); + apps.AddRange([.. appList]); // Wait a second before grabbing the next batch of apps.. await Task.Delay(TimeSpan.FromSeconds(1), Token); diff --git a/SampleObserverPlugin/SampleObserverPlugin.csproj b/SampleObserverPlugin/SampleObserverPlugin.csproj index 34f1407e..29dc3507 100644 --- a/SampleObserverPlugin/SampleObserverPlugin.csproj +++ b/SampleObserverPlugin/SampleObserverPlugin.csproj @@ -1,8 +1,7 @@  - - net6.0 + + net8.0 linux-x64;win-x64 FabricObserver.Observers SampleNewObserver @@ -19,19 +18,19 @@ - - - + - + \ No newline at end of file diff --git a/TelemetryLib/TelemetryEvents.cs b/TelemetryLib/TelemetryEvents.cs index 9101f753..67106c2c 100644 --- a/TelemetryLib/TelemetryEvents.cs +++ b/TelemetryLib/TelemetryEvents.cs @@ -58,7 +58,7 @@ public bool EmitFabricObserverOperationalEvent(FabricObserverOperationalEventDat { _ = TryGetHashStringSha256(nodeName, out string nodeHashString); - IDictionary eventProperties = new Dictionary + Dictionary eventProperties = new() { { "EventName", OperationalEventName}, { "TaskName", FOTaskName}, @@ -82,7 +82,7 @@ public bool EmitFabricObserverOperationalEvent(FabricObserverOperationalEventDat } } - IDictionary metrics = new Dictionary + Dictionary metrics = new() { { "EnabledObserverCount", foData.EnabledObserverCount } }; @@ -126,9 +126,9 @@ public bool EmitFabricObserverOperationalEvent(FabricObserverOperationalEventDat // Since we log the telemetry data to disk, check to make sure we don't send the same data again across FO restarts if the data has not changed. if (File.Exists(logFilePath) && TryDeserializeFOEventData(File.ReadAllText(logFilePath), out FabricObserverOperationalEventData foEventDataFromLogFile)) { - if (foEventDataFromLogFile.ObserverData != null && foEventDataFromLogFile.ObserverData.ContainsKey(obData.Key)) + if (foEventDataFromLogFile.ObserverData != null && foEventDataFromLogFile.ObserverData.TryGetValue(obData.Key, out ObserverData value)) { - if (foEventDataFromLogFile?.ObserverData != null && foEventDataFromLogFile?.ObserverData[obData.Key]?.ServiceData?.MonitoredAppCount == data) + if (value?.ServiceData?.MonitoredAppCount == data) { addMetric = false; } @@ -153,9 +153,9 @@ public bool EmitFabricObserverOperationalEvent(FabricObserverOperationalEventDat // Since we log the telemetry data to disk, check to make sure we don't send the same data again across FO restarts if the data has not changed. if (File.Exists(logFilePath) && TryDeserializeFOEventData(File.ReadAllText(logFilePath), out FabricObserverOperationalEventData foEventDataFromLogFile)) { - if (foEventDataFromLogFile.ObserverData != null && foEventDataFromLogFile.ObserverData.ContainsKey(obData.Key)) + if (foEventDataFromLogFile.ObserverData != null && foEventDataFromLogFile.ObserverData.TryGetValue(obData.Key, out ObserverData value)) { - if (foEventDataFromLogFile.ObserverData[obData.Key].ServiceData.MonitoredServiceProcessCount == data) + if (value.ServiceData.MonitoredServiceProcessCount == data) { addMetric = false; } @@ -239,7 +239,7 @@ public bool EmitCriticalErrorEvent(CriticalErrorEventData errorData, string sour try { - IDictionary eventProperties = new Dictionary + Dictionary eventProperties = new() { { "EventName", CriticalErrorEventName}, { "TaskName", source}, @@ -338,7 +338,7 @@ public bool EmitClusterObserverOperationalEvent(ClusterObserverOperationalEventD try { - IDictionary eventProperties = new Dictionary + Dictionary eventProperties = new() { { "EventName", OperationalEventName}, { "TaskName", COTaskName}, @@ -397,16 +397,12 @@ public static bool TryGetHashStringSha256(string source, out string result) try { StringBuilder Sb = new(); + Encoding enc = Encoding.UTF8; + byte[] byteVal = SHA256.HashData(enc.GetBytes(source)); - using (var hash = SHA256.Create()) + foreach (byte b in byteVal) { - Encoding enc = Encoding.UTF8; - byte[] byteVal = hash.ComputeHash(enc.GetBytes(source)); - - foreach (byte b in byteVal) - { - Sb.Append(b.ToString("x2")); - } + Sb.Append(b.ToString("x2")); } result = Sb.ToString(); diff --git a/TelemetryLib/TelemetryLib.csproj b/TelemetryLib/TelemetryLib.csproj index a3c9f06c..556f4b59 100644 --- a/TelemetryLib/TelemetryLib.csproj +++ b/TelemetryLib/TelemetryLib.csproj @@ -1,18 +1,18 @@  - net6.0 + net8.0 Library FabricObserver.TelemetryLib TelemetryLib x64 - 2.1.0.0 - 2.1.0.0 - Copyright © 2023 + 2.2.0.0 + 2.2.0.0 + Copyright © 2024 TelemetryLib - + \ No newline at end of file diff --git a/XmlDiffPatchSF/Program.cs b/XmlDiffPatchSF/Program.cs index 4f6ddf18..10bdbb4b 100644 --- a/XmlDiffPatchSF/Program.cs +++ b/XmlDiffPatchSF/Program.cs @@ -28,7 +28,7 @@ private static void Main(string[] args) "preceding the file extension.\n\n" + "**Note, if you have observer plugins, then you must supply true for [mergeExistingNodes] as the last argument to pull over your plugin settings as part of the merge.**.\n\n" + "Example:\n\n" + - "DiffPatchXml \"C:\\repos\\FO\\3.1.26\\configs\\ApplicationManifest.xml\" \"C:\\repos\\FO\\3.2.15\\configs\\ApplicationManifest.xml\"\n"); + "DiffPatchXml \"C:\\repos\\FO\\3.1.26\\configs\\ApplicationManifest.xml\" \"C:\\repos\\FO\\3.3.0\\configs\\ApplicationManifest.xml\"\n"); return; } diff --git a/conuget.md b/conuget.md index 515e04d1..20a4a63a 100644 --- a/conuget.md +++ b/conuget.md @@ -1,7 +1,7 @@ -### ClusterObserver 2.2.8 -#### This version requires SF Runtime >= 9.0 and targets .NET 6. +### ClusterObserver 2.3.0 (.NET 8) +#### This version requires SF Runtime >= 9.1 and targets .NET 8. -[ClusterObserver (CO)](https://github.com/microsoft/service-fabric-observer/tree/main/ClusterObserver) is a stateless singleton Service Fabric .NET 6 service that runs on one node in a cluster. CO observes cluster health (aggregated) +[ClusterObserver (CO)](https://github.com/microsoft/service-fabric-observer/tree/main/ClusterObserver) is a stateless singleton Service Fabric .NET 8 application that runs on one node in a cluster. CO observes cluster health (aggregated) and sends telemetry when a cluster is in Error or Warning. CO shares a very small subset of FabricObserver's (FO) code. It is designed to be completely independent from FO sources, but lives in this repo (and SLN) because it is very useful to have both services deployed, especially for those who want cluster-level health observation and reporting in addition to the node-level user-defined resource monitoring, health event creation, and health reporting done by FO. FabricObserver is designed to generate Service Fabric health events based on user-defined resource usage Warning and Error thresholds which ClusterObserver sends to your log analytics and alerting service. @@ -32,7 +32,7 @@ $appParams = @{ "RunInterval" = "00:10:00"; "MaxTimeNodeStatusNotOk" = "04:00:00 Then execute the application upgrade with ```Powershell -Start-ServiceFabricApplicationUpgrade -ApplicationName fabric:/ClusterObserver -ApplicationTypeVersion 2.2.1.960 -ApplicationParameter $appParams -Monitored -FailureAction rollback +Start-ServiceFabricApplicationUpgrade -ApplicationName fabric:/ClusterObserver -ApplicationTypeVersion 2.3.0 -ApplicationParameter $appParams -Monitored -FailureAction rollback ``` Example Configuration: @@ -53,8 +53,7 @@ Example Configuration: **NOTE: For Linux runtime target, just supply the name of the directory (not a path with drive letter like you for Windows).** --> + will be locally logged. This is the recommended setting. --> @@ -97,8 +96,7 @@ Example Configuration: + will be locally logged. This is the recommended setting. --> diff --git a/foextlib.md b/foextlib.md index d3cbd958..ed5d3ba8 100644 --- a/foextlib.md +++ b/foextlib.md @@ -1,10 +1,10 @@ -## FabricObserver Extensibility Library 3.2.15 +## FabricObserver Extensibility Library 3.3.0 (.NET 8) -FabricObserver.Extensibility is a .NET 6 library for building custom observers that extend FabricObserver's capabilities to match your needs. A custom observer is managed just like a built-in observer. +FabricObserver.Extensibility is a .NET 8 library for building custom observers that extend FabricObserver's and ClusterObserver's capabilities to match your needs. A custom observer is managed just like a built-in observer. ### How to implement an observer using FO's extensibility model -1. Create a new .NET core library project. You should target net6.0 in your csproj because that is the target net SDK version that FabricObserver is built for. +1. Create a new .NET core library project. You should target net8.0 in your csproj because that is the target net SDK version that FabricObserver 3.3.0 is built for. 2. Install the latest Microsoft.ServiceFabricApps.FabricObserver.Extensibility nupkg from https://www.nuget.org/profiles/ServiceFabricApps into your plugin project. diff --git a/fonuget.md b/fonuget.md index bd1f40f3..9ef5a187 100644 --- a/fonuget.md +++ b/fonuget.md @@ -1,6 +1,6 @@ -## FabricObserver 3.2.15 +## FabricObserver 3.3.0 (.NET 8) -[**FabricObserver (FO)**](https://github.com/microsoft/service-fabric-observer) is a production-ready watchdog service with an easy-to-use extensibility model, written as a stateless, singleton Service Fabric **.NET 6** application that by default +[**FabricObserver (FO)**](https://github.com/microsoft/service-fabric-observer) is a production-ready watchdog service with an easy-to-use extensibility model, written as a stateless, singleton Service Fabric **.NET 8** application that by default 1. Monitors a broad range of physical machine resources that tend to be very important to all Service Fabric services and maps these metrics to the related Service Fabric entities. 2. Runs on multiple versions of Windows Server and Ubuntu. @@ -80,7 +80,6 @@ Here are the current observers and what they monitor: | Networking - general health and monitoring of availability of user-specified, per-app endpoints | NetworkObserver | | CPU/Memory/File Handles(Linux)/Firewalls(Windows)/TCP Ports usage at machine level | NodeObserver | | OS/Hardware - OS install date, OS health status, list of hot fixes, hardware configuration, AutoUpdate configuration, Ephemeral TCP port range, TCP ports in use, memory and disk space usage | OSObserver | -| Service Fabric Configuration information | SFConfigurationObserver | | **Another resource you find important** | **Observer [that you implement](https://github.com/microsoft/service-fabric-observer/blob/main/Documentation/Plugins.md)** | To learn more about the current Observers and their configuration, please see the [Observers readme](https://github.com/microsoft/service-fabric-observer/blob/main/Documentation/Observers.md).