/// <summary> /// Initializes <see cref="FileExporter"/>. /// </summary> public override void Initialize() { base.Initialize(); Dictionary<string, string> settings = Settings; const string errorMessage = "{0} is missing from Settings - Example: exportInterval=5; useReferenceAngle=True; referenceAngleMeasurement=DEVARCHIVE:6; companyTagPrefix=TVA; useNumericQuality=True; inputMeasurementKeys={{FILTER ActiveMeasurements WHERE Device='SHELBY' AND SignalType='FREQ'}}"; string setting; double seconds; // Load required parameters if (!settings.TryGetValue("exportInterval", out setting) || !double.TryParse(setting, out seconds)) throw new ArgumentException(string.Format(errorMessage, "exportInterval")); m_exportInterval = (int)(seconds * 1000.0D); m_lastPublicationTime = 0; if (m_exportInterval <= 0) throw new ArgumentException("exportInterval should not be 0 - Example: exportInterval=5.5"); if (InputMeasurementKeys == null || InputMeasurementKeys.Length == 0) throw new InvalidOperationException("There are no input measurements defined. You must define \"inputMeasurementKeys\" to define which measurements to export."); if (!settings.TryGetValue("useReferenceAngle", out setting)) throw new ArgumentException(string.Format(errorMessage, "useReferenceAngle")); m_useReferenceAngle = setting.ParseBoolean(); if (m_useReferenceAngle) { // Reference angle measurement has to be defined if using reference angle if (!settings.TryGetValue("referenceAngleMeasurement", out setting)) throw new ArgumentException(string.Format(errorMessage, "referenceAngleMeasurement")); m_referenceAngleKey = MeasurementKey.Parse(setting); // Make sure reference angle is part of input measurement keys collection if (!InputMeasurementKeys.Contains(m_referenceAngleKey)) InputMeasurementKeys = InputMeasurementKeys.Concat(new[] { m_referenceAngleKey }).ToArray(); // Make sure reference angle key is actually an angle measurement SignalType signalType = InputMeasurementKeyTypes[InputMeasurementKeys.IndexOf(key => key == m_referenceAngleKey)]; if (signalType != SignalType.IPHA && signalType != SignalType.VPHA) throw new InvalidOperationException($"Specified reference angle measurement key is a {signalType.GetFormattedName()} signal, not a phase angle."); } // Load optional parameters if (settings.TryGetValue("companyTagPrefix", out setting)) m_companyTagPrefix = setting.ToUpper().Trim(); else m_companyTagPrefix = null; if (settings.TryGetValue("useNumericQuality", out setting)) m_useNumericQuality = setting.ParseBoolean(); else m_useNumericQuality = false; // Suffix company tag prefix with an underscore if defined if (!string.IsNullOrWhiteSpace(m_companyTagPrefix)) m_companyTagPrefix = m_companyTagPrefix.EnsureEnd('_'); // Define a default export location - user can override and add multiple locations in config later... m_dataExporter = new MultipleDestinationExporter(ConfigurationSection, m_exportInterval); m_dataExporter.StatusMessage += m_dataExporter_StatusMessage; m_dataExporter.ProcessException += m_dataExporter_ProcessException; m_dataExporter.Initialize(new[] { new ExportDestination(FilePath.GetAbsolutePath(ConfigurationSection + ".txt"), false) }); // Create new measurement tag name dictionary m_measurementTags = new ConcurrentDictionary<MeasurementKey, string>(); string pointID = "undefined"; // Lookup point tag name for input measurement in the ActiveMeasurements table foreach (MeasurementKey key in InputMeasurementKeys) { try { // Get measurement key as a string pointID = key.ToString(); // Lookup measurement key in active measurements table DataRow row = DataSource.Tables["ActiveMeasurements"].Select($"ID='{pointID}'")[0]; // Remove invalid symbols that may be in tag name string pointTag = row["PointTag"].ToNonNullString(pointID).Replace('-', '_').Replace(':', '_').ToUpper(); // Prefix point tag with company prefix if defined if (!string.IsNullOrWhiteSpace(m_companyTagPrefix) && !pointTag.StartsWith(m_companyTagPrefix)) pointTag = m_companyTagPrefix + pointTag; m_measurementTags.TryAdd(key, pointTag); } catch (Exception ex) { OnProcessException(MessageLevel.Warning, new InvalidOperationException($"Failed to lookup point tag for measurement [{pointID}] due to exception: {ex.Message}")); } } // We enable tracking of latest measurements so we can use these values if points are missing - since we are using // latest measurement tracking, we sort all incoming points even though most of them will be thrown out... TrackLatestMeasurements = true; }
/// <summary> /// Event handler for service stopping operation. /// </summary> /// <param name="sender">Event source.</param> /// <param name="e">Event arguments.</param> /// <remarks> /// Time-series framework uses this handler to un-wire events and dispose of system objects. /// </remarks> protected virtual void ServiceStoppingHandler(object sender, EventArgs e) { // Dispose system health exporter if ((object)m_healthExporter != null) { m_healthExporter.Enabled = false; m_serviceHelper.ServiceComponents.Remove(m_healthExporter); m_healthExporter.Dispose(); m_healthExporter.StatusMessage -= m_iaonSession.StatusMessageHandler; m_healthExporter.ProcessException -= m_iaonSession.ProcessExceptionHandler; m_healthExporter = null; } // Dispose system status exporter if ((object)m_statusExporter != null) { m_statusExporter.Enabled = false; m_serviceHelper.ServiceComponents.Remove(m_statusExporter); m_statusExporter.Dispose(); m_statusExporter.StatusMessage -= m_iaonSession.StatusMessageHandler; m_statusExporter.ProcessException -= m_iaonSession.ProcessExceptionHandler; m_statusExporter = null; } // Dispose reload config queue if ((object)m_reloadConfigQueue != null) { m_reloadConfigQueue.ProcessException -= m_iaonSession.ProcessExceptionHandler; m_reloadConfigQueue.Dispose(); m_reloadConfigQueue = null; } // Dispose reporting processes if ((object)m_reportingProcesses != null) { m_serviceHelper.ServiceComponents.Remove(m_reportingProcesses); m_reportingProcesses = null; } // Dispose Iaon session if ((object)m_iaonSession != null) { m_serviceHelper.ServiceComponents.Remove(m_iaonSession.InputAdapters); m_serviceHelper.ServiceComponents.Remove(m_iaonSession.ActionAdapters); m_serviceHelper.ServiceComponents.Remove(m_iaonSession.OutputAdapters); m_iaonSession.Dispose(); m_iaonSession.StatusMessage -= StatusMessageHandler; m_iaonSession.ProcessException -= ProcessExceptionHandler; m_iaonSession.ConfigurationChanged -= ConfigurationChangedHandler; m_iaonSession = null; } // Dispose of run-time log if ((object)m_runTimeLog != null) { m_serviceHelper.ServiceComponents.Remove(m_runTimeLog); m_runTimeLog.ProcessException -= ProcessExceptionHandler; m_runTimeLog.Dispose(); m_runTimeLog = null; } m_serviceHelper.ServiceStarting -= ServiceStartingHandler; m_serviceHelper.ServiceStarted -= ServiceStartedHandler; m_serviceHelper.ServiceStopping -= ServiceStoppingHandler; m_serviceHelper.UpdatedStatus -= UpdatedStatusHandler; m_serviceHelper.LoggedException -= LoggedExceptionHandler; if ((object)m_serviceHelper.StatusLog != null) { m_serviceHelper.StatusLog.Flush(); m_serviceHelper.StatusLog.LogException -= LogExceptionHandler; } if ((object)m_serviceHelper.ErrorLogger != null && (object)m_serviceHelper.ErrorLogger.ErrorLog != null) { m_serviceHelper.ErrorLogger.ErrorLog.Flush(); m_serviceHelper.ErrorLogger.ErrorLog.LogException -= LogExceptionHandler; } // Detach from handler for unobserved task exceptions TaskScheduler.UnobservedTaskException -= TaskScheduler_UnobservedTaskException; ShutdownHandler.InitiateSafeShutdown(); }
/// <summary> /// Releases the unmanaged resources used by the <see cref="FileExporter"/> object and optionally releases the managed resources. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { if (m_dataExporter != null) { m_dataExporter.SaveSettings(); m_dataExporter.StatusMessage -= m_dataExporter_StatusMessage; m_dataExporter.ProcessException -= m_dataExporter_ProcessException; m_dataExporter.Dispose(); } m_dataExporter = null; } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } }
/// <summary> /// Event handler for service started operation. /// </summary> /// <param name="sender">Event source.</param> /// <param name="e">Event arguments.</param> /// <remarks> /// Time-series framework uses this handler to handle initialization of system objects. /// </remarks> protected virtual void ServiceStartedHandler(object sender, EventArgs e) { // Define a line of asterisks for emphasis string stars = new string('*', 79); // Get current process memory usage long processMemory = GSF.Common.GetProcessMemory(); // Log startup information m_serviceHelper.UpdateStatus( UpdateType.Information, "\r\n\r\n{0}\r\n\r\n" + "Node {{{1}}} Initializing\r\n\r\n" + " System Time: {2} UTC\r\n\r\n" + " Current Path: {3}\r\n\r\n" + " Machine Name: {4}\r\n\r\n" + " OS Version: {5}\r\n\r\n" + " Product Name: {6}\r\n\r\n" + " Working Memory: {7}\r\n\r\n" + " Execution Mode: {8}-bit\r\n\r\n" + " Processors: {9}\r\n\r\n" + " GC Server Mode: {10}\r\n\r\n" + " GC Latency Mode: {11}\r\n\r\n" + " Process Account: {12}\\{13}\r\n\r\n" + "{14}\r\n", stars, m_iaonSession.NodeID, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff"), FilePath.TrimFileName(FilePath.RemovePathSuffix(FilePath.GetAbsolutePath("")), 61), Environment.MachineName, Environment.OSVersion.VersionString, GSF.Common.GetOSProductName(), processMemory > 0 ? SI2.ToScaledString(processMemory, 4, "B", SI2.IECSymbols) : "Undetermined", IntPtr.Size * 8, Environment.ProcessorCount, GCSettings.IsServerGC, GCSettings.LatencyMode, Environment.UserDomainName, Environment.UserName, stars); // Add run-time log as a service component m_serviceHelper.ServiceComponents.Add(m_runTimeLog); // Create health exporter m_healthExporter = new MultipleDestinationExporter("HealthExporter", Timeout.Infinite); m_healthExporter.Initialize(new[] { new ExportDestination(FilePath.GetAbsolutePath("Health.txt"), false) }); m_healthExporter.StatusMessage += m_iaonSession.StatusMessageHandler; m_healthExporter.ProcessException += m_iaonSession.ProcessExceptionHandler; m_serviceHelper.ServiceComponents.Add(m_healthExporter); // Create status exporter m_statusExporter = new MultipleDestinationExporter("StatusExporter", Timeout.Infinite); m_statusExporter.Initialize(new[] { new ExportDestination(FilePath.GetAbsolutePath("Status.txt"), false) }); m_statusExporter.StatusMessage += m_iaonSession.StatusMessageHandler; m_statusExporter.ProcessException += m_iaonSession.ProcessExceptionHandler; m_serviceHelper.ServiceComponents.Add(m_statusExporter); // Create reporting processes m_reportingProcesses = ReportingProcessCollection.LoadImplementations(ex => { DisplayStatusMessage("Failed to load reporting process: {0}", UpdateType.Warning, ex.Message); LogException(ex); }); m_serviceHelper.ServiceComponents.Add(m_reportingProcesses); // Define scheduled service processes m_serviceHelper.AddScheduledProcess(HealthMonitorProcessHandler, "HealthMonitor", "* * * * *"); // Every minute m_serviceHelper.AddScheduledProcess(StatusExportProcessHandler, "StatusExport", "*/30 * * * *"); // Every 30 minutes // Define processes for each report (initially unscheduled) foreach (IReportingProcess reportingProcess in m_reportingProcesses) m_serviceHelper.AddProcess(ReportingProcessHandler, $"{reportingProcess.ReportType}Reporting"); // Add key Iaon collections as service components m_serviceHelper.ServiceComponents.Add(m_iaonSession.InputAdapters); m_serviceHelper.ServiceComponents.Add(m_iaonSession.ActionAdapters); m_serviceHelper.ServiceComponents.Add(m_iaonSession.OutputAdapters); // Define remote client requests (i.e., console commands) m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("List", "Displays status for specified adapter or collection", ListRequestHandler, new[] { "ls", "dir" })); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Connect", "Connects (or starts) specified adapter", StartRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Disconnect", "Disconnects (or stops) specified adapter", StopRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Invoke", "Invokes a command for specified adapter", InvokeRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("ListCommands", "Displays possible commands for specified adapter", ListCommandsRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Initialize", "Initializes specified adapter or collection", InitializeRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("ReloadConfig", "Manually reloads the system configuration", ReloadConfigRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("UpdateConfigFile", "Updates an option in the configuration file", UpdateConfigFileRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Authenticate", "Authenticates network shares for health and status exports", AuthenticateRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Restart", "Attempts to restart the host service", RestartServiceHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("RefreshRoutes", "Spawns request to recalculate routing tables", RefreshRoutesRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("TemporalSupport", "Determines if any adapters support temporal processing", TemporalSupportRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("ListReports", "Lists the available reports that have been generated", ListReportsRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("GetReport", "Gets a previously generated report for the specified date", GetReportRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("GetReportStatus", "Gets a reporting process status", GetReportStatusRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("GenerateReport", "Generates a report for the specified date", GenerateReportRequestHandler)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("ReportingConfig", "Displays or modifies the configuration of the reporting process", ReportingConfigRequestHandler, false)); m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("LogEvent", "Logs remote event log entries.", LogEventRequestHandler, false)); // Start system initialization on an independent thread so that service responds in a timely fashion... InitializeSystem(); }