//****************************************************************************************************** // TimeSeriesStartupOperations.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 02/14/2012 - Stephen C. Wills // Generated original version of source code. // 12/20/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Data; using System.Linq; using GSF.Data; using GSF.Identity; // ReSharper disable UnusedParameter.Local // ReSharper disable NotAccessedField.Local // ReSharper disable UnusedMember.Local namespace GSF.TimeSeries { /// /// Defines a data operations to be performed at startup. /// public static class TimeSeriesStartupOperations { // Messaging to the service private static Action s_statusMessage; private static Action s_processException; /// /// Delegates control to the data operations that are to be performed at startup. /// private static void PerformTimeSeriesStartupOperations(AdoDataConnection database, string nodeIDQueryString, ulong trackingVersion, string arguments, Action statusMessage, Action processException) { // Set up messaging to the service s_statusMessage = statusMessage; s_processException = processException; // Run data operations ValidateDefaultNode(database, nodeIDQueryString); ValidateAdapterCollections(database, nodeIDQueryString); ValidateActiveMeasurements(database, nodeIDQueryString); ValidateAccountsAndGroups(database, nodeIDQueryString); ValidateDataPublishers(database, nodeIDQueryString, arguments); ValidateStatistics(database, nodeIDQueryString); ValidateAlarming(database, nodeIDQueryString); } /// /// Data operation to validate and ensure there is a node in the database. /// private static void ValidateDefaultNode(AdoDataConnection database, string nodeIDQueryString) { // Queries const string NodeCountFormat = "SELECT COUNT(*) FROM Node"; const string NodeInsertFormat = "INSERT INTO Node(Name, CompanyID, Description, Settings, MenuType, MenuData, Master, LoadOrder, Enabled) " + "VALUES('Default', NULL, 'Default node', 'RemoteStatusServerConnectionString={server=localhost:8500};datapublisherport=6165;RealTimeStatisticServiceUrl=http://localhost:6052/historian', " + "'File', 'Menu.xml', 1, 0, 1)"; const string NodeUpdateFormat = "UPDATE Node SET ID = {0}"; // Determine whether the node exists in the database and create it if it doesn't. int nodeCount = Convert.ToInt32(database.Connection.ExecuteScalar(NodeCountFormat)); if (nodeCount == 0) { database.Connection.ExecuteNonQuery(NodeInsertFormat); database.Connection.ExecuteNonQuery(string.Format(NodeUpdateFormat, nodeIDQueryString)); } } /// /// Data operation to validate and ensure there is a record in the /// ConfigurationEntity table for each of the adapter collections. /// private static void ValidateAdapterCollections(AdoDataConnection database, string nodeIDQueryString) { const string ConfigEntityCountFormat = "SELECT COUNT(*) FROM ConfigurationEntity WHERE RuntimeName = {0}"; const string ConfigEntityInsertFormat = "INSERT INTO ConfigurationEntity(SourceName, RuntimeName, Description, LoadOrder, Enabled) VALUES({0}, {1}, {2}, {3}, 1)"; string[] sourceNames = { "IaonFilterAdapter", "IaonInputAdapter", "IaonActionAdapter", "IaonOutputAdapter" }; string[] runtimeNames = { "FilterAdapters", "InputAdapters", "ActionAdapters", "OutputAdapters" }; string[] descriptions = { "Defines IFilterAdapter definitions for a PDC node", "Defines IInputAdapter definitions for a PDC node", "Defines IActionAdapter definitions for a PDC node", "Defines IOutputAdapter definitions for a PDC node" }; for (int i = 0; i < runtimeNames.Length; i++) { string sourceName = sourceNames[i]; string runtimeName = runtimeNames[i]; string description = descriptions[i]; int loadOrder = i + 1; if (database.ExecuteScalar(ConfigEntityCountFormat, runtimeName) == 0) database.ExecuteNonQuery(ConfigEntityInsertFormat, sourceName, runtimeName, description, loadOrder); } } /// /// Data operation to validate and ensure there is a record /// in the ConfigurationEntity table for ActiveMeasurements. /// private static void ValidateActiveMeasurements(AdoDataConnection database, string nodeIDQueryString) { const string MeasurementConfigEntityCountFormat = "SELECT COUNT(*) FROM ConfigurationEntity WHERE RuntimeName = 'ActiveMeasurements'"; const string MeasurementConfigEntityInsertFormat = "INSERT INTO ConfigurationEntity(SourceName, RuntimeName, Description, LoadOrder, Enabled) VALUES('ActiveMeasurement', 'ActiveMeasurements', 'Defines active system measurements for a TSF node', 4, 1)"; int measurementConfigEntityCount = Convert.ToInt32(database.Connection.ExecuteScalar(MeasurementConfigEntityCountFormat)); if (measurementConfigEntityCount == 0) database.Connection.ExecuteNonQuery(MeasurementConfigEntityInsertFormat); } /// /// Data operation to validate accounts and groups to ensure /// that account names and group names are converted to SIDs. /// private static void ValidateAccountsAndGroups(AdoDataConnection database, string nodeIDQueryString) { const string SelectUserAccountQuery = "SELECT ID, Name, UseADAuthentication FROM UserAccount"; const string SelectSecurityGroupQuery = "SELECT ID, Name FROM SecurityGroup"; const string UpdateUserAccountFormat = "UPDATE UserAccount SET Name = '{0}' WHERE ID = '{1}'"; const string UpdateSecurityGroupFormat = "UPDATE SecurityGroup SET Name = '{0}' WHERE ID = '{1}'"; string id; string sid; string accountName; Dictionary updateMap = new Dictionary(); // Find user accounts that need to be updated using (IDataReader userAccountReader = database.Connection.ExecuteReader(SelectUserAccountQuery)) { while (userAccountReader.Read()) { id = userAccountReader["ID"].ToNonNullString(); accountName = userAccountReader["Name"].ToNonNullString(); if (userAccountReader["UseADAuthentication"].ToNonNullString().ParseBoolean()) { sid = UserInfo.UserNameToSID(accountName); if (!ReferenceEquals(accountName, sid) && UserInfo.IsUserSID(sid)) updateMap.Add(id, sid); } } } // Update user accounts foreach (KeyValuePair pair in updateMap) database.Connection.ExecuteNonQuery(string.Format(UpdateUserAccountFormat, pair.Value, pair.Key)); updateMap.Clear(); // Find security groups that need to be updated using (IDataReader securityGroupReader = database.Connection.ExecuteReader(SelectSecurityGroupQuery)) { while (securityGroupReader.Read()) { id = securityGroupReader["ID"].ToNonNullString(); accountName = securityGroupReader["Name"].ToNonNullString(); if (accountName.Contains('\\')) { sid = UserInfo.GroupNameToSID(accountName); if (!ReferenceEquals(accountName, sid) && UserInfo.IsGroupSID(sid)) updateMap.Add(id, sid); } } } // Update security groups foreach (KeyValuePair pair in updateMap) database.Connection.ExecuteNonQuery(string.Format(UpdateSecurityGroupFormat, pair.Value, pair.Key)); } /// /// Data operation to validate and ensure there is a record in the /// CustomActionAdapter table for the external and TLS data publishers. /// private static void ValidateDataPublishers(AdoDataConnection database, string nodeIDQueryString, string arguments) { const string DataPublisherCountFormat = "SELECT COUNT(*) FROM CustomActionAdapter WHERE AdapterName='{0}!DATAPUBLISHER' AND NodeID = {1}"; const string GEPDataPublisherInsertFormat = "INSERT INTO CustomActionAdapter(NodeID, AdapterName, AssemblyName, TypeName, ConnectionString, Enabled) VALUES({0}, '{1}!DATAPUBLISHER', 'GSF.TimeSeries.dll', 'GSF.TimeSeries.Transport.DataPublisher', 'securityMode={2}; allowSynchronizedSubscription=false; useBaseTimeOffsets=true; {3}', {4})"; const string STTPDataPublisherInsertFormat = "INSERT INTO CustomActionAdapter(NodeID, AdapterName, AssemblyName, TypeName, ConnectionString, Enabled) VALUES({0}, '{1}!DATAPUBLISHER', 'sttp.gsf.dll', 'sttp.DataPublisher', 'securityMode={2}; {3}', {4})"; bool internalDataPublisherEnabled = true; bool externalDataPublisherEnabled = true; bool tlsDataPublisherEnabled = true; bool sttpDataPublisherEnabled = true; bool sttpsDataPublisherEnabled = true; if (!string.IsNullOrEmpty(arguments)) { Dictionary kvps = arguments.ParseKeyValuePairs(); if (kvps.TryGetValue("internalDataPublisherEnabled", out string value)) internalDataPublisherEnabled = value.ParseBoolean(); if (kvps.TryGetValue("externalDataPublisherEnabled", out value)) externalDataPublisherEnabled = value.ParseBoolean(); if (kvps.TryGetValue("tlsDataPublisherEnabled", out value)) tlsDataPublisherEnabled = value.ParseBoolean(); if (kvps.TryGetValue("sttpDataPublisherEnabled", out value)) sttpDataPublisherEnabled = value.ParseBoolean(); if (kvps.TryGetValue("sttpsDataPublisherEnabled", out value)) sttpsDataPublisherEnabled = value.ParseBoolean(); } int internalDataPublisherCount = Convert.ToInt32(database.Connection.ExecuteScalar(string.Format(DataPublisherCountFormat, "INTERNAL", nodeIDQueryString))); int externalDataPublisherCount = Convert.ToInt32(database.Connection.ExecuteScalar(string.Format(DataPublisherCountFormat, "EXTERNAL", nodeIDQueryString))); int tlsDataPublisherCount = Convert.ToInt32(database.Connection.ExecuteScalar(string.Format(DataPublisherCountFormat, "TLS", nodeIDQueryString))); int sttpDataPublisherCount = Convert.ToInt32(database.Connection.ExecuteScalar(string.Format(DataPublisherCountFormat, "STTP", nodeIDQueryString))); int sttpsDataPublisherCount = Convert.ToInt32(database.Connection.ExecuteScalar(string.Format(DataPublisherCountFormat, "STTPS", nodeIDQueryString))); if (internalDataPublisherCount == 0) database.Connection.ExecuteNonQuery(string.Format(GEPDataPublisherInsertFormat, nodeIDQueryString, "INTERNAL", "None", "cacheMeasurementKeys={FILTER ActiveMeasurements WHERE SignalType = ''STAT''}", internalDataPublisherEnabled ? 1 : 0)); if (externalDataPublisherCount == 0) database.Connection.ExecuteNonQuery(string.Format(GEPDataPublisherInsertFormat, nodeIDQueryString, "EXTERNAL", "Gateway", "", externalDataPublisherEnabled ? 1 : 0)); if (tlsDataPublisherCount == 0) database.Connection.ExecuteNonQuery(string.Format(GEPDataPublisherInsertFormat, nodeIDQueryString, "TLS", "TLS", "", tlsDataPublisherEnabled ? 1 : 0)); if (sttpDataPublisherCount == 0) database.Connection.ExecuteNonQuery(string.Format(STTPDataPublisherInsertFormat, nodeIDQueryString, "STTP", "None", "cachedMeasurementExpression={FILTER ActiveMeasurements WHERE SignalType = ''STAT''}", sttpDataPublisherEnabled ? 1 : 0)); if (sttpsDataPublisherCount == 0) database.Connection.ExecuteNonQuery(string.Format(STTPDataPublisherInsertFormat, nodeIDQueryString, "STTPS", "TLS", "", sttpsDataPublisherEnabled ? 1 : 0)); } /// /// Data operation to validate and ensure that certain records that /// are required for statistics calculations exist in the database. /// private static void ValidateStatistics(AdoDataConnection database, string nodeIDQueryString) { // SELECT queries const string StatConfigEntityCountFormat = "SELECT COUNT(*) FROM ConfigurationEntity WHERE RuntimeName = 'Statistics'"; const string StatSignalTypeCountFormat = "SELECT COUNT(*) FROM SignalType WHERE Acronym = 'STAT'"; const string StatHistorianCountFormat = "SELECT COUNT(*) FROM Historian WHERE Acronym = 'STAT' AND NodeID = {0}"; const string StatEngineCountFormat = "SELECT COUNT(*) FROM CustomActionAdapter WHERE AdapterName = 'STATISTIC!SERVICES' AND NodeID = {0}"; const string SystemStatCountFormat = "SELECT COUNT(*) FROM Statistic WHERE Source = 'System' AND AssemblyName = 'GSF.TimeSeries.dll'"; const string DeviceStatCountFormat = "SELECT COUNT(*) FROM Statistic WHERE Source = 'Device' AND AssemblyName = 'GSF.TimeSeries.dll'"; const string SubscriberStatCountFormat = "SELECT COUNT(*) FROM Statistic WHERE Source = 'Subscriber' AND AssemblyName = 'GSF.TimeSeries.dll'"; const string PublisherStatCountFormat = "SELECT COUNT(*) FROM Statistic WHERE Source = 'Publisher' AND AssemblyName = 'GSF.TimeSeries.dll'"; const string ProcessStatCountFormat = "SELECT COUNT(*) FROM Statistic WHERE Source = 'Process' AND AssemblyName = 'FileAdapters.dll'"; // INSERT queries const string StatConfigEntityInsertFormat = "INSERT INTO ConfigurationEntity(SourceName, RuntimeName, Description, LoadOrder, Enabled) VALUES('RuntimeStatistic', 'Statistics', 'Defines statistics that are monitored for the system, devices, and output streams', 11, 1)"; const string StatSignalTypeInsertFormat = "INSERT INTO SignalType(Name, Acronym, Suffix, Abbreviation, Source, EngineeringUnits) VALUES('Statistic', 'STAT', 'ST', 'P', 'Any', '')"; const string StatHistorianInsertFormat = "INSERT INTO Historian(NodeID, Acronym, Name, AssemblyName, TypeName, ConnectionString, IsLocal, Description, LoadOrder, Enabled) VALUES({0}, 'STAT', 'Statistics Archive', 'TestingAdapters.dll', 'TestingAdapters.VirtualOutputAdapter', '', 1, 'Local historian used to archive system statistics', 9999, 1)"; const string StatEngineInsertFormat = "INSERT INTO CustomActionAdapter(NodeID, AdapterName, AssemblyName, TypeName, LoadOrder, Enabled) VALUES({0}, 'STATISTIC!SERVICES', 'GSF.TimeSeries.dll', 'GSF.TimeSeries.Statistics.StatisticsEngine', 0, 1)"; const string SystemStatInsertFormat = "INSERT INTO Statistic(Source, SignalIndex, Name, Description, AssemblyName, TypeName, MethodName, Arguments, Enabled, DataType, DisplayFormat, IsConnectedState, LoadOrder) VALUES('System', {0}, '{1}', '{2}', 'GSF.TimeSeries.dll', 'GSF.TimeSeries.Statistics.PerformanceStatistics', 'GetSystemStatistic_{3}', '', 1, '{4}', '{5}', 0, {0})"; const string DeviceStatInsertFormat = "INSERT INTO Statistic(Source, SignalIndex, Name, Description, AssemblyName, TypeName, MethodName, Arguments, Enabled, DataType, DisplayFormat, IsConnectedState, LoadOrder) VALUES('Device', {0}, '{1}', '{2}', 'GSF.TimeSeries.dll', 'GSF.TimeSeries.Statistics.DeviceStatistics', 'GetDeviceStatistic_{3}', '', 1, '{4}', '{5}', 0, {0})"; const string SubscriberStatInsertFormat = "INSERT INTO Statistic(Source, SignalIndex, Name, Description, AssemblyName, TypeName, MethodName, Arguments, Enabled, DataType, DisplayFormat, IsConnectedState, LoadOrder) VALUES('Subscriber', {0}, '{1}', '{2}', 'GSF.TimeSeries.dll', 'GSF.TimeSeries.Statistics.GatewayStatistics', 'GetSubscriberStatistic_{3}', '', 1, '{4}', '{5}', {6}, {0})"; const string PublisherStatInsertFormat = "INSERT INTO Statistic(Source, SignalIndex, Name, Description, AssemblyName, TypeName, MethodName, Arguments, Enabled, DataType, DisplayFormat, IsConnectedState, LoadOrder) VALUES('Publisher', {0}, '{1}', '{2}', 'GSF.TimeSeries.dll', 'GSF.TimeSeries.Statistics.GatewayStatistics', 'GetPublisherStatistic_{3}', '', 1, '{4}', '{5}', {6}, {0})"; const string ProcessStatInsertFormat = "INSERT INTO Statistic(Source, SignalIndex, Name, Description, AssemblyName, TypeName, MethodName, Arguments, Enabled, DataType, DisplayFormat, IsConnectedState, LoadOrder) VALUES('Process', {0}, '{1}', '{2}', 'FileAdapters.dll', 'FileAdapters.ProcessLauncher', 'GetProcessStatistic_{3}', '', 1, 'System.Double', '{{0:N3}}', 0, {0})"; // DELETE queries const string SystemStatisticDeleteFormat = "DELETE FROM Statistic WHERE Source = 'System' AND SignalIndex <= {0}"; const string DeviceStatisticDeleteFormat = "DELETE FROM Statistic WHERE Source = 'Device' AND SignalIndex <= {0}"; const string SubscriberStatisticDeleteFormat = "DELETE FROM Statistic WHERE Source = 'Subscriber' AND SignalIndex <= {0}"; const string PublisherStatisticDeleteFormat = "DELETE FROM Statistic WHERE Source = 'Publisher' AND SignalIndex <= {0}"; const string ProcessStatisticDeleteFormat = "DELETE FROM Statistic WHERE Source = 'Process' AND SignalIndex <= {0}"; // Names and descriptions for each of the statistics // NOTE: !! The statistic names defined in the following array are used to define associated function names (minus spaces) - as a result, do *not* leisurely change these statistic names without understanding the consequences string[] SystemStatNames = { /* 01 */ "CPU Usage", /* 02 */ "Average CPU Usage", /* 03 */ "Memory Usage", /* 04 */ "Average Memory Usage", /* 05 */ "Thread Count", /* 06 */ "Average Thread Count", /* 07 */ "Threading Contention Rate", /* 08 */ "Average Threading Contention Rate", /* 09 */ "IO Usage", /* 10 */ "Average IO Usage", /* 11 */ "IP Data Send Rate", /* 12 */ "Average IP Data Send Rate", /* 13 */ "IP Data Receive Rate", /* 14 */ "Average IP Data Receive Rate", /* 15 */ "Up Time", /* 16 */ "System CPU Usage", /* 17 */ "Average System CPU Usage", /* 18 */ "Available System Memory", /* 19 */ "Average Available System Memory", /* 20 */ "System Memory Usage", /* 21 */ "Average Device Time", /* 22 */ "Minimum Device Time", /* 23 */ "Maximum Device Time", /* 24 */ "System Time Deviation From Average", /* 25 */ "Primary Disk Usage" }; string[] SystemStatDescriptions = { /* 01 */ "Percentage of CPU currently used by this process.", /* 02 */ "Average percentage of CPU used by this process.", /* 03 */ "Amount of memory currently used by this process in megabytes.", /* 04 */ "Average amount of memory used by this process in megabytes.", /* 05 */ "Number of threads currently used by this process.", /* 06 */ "Average number of threads used by this process.", /* 07 */ "Current thread lock contention rate in attempts per second.", /* 08 */ "Average thread lock contention rate in attempts per second.", /* 09 */ "Amount of IO currently used by this process in kilobytes per second.", /* 10 */ "Average amount of IO used by this process in kilobytes per second.", /* 11 */ "Number of IP datagrams (or bytes on Mono) currently sent by this process per second.", /* 12 */ "Average number of IP datagrams (or bytes on Mono) sent by this process per second.", /* 13 */ "Number of IP datagrams (or bytes on Mono) currently received by this process per second.", /* 14 */ "Average number of IP datagrams (or bytes on Mono) received by this process per second.", /* 15 */ "Total number of seconds system has been running.", /* 16 */ "Percentage of total CPU currently used by the host system.", /* 17 */ "Average percentage of total CPU used by the host system.", /* 18 */ "Amount of memory available on the host system in gigabytes", /* 19 */ "Average amount of memory available on the host system in gigabytes", /* 20 */ "Percentage of memory currently used on the host system", /* 21 */ "Average time for all input devices", /* 22 */ "Minimum time for all input devices", /* 23 */ "Maximum time for all input devices", /* 24 */ "System time deviation from average for all input devices in seconds", /* 25 */ "Percentage of primary disk (C: or /) usage on the host system" }; string[] SystemStatTypes = { /* 01 */ "System.Double", /* 02 */ "System.Double", /* 03 */ "System.Double", /* 04 */ "System.Double", /* 05 */ "System.Int32", /* 06 */ "System.Double", /* 07 */ "System.Double", /* 08 */ "System.Double", /* 09 */ "System.Double", /* 10 */ "System.Double", /* 11 */ "System.Double", /* 12 */ "System.Double", /* 13 */ "System.Double", /* 14 */ "System.Double", /* 15 */ "System.Double", /* 16 */ "System.Double", /* 17 */ "System.Double", /* 18 */ "System.Double", /* 19 */ "System.Double", /* 20 */ "System.Double", /* 21 */ "GSF.UnixTimeTag", /* 22 */ "GSF.UnixTimeTag", /* 23 */ "GSF.UnixTimeTag", /* 24 */ "System.Double", /* 25 */ "System.Double" }; string[] SystemStatFormats = { /* 01 */ "{0:N3} %", /* 02 */ "{0:N3} %", /* 03 */ "{0:N3} MB", /* 04 */ "{0:N3} MB", /* 05 */ "{0:N0}", /* 06 */ "{0:N3}", /* 07 */ "{0:N3}", /* 08 */ "{0:N3}", /* 09 */ "{0:N3} KBps", /* 10 */ "{0:N3} KBps", /* 11 */ "{0:N3}", /* 12 */ "{0:N3}", /* 13 */ "{0:N3}", /* 14 */ "{0:N3}", /* 15 */ "{0:N3} s", /* 16 */ "{0:N3} %", /* 17 */ "{0:N3} %", /* 18 */ "{0:N3} GB", /* 19 */ "{0:N3} GB", /* 20 */ "{0:N3} %", /* 21 */ "{0:yyyy''-''MM''-''dd'' ''HH'':''mm'':''ss''.''fff}", /* 22 */ "{0:yyyy''-''MM''-''dd'' ''HH'':''mm'':''ss''.''fff}", /* 23 */ "{0:yyyy''-''MM''-''dd'' ''HH'':''mm'':''ss''.''fff}", /* 24 */ "{0:N3} s", /* 25 */ "{0:N3} %" }; // NOTE: !! The statistic names defined in the following array are used to define associated function names (minus spaces) - as a result, do *not* leisurely change these statistic names without understanding the consequences string[] DeviceStatNames = { /* 01 */ "Data Quality Errors", /* 02 */ "Time Quality Errors", /* 03 */ "Device Errors", /* 04 */ "Measurements Received", /* 05 */ "Measurements Expected", /* 06 */ "Measurements With Error", /* 07 */ "Measurements Defined", /* 08 */ "Device Time Deviation From Average" }; string[] DeviceStatDescriptions = { /* 01 */ "Number of data quality errors reported by device during last reporting interval.", /* 02 */ "Number of time quality errors reported by device during last reporting interval.", /* 03 */ "Number of device errors reported by device during last reporting interval.", /* 04 */ "Number of measurements received from device during last reporting interval.", /* 05 */ "Expected number of measurements received from device during last reporting interval.", /* 06 */ "Number of measurements received while device was reporting errors during last reporting interval.", /* 07 */ "Number of defined measurements from device during last reporting interval.", /* 08 */ "Device time deviation from average for all input devices in seconds" }; string[] DeviceStatTypes = { /* 01 */ "System.Int32", /* 02 */ "System.Int32", /* 03 */ "System.Int32", /* 04 */ "System.Int32", /* 05 */ "System.Int32", /* 06 */ "System.Int32", /* 07 */ "System.Int32", /* 08 */ "System.Double" }; string[] DeviceStatFormats = { /* 01 */ "{0:N0}", /* 02 */ "{0:N0}", /* 03 */ "{0:N0}", /* 04 */ "{0:N0}", /* 05 */ "{0:N0}", /* 06 */ "{0:N0}", /* 07 */ "{0:N0}", /* 08 */ "{0:N3} s" }; string[] SubscriberStatNames = { /* 01 */ "Subscriber Connected", /* 02 */ "Subscriber Authenticated", /* 03 */ "Processed Measurements", /* 04 */ "Total Bytes Received", /* 05 */ "Authorized Signal Count", /* 06 */ "Unauthorized Signal Count", /* 07 */ "Lifetime Measurements", /* 08 */ "Lifetime Bytes Received", /* 09 */ "Minimum Measurements Per Second", /* 10 */ "Maximum Measurements Per Second", /* 11 */ "Average Measurements Per Second", /* 12 */ "Lifetime Minimum Latency", /* 13 */ "Lifetime Maximum Latency", /* 14 */ "Lifetime Average Latency", /* 15 */ "Up Time", /* 16 */ "TLS Secured Channel" }; string[] SubscriberStatDescriptions = { /* 01 */ "Boolean value representing if the subscriber was continually connected during last reporting interval.", /* 02 */ "Boolean value representing if the subscriber was authenticated to the publisher during last reporting interval.", /* 03 */ "Number of processed measurements reported by the subscriber during last reporting interval.", /* 04 */ "Number of bytes received from subscriber during last reporting interval.", /* 05 */ "Number of signals authorized to the subscriber by the publisher.", /* 06 */ "Number of signals denied to the subscriber by the publisher.", /* 07 */ "Number of processed measurements reported by the subscriber during the lifetime of the subscriber.", /* 08 */ "Number of bytes received from subscriber during the lifetime of the subscriber.", /* 09 */ "The minimum number of measurements received per second during the last reporting interval.", /* 10 */ "The maximum number of measurements received per second during the last reporting interval.", /* 11 */ "The average number of measurements received per second during the last reporting interval.", /* 12 */ "Minimum latency from output stream, in milliseconds, during the lifetime of the subscriber.", /* 13 */ "Maximum latency from output stream, in milliseconds, during the lifetime of the subscriber.", /* 14 */ "Average latency from output stream, in milliseconds, during the lifetime of the subscriber.", /* 15 */ "Total number of seconds subscriber has been running.", /* 16 */ "Boolean value representing if subscriber command channel has transport layer security enabled" }; string[] SubscriberStatMethodSuffix = { /* 01 */ "Connected", /* 02 */ "Authenticated", /* 03 */ "ProcessedMeasurements", /* 04 */ "TotalBytesReceived", /* 05 */ "AuthorizedCount", /* 06 */ "UnauthorizedCount", /* 07 */ "LifetimeMeasurements", /* 08 */ "LifetimeBytesReceived", /* 09 */ "MinimumMeasurementsPerSecond", /* 10 */ "MaximumMeasurementsPerSecond", /* 11 */ "AverageMeasurementsPerSecond", /* 12 */ "LifetimeMinimumLatency", /* 13 */ "LifetimeMaximumLatency", /* 14 */ "LifetimeAverageLatency", /* 15 */ "UpTime", /* 16 */ "TLSSecuredChannel" }; string[] SubscriberStatTypes = { /* 01 */ "System.Boolean", /* 02 */ "System.Boolean", /* 03 */ "System.Int32", /* 04 */ "System.Int32", /* 05 */ "System.Int32", /* 06 */ "System.Int32", /* 07 */ "System.Int64", /* 08 */ "System.Int64", /* 09 */ "System.Int32", /* 10 */ "System.Int32", /* 11 */ "System.Int32", /* 12 */ "System.Int32", /* 13 */ "System.Int32", /* 14 */ "System.Int32", /* 15 */ "System.Double", /* 16 */ "System.Boolean" }; string[] SubscriberStatFormats = { /* 01 */ "{0}", /* 02 */ "{0}", /* 03 */ "{0:N0}", /* 04 */ "{0:N0}", /* 05 */ "{0:N0}", /* 06 */ "{0:N0}", /* 07 */ "{0:N0}", /* 08 */ "{0:N0}", /* 09 */ "{0:N0}", /* 10 */ "{0:N0}", /* 11 */ "{0:N0}", /* 12 */ "{0:N0} ms", /* 13 */ "{0:N0} ms", /* 14 */ "{0:N0} ms", /* 15 */ "{0:N3} s", /* 16 */ "{0}" }; string[] PublisherStatNames = { /* 01 */ "Publisher Connected", /* 02 */ "Connected Clients", /* 03 */ "Processed Measurements", /* 04 */ "Total Bytes Sent", /* 05 */ "Lifetime Measurements", /* 06 */ "Lifetime Bytes Sent", /* 07 */ "Minimum Measurements Per Second", /* 08 */ "Maximum Measurements Per Second", /* 09 */ "Average Measurements Per Second", /* 10 */ "Lifetime Minimum Latency", /* 11 */ "Lifetime Maximum Latency", /* 12 */ "Lifetime Average Latency", /* 13 */ "Up Time", /* 14 */ "TLS Secured Channel" }; string[] PublisherStatDescriptions = { /* 01 */ "Boolean value representing if the publisher was continually connected during last reporting interval.", /* 02 */ "Number of clients connected to the command channel of the publisher during last reporting interval.", /* 03 */ "Number of processed measurements reported by the publisher during last reporting interval.", /* 04 */ "Number of bytes sent by the publisher during the last reporting interval.", /* 05 */ "Number of processed measurements reported by the publisher during the lifetime of the publisher.", /* 06 */ "Number of bytes sent by the publisher during the lifetime of the publisher.", /* 07 */ "The minimum number of measurements sent per second during the last reporting interval.", /* 08 */ "The maximum number of measurements sent per second during the last reporting interval.", /* 09 */ "The average number of measurements sent per second during the last reporting interval.", /* 10 */ "Minimum latency from output stream, in milliseconds, during the lifetime of the publisher.", /* 11 */ "Maximum latency from output stream, in milliseconds, during the lifetime of the publisher.", /* 12 */ "Average latency from output stream, in milliseconds, during the lifetime of the publisher.", /* 13 */ "Total number of seconds publisher has been running.", /* 14 */ "Boolean value representing if publisher command channel has transport layer security enabled" }; string[] PublisherStatMethodSuffix = { /* 01 */ "Connected", /* 02 */ "ConnectedClientCount", /* 03 */ "ProcessedMeasurements", /* 04 */ "TotalBytesSent", /* 05 */ "LifetimeMeasurements", /* 06 */ "LifetimeBytesSent", /* 07 */ "MinimumMeasurementsPerSecond", /* 08 */ "MaximumMeasurementsPerSecond", /* 09 */ "AverageMeasurementsPerSecond", /* 10 */ "LifetimeMinimumLatency", /* 11 */ "LifetimeMaximumLatency", /* 12 */ "LifetimeAverageLatency", /* 13 */ "UpTime", /* 14 */ "TLSSecuredChannel" }; string[] PublisherStatTypes = { /* 01 */ "System.Boolean", /* 02 */ "System.Int32", /* 03 */ "System.Int32", /* 04 */ "System.Int32", /* 05 */ "System.Int64", /* 06 */ "System.Int64", /* 07 */ "System.Int32", /* 08 */ "System.Int32", /* 09 */ "System.Int32", /* 10 */ "System.Int32", /* 11 */ "System.Int32", /* 12 */ "System.Int32", /* 13 */ "System.Double", /* 14 */ "System.Boolean" }; string[] PublisherStatFormats = { /* 01 */ "{0}", /* 02 */ "{0:N0}", /* 03 */ "{0:N0}", /* 04 */ "{0:N0}", /* 05 */ "{0:N0}", /* 06 */ "{0:N0}", /* 07 */ "{0:N0}", /* 08 */ "{0:N0}", /* 09 */ "{0:N0}", /* 10 */ "{0:N0} ms", /* 11 */ "{0:N0} ms", /* 12 */ "{0:N0} ms", /* 13 */ "{0:N3} s", /* 14 */ "{0}" }; // NOTE: !! The statistic names defined in the following array are used to define associated function names (minus spaces) - as a result, do *not* leisurely change these statistic names without understanding the consequences string[] ProcessStatNames = { /* 01 */ "CPU Usage", /* 02 */ "Memory Usage", /* 03 */ "Up Time" }; string[] ProcessStatDescriptions = { /* 01 */ "Percentage of CPU currently used by the launched process.", /* 02 */ "Amount of memory currently used by the launched process in megabytes.", /* 03 */ "Total number of seconds the launched process has been running." }; // Query for count values to ensure existence of these records int statConfigEntityCount = Convert.ToInt32(database.Connection.ExecuteScalar(StatConfigEntityCountFormat)); int statSignalTypeCount = Convert.ToInt32(database.Connection.ExecuteScalar(StatSignalTypeCountFormat)); int statHistorianCount = Convert.ToInt32(database.Connection.ExecuteScalar(string.Format(StatHistorianCountFormat, nodeIDQueryString))); int statEngineCount = Convert.ToInt32(database.Connection.ExecuteScalar(string.Format(StatEngineCountFormat, nodeIDQueryString))); int systemStatCount = Convert.ToInt32(database.Connection.ExecuteScalar(SystemStatCountFormat)); int deviceStatCount = Convert.ToInt32(database.Connection.ExecuteScalar(DeviceStatCountFormat)); int subscriberStatCount = Convert.ToInt32(database.Connection.ExecuteScalar(SubscriberStatCountFormat)); int publisherStatCount = Convert.ToInt32(database.Connection.ExecuteScalar(PublisherStatCountFormat)); int processStatCount = Convert.ToInt32(database.Connection.ExecuteScalar(ProcessStatCountFormat)); // Statistic info for inserting statistics int signalIndex; string statName; string statDescription; string statMethodSuffix; string statType; string statFormat; // Ensure that STAT signal type exists if (statSignalTypeCount == 0) database.Connection.ExecuteNonQuery(StatSignalTypeInsertFormat); // Ensure that statistics configuration entity record exists if (statConfigEntityCount == 0) database.Connection.ExecuteNonQuery(StatConfigEntityInsertFormat); // Ensure that statistics historian exists if (statHistorianCount == 0) database.Connection.ExecuteNonQuery(string.Format(StatHistorianInsertFormat, nodeIDQueryString)); // Ensure that statistics engine exists if (statEngineCount == 0) database.Connection.ExecuteNonQuery(string.Format(StatEngineInsertFormat, nodeIDQueryString)); // Ensure that system statistics exist if (systemStatCount < SystemStatNames.Length) { database.Connection.ExecuteNonQuery(string.Format(SystemStatisticDeleteFormat, SystemStatNames.Length)); for (int i = 0; i < SystemStatNames.Length; i++) { signalIndex = i + 1; statName = SystemStatNames[i]; statDescription = SystemStatDescriptions[i]; statMethodSuffix = statName.Replace(" ", ""); statType = SystemStatTypes[i]; statFormat = SystemStatFormats[i]; database.Connection.ExecuteNonQuery(string.Format(SystemStatInsertFormat, signalIndex, statName, statDescription, statMethodSuffix, statType, statFormat)); } } // Ensure that system statistics exist if (deviceStatCount < DeviceStatNames.Length) { database.Connection.ExecuteNonQuery(string.Format(DeviceStatisticDeleteFormat, DeviceStatNames.Length)); for (int i = 0; i < DeviceStatNames.Length; i++) { signalIndex = i + 1; statName = DeviceStatNames[i]; statDescription = DeviceStatDescriptions[i]; statMethodSuffix = statName.Replace(" ", ""); statType = DeviceStatTypes[i]; statFormat = DeviceStatFormats[i]; database.Connection.ExecuteNonQuery(string.Format(DeviceStatInsertFormat, signalIndex, statName, statDescription, statMethodSuffix, statType, statFormat)); } } // Ensure that subscriber statistics exist if (subscriberStatCount < SubscriberStatNames.Length) { database.Connection.ExecuteNonQuery(string.Format(SubscriberStatisticDeleteFormat, SubscriberStatNames.Length)); for (int i = 0; i < SubscriberStatNames.Length; i++) { signalIndex = i + 1; statName = SubscriberStatNames[i]; statDescription = SubscriberStatDescriptions[i]; statMethodSuffix = SubscriberStatMethodSuffix[i]; statType = SubscriberStatTypes[i]; statFormat = SubscriberStatFormats[i]; database.Connection.ExecuteNonQuery(string.Format(SubscriberStatInsertFormat, signalIndex, statName, statDescription, statMethodSuffix, statType, statFormat, signalIndex == 1 ? 1 : 0)); } } // Ensure that publisher statistics exist if (publisherStatCount < PublisherStatNames.Length) { database.Connection.ExecuteNonQuery(string.Format(PublisherStatisticDeleteFormat, PublisherStatNames.Length)); for (int i = 0; i < PublisherStatNames.Length; i++) { signalIndex = i + 1; statName = PublisherStatNames[i]; statDescription = PublisherStatDescriptions[i]; statMethodSuffix = PublisherStatMethodSuffix[i]; statType = PublisherStatTypes[i]; statFormat = PublisherStatFormats[i]; database.Connection.ExecuteNonQuery(string.Format(PublisherStatInsertFormat, signalIndex, statName, statDescription, statMethodSuffix, statType, statFormat, signalIndex == 1 ? 1 : 0)); } } // Ensure that process statistics exist if (processStatCount < ProcessStatNames.Length) { database.Connection.ExecuteNonQuery(string.Format(ProcessStatisticDeleteFormat, ProcessStatNames.Length)); for (int i = 0; i < ProcessStatNames.Length; i++) { signalIndex = i + 1; statName = ProcessStatNames[i]; statDescription = ProcessStatDescriptions[i]; statMethodSuffix = statName.Replace(" ", ""); database.Connection.ExecuteNonQuery(string.Format(ProcessStatInsertFormat, signalIndex, statName, statDescription, statMethodSuffix)); } } } /// /// Data operation to validate and ensure that certain records /// that are required for alarming exist in the database. /// private static void ValidateAlarming(AdoDataConnection connection, string nodeIDQueryString) { // SELECT queries const string AlarmCountFormat = "SELECT COUNT(*) FROM Alarm"; const string AlarmAdapterCountFormat = "SELECT COUNT(*) FROM CustomActionAdapter WHERE AdapterName = 'ALARM!SERVICES' AND NodeID = {0}"; const string AlarmConfigEntityCountFormat = "SELECT COUNT(*) FROM ConfigurationEntity WHERE RuntimeName = 'Alarms'"; const string AlarmSignalTypeCountFormat = "SELECT COUNT(*) FROM SignalType WHERE Name = 'Alarm'"; // INSERT queries const string AlarmAdapterInsertFormat = "INSERT INTO CustomActionAdapter(NodeID, AdapterName, AssemblyName, TypeName, ConnectionString, LoadOrder, Enabled) VALUES({0}, 'ALARM!SERVICES', 'DataQualityMonitoring.dll', 'DataQualityMonitoring.AlarmAdapter', 'useAlarmLog=false', 0, 1)"; const string AlarmConfigEntityInsertFormat = "INSERT INTO ConfigurationEntity(SourceName, RuntimeName, Description, LoadOrder, Enabled) VALUES('Alarm', 'Alarms', 'Defines alarms that monitor the values of measurements', 17, 1)"; const string AlarmSignalTypeInsertFormat = "INSERT INTO SignalType(Name, Acronym, Suffix, Abbreviation, Source, EngineeringUnits) VALUES('Alarm', 'ALRM', 'AL', 'AL', 'Any', '')"; bool alarmTableExists; try { // Determine whether the alarm table exists // before inserting records related to alarming connection.Connection.ExecuteScalar(AlarmCountFormat); alarmTableExists = true; } catch { alarmTableExists = false; } if (alarmTableExists) { Guid nodeID = Guid.Parse(nodeIDQueryString.Trim('\'')); // Ensure that the alarm adapter is defined. int alarmAdapterCount = connection.ExecuteScalar(AlarmAdapterCountFormat, nodeID); if (alarmAdapterCount == 0) connection.ExecuteNonQuery(AlarmAdapterInsertFormat, nodeID); // Ensure that the alarm record is defined in the ConfigurationEntity table. int alarmConfigEntityCount = connection.ExecuteScalar(AlarmConfigEntityCountFormat); if (alarmConfigEntityCount == 0) connection.ExecuteNonQuery(AlarmConfigEntityInsertFormat); // Ensure that the alarm record is defined in the SignalType table. int alarmSignalTypeCount = connection.ExecuteScalar(AlarmSignalTypeCountFormat); if (alarmSignalTypeCount == 0) connection.ExecuteNonQuery(AlarmSignalTypeInsertFormat); ValidateAlarmStatistics(connection, nodeID, "Point"); } } private static void ValidateAlarmStatistics(AdoDataConnection connection, Guid nodeID, string source) { const string MissingStatisticsFormat = "SELECT DISTINCT Severity FROM Alarm WHERE Severity <> 0 AND Severity NOT IN (SELECT Arguments FROM Statistic WHERE Source = {0} AND MethodName = {1})"; const string MaxSignalIndexFormat = "SELECT COALESCE(MAX(SignalIndex), 0) FROM Statistic WHERE Source = {0}"; const string InsertAlarmStatisticFormat = "INSERT INTO Statistic(Source, SignalIndex, Name, Description, AssemblyName, TypeName, MethodName, Arguments, Enabled, DataType, DisplayFormat, IsConnectedState, LoadOrder) VALUES({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12})"; // Add statistics for the alarms defined in the Alarm table. string methodName = $"Get{source}Statistic_MeasurementCountForSeverity"; DataTable missingStatistics = connection.RetrieveData(MissingStatisticsFormat, source, methodName); if (missingStatistics.Rows.Count > 0) { int signalIndex = connection.ExecuteScalar(MaxSignalIndexFormat, source); foreach (DataRow missingStatistic in missingStatistics.Rows) { signalIndex++; int severity = missingStatistic.ConvertField("Severity"); string name = $"Alarm Severity {severity}"; string description = $"Number of measurements received while alarm with severity {severity} was raised during the last reporting interval."; connection.ExecuteNonQuery(InsertAlarmStatisticFormat, source, signalIndex, name, description, "DataQualityMonitoring.dll", "DataQualityMonitoring.AlarmStatistics", methodName, severity, 1, "System.Int32", "{0:N0}", 0, 1001 - severity); } } } } }