It would be nice to have multiple config xml files allowed, so that the archive, srvdescriptors and the monitored servers are in separate files. This makes life easier to e.g. promote a set of srvdescriptors into a production environment, is it not necessary to update the list of servers being monitored, and it makes it simple to diff the srvdescriptor files to see what the differences are.
I have made a prototype of this - simply read in multiple config files and do the parse & validate after they have been loaded. You may want to add more validation. Changed Asemon_logger.java, CnxMgr.java and Config.java
PS thanks for asemon - it's a very useful!
diff -ru asemon_logger-2.7.15/Asemon_logger.java asemon_logger.new/Asemon_logger.java --- asemon_logger-2.7.15/Asemon_logger.java Wed Jun 11 11:39:38 2014 +++ asemon_logger.new/Asemon_logger.java Wed Jun 11 12:38:05 2014 @@ -15,7 +15,7 @@ static String Asemon_logger_version = "Version V2.7.15"; static int asemon_procs_version = 2760; static int threadNameSizeMax=1; // Used to format messages - static String config_file; // Configuration file name (with or without path) + static Vector<String> configFiles; // Configuration file name (with or without path) static Config config; // Used to store all the configuration static CnxMgr cnxMgr; // Used to manage all types of connections static PassFileMgr pfm=null; // Used to manage passwords @@ -51,7 +51,7 @@ // static Connection archive_conn=null; - static String usage_string = "Usage : asemon_logger -c config_file.xml [-V] \n"; + static String usage_string = "Usage : asemon_logger -c config_file.xml [-c ...] [-V] \n"; static void getArgs(String args[]) { @@ -64,7 +64,7 @@ while ((c = g.getopt()) != -1) { switch(c) { case 'c': - config_file = g.getOptarg(); + configFiles.add(g.getOptarg()); break; case 'T': traceflag=0; @@ -127,7 +127,7 @@ } } - if ( config_file==null ) { + if ( configFiles.isEmpty() ) { Asemon_logger.printmess ("ERROR Provide a configuration file"); System.out.println(usage_string); System.exit(1); @@ -159,6 +159,7 @@ public static void main(String[] args) { traceflags = new Vector(); + configFiles = new Vector(); getArgs(args); @@ -179,7 +180,9 @@ System.exit(1); } printmess ("Classpath is : "+System.getProperty("java.class.path")); - printmess ("Config file used : "+config_file); + for (String cfgFile : configFiles) { + printmess ("Config file used : "+cfgFile); + } // initialize the password manager pfm = new PassFileMgr("passwords"); // encrypt clear passwords @@ -193,9 +196,22 @@ // Initialize configuration config = new Config(); - if ( !config.loadConfig(config_file) ) - System.exit(1); + for (String cfgFile : configFiles) { + if ( !config.loadConfig(cfgFile) ) { + System.exit(1); + } + } + try { + config.loadMetricdescriptors(); + config.validateConfig(); + } + catch (Exception e){ + Asemon_logger.printmess (e.getMessage()); + System.exit(1); + } + + if (skipRetreiveSQLText==true) Asemon_logger.printmess("Active traceflag : 1 = skipRetreiveSQLText"); if (disableBulkLoad==true) diff -ru asemon_logger-2.7.15/CnxMgr.java asemon_logger.new/CnxMgr.java --- asemon_logger-2.7.15/CnxMgr.java Wed Jun 11 11:39:39 2014 +++ asemon_logger.new/CnxMgr.java Wed Jun 11 12:43:45 2014 @@ -381,11 +381,7 @@ try { Statement stmt = aArchServCnx.archive_conn.createStatement(); - int len = Asemon_logger.config_file.length(); - int start, end; - if (len > 30) {start=len-30; end=len;} - else {start=0; end=len-1;} - stmt.executeUpdate("set clientapplname '"+Asemon_logger.config_file.substring(start, end)+"'"); + stmt.executeUpdate("set clientapplname 'asemon_logger'"); stmt.close(); } catch (Exception e) { @@ -421,11 +417,7 @@ Asemon_logger.printmess ("Connected to archive server : "+ Asemon_logger.archive_server + " Database : " + Asemon_logger.archive_base); try { Statement stmt = msrv.purge_conn.createStatement(); - int len = Asemon_logger.config_file.length(); - int start, end; - if (len > 30) {start=len-30; end=len;} - else {start=0; end=len-1;} - stmt.executeUpdate("set clientapplname '"+Asemon_logger.config_file.substring(start, end)+"'"); + stmt.executeUpdate("set clientapplname 'asemon_logger'"); stmt.close(); } catch (Exception e) { diff -ru asemon_logger-2.7.15/Config.java asemon_logger.new/Config.java --- asemon_logger-2.7.15/Config.java Wed Jun 11 11:39:39 2014 +++ asemon_logger.new/Config.java Wed Jun 11 12:36:33 2014 @@ -150,184 +150,180 @@ // Retreive archive server configuration Element archiveSrvXml = root.getChild("ArchiveSrv"); - if (archiveSrvXml == null) { - Asemon_logger.printmess ("parseConfigFile : (" +fname+ ") : missing archive server" ); - System.exit(1); - } - // Retreive archive server conf - Asemon_logger.archive_server = archiveSrvXml.getChildTextTrim("name"); - Asemon_logger.archive_user = archiveSrvXml.getChildTextTrim("user"); - useKerberosStr = archiveSrvXml.getChildTextTrim("useKerberos"); - if ((useKerberosStr != null)&&(useKerberosStr.equalsIgnoreCase("YES"))) Asemon_logger.archive_useKerberos=true; - else Asemon_logger.archive_useKerberos=false; - Asemon_logger.archive_base = archiveSrvXml.getChildTextTrim("database"); - Asemon_logger.archive_charset = archiveSrvXml.getChildTextTrim("charset"); - Asemon_logger.archive_granteeList = archiveSrvXml.getChildTextTrim("GranteeList"); - if (Asemon_logger.archive_granteeList != null) Asemon_logger.archive_granteeList.trim(); - - String archpoolSzStr = archiveSrvXml.getChildTextTrim("poolsize"); - if (archpoolSzStr != null) { - try { - Asemon_logger.archive_poolsize=Integer.parseInt(archpoolSzStr); + if (archiveSrvXml != null) { + // Retreive archive server conf + Asemon_logger.archive_server = archiveSrvXml.getChildTextTrim("name"); + Asemon_logger.archive_user = archiveSrvXml.getChildTextTrim("user"); + useKerberosStr = archiveSrvXml.getChildTextTrim("useKerberos"); + if ((useKerberosStr != null)&&(useKerberosStr.equalsIgnoreCase("YES"))) Asemon_logger.archive_useKerberos=true; + else Asemon_logger.archive_useKerberos=false; + Asemon_logger.archive_base = archiveSrvXml.getChildTextTrim("database"); + Asemon_logger.archive_charset = archiveSrvXml.getChildTextTrim("charset"); + Asemon_logger.archive_granteeList = archiveSrvXml.getChildTextTrim("GranteeList"); + if (Asemon_logger.archive_granteeList != null) Asemon_logger.archive_granteeList.trim(); + + String archpoolSzStr = archiveSrvXml.getChildTextTrim("poolsize"); + if (archpoolSzStr != null) { + try { + Asemon_logger.archive_poolsize=Integer.parseInt(archpoolSzStr); + } + catch (NumberFormatException e) { + Asemon_logger.printmess ("parseConfigFile : (" +fname+ ") : incorrect poolsize" ); + Asemon_logger.archive_poolsize =1; + } + if (Asemon_logger.archive_poolsize <= 0) { + // Ignore bad conf, reset to default + Asemon_logger.printmess ("parseConfigFile : (" +fname+ ") : incorrect poolsize" ); + Asemon_logger.archive_poolsize =1; + } } - catch (NumberFormatException e) { - Asemon_logger.printmess ("parseConfigFile : (" +fname+ ") : incorrect poolsize" ); - Asemon_logger.archive_poolsize =1; + String packet_size_STR = archiveSrvXml.getChildTextTrim("packet_size"); + if ( (packet_size_STR!=null) && (packet_size_STR.length()>0) ) { + try { + Asemon_logger.archive_packet_size = Integer.parseInt(packet_size_STR); + Asemon_logger.printmess("Archive server packet_size asked by asemon_logger : " + Asemon_logger.archive_packet_size); + } + catch (Exception e) { + Asemon_logger.printmess ("parseConfigFile : Invalid packet_size. Will be ignored."); + Asemon_logger.archive_packet_size = 0; + } } - if (Asemon_logger.archive_poolsize <= 0) { - // Ignore bad conf, reset to default - Asemon_logger.printmess ("parseConfigFile : (" +fname+ ") : incorrect poolsize" ); - Asemon_logger.archive_poolsize =1; - } } - String packet_size_STR = archiveSrvXml.getChildTextTrim("packet_size"); - if ( (packet_size_STR!=null) && (packet_size_STR.length()>0) ) { - try { - Asemon_logger.archive_packet_size = Integer.parseInt(packet_size_STR); - Asemon_logger.printmess("Archive server packet_size asked by asemon_logger : " + Asemon_logger.archive_packet_size); - } - catch (Exception e) { - Asemon_logger.printmess ("parseConfigFile : Invalid packet_size. Will be ignored."); - Asemon_logger.archive_packet_size = 0; - } - } // Initialize monitored serveurs structure Element monSrvXml = root.getChild("MonitoredSrv"); - if (monSrvXml == null) { - Asemon_logger.printmess ("parseConfigFile : (" +fname+ ") : missing monitored server" ); - System.exit(1); - } - Element aMSXml; - MonitoredSRV aMonitoredSRV; - // Get list of monitored servers - List msl = monSrvXml.getChildren("SRV"); - monitoredSRVs = new Hashtable(msl.size()); - for (Iterator itMsl = msl.iterator(); itMsl.hasNext();) { - // Retreive a monitored server (SRV) - aMSXml = (Element)itMsl.next(); - aMonitoredSRV = new MonitoredSRV(); - aMonitoredSRV.name = aMSXml.getChildTextTrim("name"); - if (aMonitoredSRV.name.length() >20) - aMonitoredSRV.srvNormalized = aMonitoredSRV.name.substring(0,20); - else aMonitoredSRV.srvNormalized = aMonitoredSRV.name; - aMonitoredSRV.amStats = new AsemonStats(aMonitoredSRV.srvNormalized); // Allocate monitoring pipe - aMonitoredSRV.user = aMSXml.getChildTextTrim("user"); - - useKerberosStr = aMSXml.getChildTextTrim("useKerberos"); - if ((useKerberosStr != null)&&(useKerberosStr.equalsIgnoreCase("YES"))) aMonitoredSRV.useKerberos=true; - else aMonitoredSRV.useKerberos=false; - - - - aMonitoredSRV.charset = aMSXml.getChildTextTrim("charset"); - aMonitoredSRV.RSSDServer = aMSXml.getChildTextTrim("RSSDServer"); - aMonitoredSRV.RSSDUser = aMSXml.getChildTextTrim("RSSDUser"); - aMonitoredSRV.RSSDDatabase = aMSXml.getChildTextTrim("RSSDDatabase"); - aMonitoredSRV.srvDescriptor = aMSXml.getChildTextTrim("srvDescriptor"); - - aMonitoredSRV.purgeArchive = false; // By default, purge is deactivated - aMonitoredSRV.daysToKeep=90; // Default days to keep if purge is activated - Element purgeXml = aMSXml.getChild("purgearchive"); - if (purgeXml != null){ - aMonitoredSRV.purgeArchive = true; - String daysToKeepStr = purgeXml.getAttributeValue("daysToKeep"); - String deleteSleepStr = purgeXml.getAttributeValue("deleteSleep"); - try { - if (daysToKeepStr != null) aMonitoredSRV.daysToKeep = Integer.parseInt(daysToKeepStr); - if (aMonitoredSRV.daysToKeep <=0 ){ - Asemon_logger.printmess ("ERROR - Parse XML Config file : 'daysToKeep' for server '"+ - aMonitoredSRV.name+"' must be > 0. Purge deactivated."); + if (monSrvXml != null) { + Element aMSXml; + MonitoredSRV aMonitoredSRV; + // Get list of monitored servers + List msl = monSrvXml.getChildren("SRV"); + monitoredSRVs = new Hashtable(msl.size()); + for (Iterator itMsl = msl.iterator(); itMsl.hasNext();) { + // Retreive a monitored server (SRV) + aMSXml = (Element)itMsl.next(); + aMonitoredSRV = new MonitoredSRV(); + aMonitoredSRV.name = aMSXml.getChildTextTrim("name"); + if (aMonitoredSRV.name.length() >20) + aMonitoredSRV.srvNormalized = aMonitoredSRV.name.substring(0,20); + else aMonitoredSRV.srvNormalized = aMonitoredSRV.name; + aMonitoredSRV.amStats = new AsemonStats(aMonitoredSRV.srvNormalized); // Allocate monitoring pipe + aMonitoredSRV.user = aMSXml.getChildTextTrim("user"); + + useKerberosStr = aMSXml.getChildTextTrim("useKerberos"); + if ((useKerberosStr != null)&&(useKerberosStr.equalsIgnoreCase("YES"))) aMonitoredSRV.useKerberos=true; + else aMonitoredSRV.useKerberos=false; + + + + aMonitoredSRV.charset = aMSXml.getChildTextTrim("charset"); + aMonitoredSRV.RSSDServer = aMSXml.getChildTextTrim("RSSDServer"); + aMonitoredSRV.RSSDUser = aMSXml.getChildTextTrim("RSSDUser"); + aMonitoredSRV.RSSDDatabase = aMSXml.getChildTextTrim("RSSDDatabase"); + aMonitoredSRV.srvDescriptor = aMSXml.getChildTextTrim("srvDescriptor"); + + aMonitoredSRV.purgeArchive = false; // By default, purge is deactivated + aMonitoredSRV.daysToKeep=90; // Default days to keep if purge is activated + Element purgeXml = aMSXml.getChild("purgearchive"); + if (purgeXml != null){ + aMonitoredSRV.purgeArchive = true; + String daysToKeepStr = purgeXml.getAttributeValue("daysToKeep"); + String deleteSleepStr = purgeXml.getAttributeValue("deleteSleep"); + try { + if (daysToKeepStr != null) aMonitoredSRV.daysToKeep = Integer.parseInt(daysToKeepStr); + if (aMonitoredSRV.daysToKeep <=0 ){ + Asemon_logger.printmess ("ERROR - Parse XML Config file : 'daysToKeep' for server '"+ + aMonitoredSRV.name+"' must be > 0. Purge deactivated."); + aMonitoredSRV.purgeArchive = false; + } + } + catch (NumberFormatException e) { + Asemon_logger.printmess ("ERROR - Parse XML Config file : invalid number for 'daysToKeep' for server '"+ + aMonitoredSRV.name+"'. Purge deactivated."); aMonitoredSRV.purgeArchive = false; } - } - catch (NumberFormatException e) { - Asemon_logger.printmess ("ERROR - Parse XML Config file : invalid number for 'daysToKeep' for server '"+ - aMonitoredSRV.name+"'. Purge deactivated."); - aMonitoredSRV.purgeArchive = false; - } - - aMonitoredSRV.deleteSleep = 100; - try { - if (deleteSleepStr != null) aMonitoredSRV.deleteSleep = Integer.parseInt(deleteSleepStr); - if (aMonitoredSRV.deleteSleep < 0 ){ - Asemon_logger.printmess ("ERROR - Parse XML Config file : 'deleteSleep' for server '"+ - aMonitoredSRV.name+"' must be >= 0. Default sleep time of 100 ms will be used."); + + aMonitoredSRV.deleteSleep = 100; + try { + if (deleteSleepStr != null) aMonitoredSRV.deleteSleep = Integer.parseInt(deleteSleepStr); + if (aMonitoredSRV.deleteSleep < 0 ){ + Asemon_logger.printmess ("ERROR - Parse XML Config file : 'deleteSleep' for server '"+ + aMonitoredSRV.name+"' must be >= 0. Default sleep time of 100 ms will be used."); + aMonitoredSRV.deleteSleep = 100; + } + } + catch (NumberFormatException e) { + Asemon_logger.printmess ("ERROR - Parse XML Config file : invalid number for 'deleteSleep' for server '"+ + aMonitoredSRV.name+"'. Default sleep time of 100 ms will be used."); aMonitoredSRV.deleteSleep = 100; } - } - catch (NumberFormatException e) { - Asemon_logger.printmess ("ERROR - Parse XML Config file : invalid number for 'deleteSleep' for server '"+ - aMonitoredSRV.name+"'. Default sleep time of 100 ms will be used."); - aMonitoredSRV.deleteSleep = 100; - } - - String startDelayStr = purgeXml.getAttributeValue("startDelay"); - try { - if (startDelayStr != null) aMonitoredSRV.startDelay = Integer.parseInt(startDelayStr); - else aMonitoredSRV.startDelay = 30; - if (aMonitoredSRV.startDelay == -1) { - // Special case, generate a random number between 1 and 30 - aMonitoredSRV.startDelay = ((int)(java.lang.Math.random() * 29)) +1; + + String startDelayStr = purgeXml.getAttributeValue("startDelay"); + try { + if (startDelayStr != null) aMonitoredSRV.startDelay = Integer.parseInt(startDelayStr); + else aMonitoredSRV.startDelay = 30; + if (aMonitoredSRV.startDelay == -1) { + // Special case, generate a random number between 1 and 30 + aMonitoredSRV.startDelay = ((int)(java.lang.Math.random() * 29)) +1; + } + else if (aMonitoredSRV.startDelay <=0 ){ + Asemon_logger.printmess ("ERROR - Parse XML Config file : 'startDelay' for server '"+ + aMonitoredSRV.name+"' must be > 0. Purge deactivated."); + aMonitoredSRV.purgeArchive = false; + } } - else if (aMonitoredSRV.startDelay <=0 ){ - Asemon_logger.printmess ("ERROR - Parse XML Config file : 'startDelay' for server '"+ - aMonitoredSRV.name+"' must be > 0. Purge deactivated."); + catch (NumberFormatException e) { + Asemon_logger.printmess ("ERROR - Parse XML Config file : invalid number for 'daysToKeep' for server '"+ + aMonitoredSRV.name+"'. Purge deactivated."); aMonitoredSRV.purgeArchive = false; } + + aMonitoredSRV.batchsize = 1000; + String batchsizeStr = purgeXml.getAttributeValue("batchsize"); + if (batchsizeStr!= null) try { + aMonitoredSRV.batchsize = Integer.parseInt(batchsizeStr); + } + catch (Exception e) { + Asemon_logger.printmess ("parseConfigFile : Invalid batchsize. Will be ignored."); + aMonitoredSRV.batchsize = 1000; + } + if (aMonitoredSRV.batchsize < 0){ + Asemon_logger.printmess ("parseConfigFile : Invalid batchsize. Will be ignored."); + aMonitoredSRV.batchsize = 1000; + } + } - catch (NumberFormatException e) { - Asemon_logger.printmess ("ERROR - Parse XML Config file : invalid number for 'daysToKeep' for server '"+ - aMonitoredSRV.name+"'. Purge deactivated."); - aMonitoredSRV.purgeArchive = false; + + // Packet_size + String packet_size_STR = aMSXml.getChildTextTrim("packet_size"); + if ( (packet_size_STR!=null) && (packet_size_STR.length()>0) ) { + try { + aMonitoredSRV.packet_size = Integer.parseInt(packet_size_STR); + Asemon_logger.printmess("Packet_size asked by asemon_logger for server '"+aMonitoredSRV.name+"': " + aMonitoredSRV.packet_size); + } + catch (Exception e) { + Asemon_logger.printmess ("parseConfigFile : Invalid packet_size for server '"+aMonitoredSRV.name+"'. Will be ignored."); + aMonitoredSRV.packet_size = 0; + } } - - aMonitoredSRV.batchsize = 1000; - String batchsizeStr = purgeXml.getAttributeValue("batchsize"); - if (batchsizeStr!= null) try { - aMonitoredSRV.batchsize = Integer.parseInt(batchsizeStr); + + // textsize + String textsize_STR = aMSXml.getChildTextTrim("textsize"); + if ( (textsize_STR!=null) && (textsize_STR.length()>0) ) { + try { + aMonitoredSRV.textsize = Integer.parseInt(textsize_STR); + Asemon_logger.printmess("textsize for retreiving text (XML documents) for '"+aMonitoredSRV.name+"': " + aMonitoredSRV.textsize); + } + catch (Exception e) { + Asemon_logger.printmess ("parseConfigFile : Invalid textsize for server '"+aMonitoredSRV.name+"'. Will be ignored."); + aMonitoredSRV.textsize = 32768; + } } - catch (Exception e) { - Asemon_logger.printmess ("parseConfigFile : Invalid batchsize. Will be ignored."); - aMonitoredSRV.batchsize = 1000; - } - if (aMonitoredSRV.batchsize < 0){ - Asemon_logger.printmess ("parseConfigFile : Invalid batchsize. Will be ignored."); - aMonitoredSRV.batchsize = 1000; - } - - } - - // Packet_size - packet_size_STR = aMSXml.getChildTextTrim("packet_size"); - if ( (packet_size_STR!=null) && (packet_size_STR.length()>0) ) { - try { - aMonitoredSRV.packet_size = Integer.parseInt(packet_size_STR); - Asemon_logger.printmess("Packet_size asked by asemon_logger for server '"+aMonitoredSRV.name+"': " + aMonitoredSRV.packet_size); - } - catch (Exception e) { - Asemon_logger.printmess ("parseConfigFile : Invalid packet_size for server '"+aMonitoredSRV.name+"'. Will be ignored."); - aMonitoredSRV.packet_size = 0; - } - } - - // textsize - String textsize_STR = aMSXml.getChildTextTrim("textsize"); - if ( (textsize_STR!=null) && (textsize_STR.length()>0) ) { - try { - aMonitoredSRV.textsize = Integer.parseInt(textsize_STR); - Asemon_logger.printmess("textsize for retreiving text (XML documents) for '"+aMonitoredSRV.name+"': " + aMonitoredSRV.textsize); - } - catch (Exception e) { - Asemon_logger.printmess ("parseConfigFile : Invalid textsize for server '"+aMonitoredSRV.name+"'. Will be ignored."); - aMonitoredSRV.textsize = 32768; - } - } - - - - // finaly add the monitored srv to the list - monitoredSRVs.put(aMonitoredSRV.name, aMonitoredSRV); + + + + // finaly add the monitored srv to the list + monitoredSRVs.put(aMonitoredSRV.name, aMonitoredSRV); + } } // Initialize serveur descriptors @@ -423,7 +419,7 @@ ** Validate the configuration ** Exit if error */ - void validateConfig(String fname) { + void validateConfig() { // Check parameters for archive server if ( Asemon_logger.archive_server==null ) { Asemon_logger.printmess ("ERROR Provide Ase connection to archive results"); @@ -445,20 +441,20 @@ // Retreive a monitored server (SRV) aMonitoredSRV = (MonitoredSRV)eMs.nextElement(); if (aMonitoredSRV.name==null) { - Asemon_logger.printmess ("validateConfig : (" +fname+ ") : missing server name in SRV structure" ); + Asemon_logger.printmess ("validateConfig : missing server name in SRV structure" ); System.exit(1); } if (aMonitoredSRV.user==null) { - Asemon_logger.printmess ("validateConfig : (" +fname+ ") : missing user name in SRV structure for " +aMonitoredSRV.name ); + Asemon_logger.printmess ("validateConfig : missing user name in SRV structure for " +aMonitoredSRV.name ); System.exit(1); } if (aMonitoredSRV.srvDescriptor==null) { - Asemon_logger.printmess ("validateConfig : (" +fname+ ") : missing srvDescriptor in SRV structure for "+aMonitoredSRV.name ); + Asemon_logger.printmess ("validateConfig : missing srvDescriptor in SRV structure for "+aMonitoredSRV.name ); System.exit(1); } // retreive corresponding srvDescriptor and bind it in the monitoredSrv structure if (srvDescriptors==null) { - Asemon_logger.printmess ("validateConfig : (" +fname+ ") : no SrvDescriptors" ); + Asemon_logger.printmess ("validateConfig : no SrvDescriptors" ); System.exit(1); } SrvDescriptor aSrvDescriptor; @@ -473,7 +469,7 @@ } } if (!found) { - Asemon_logger.printmess ("validateConfig : (" +fname+ ") : not all monitored servers have a corresponding srvDescriptor"); + Asemon_logger.printmess ("validateConfig : not all monitored servers have a corresponding srvDescriptor"); System.exit(1); } } @@ -492,8 +488,6 @@ MetricDescriptor md=null; try { parseConfigFile(fname); - loadMetricdescriptors(); - validateConfig(fname); } catch (Exception e){ Asemon_logger.printmess ("loadConfig : (" +fname+ ") " + e);
Anonymous
Thank you for prototype
Just a question : why don't you group your production servers in a few config files ( say 30 servers per config file) ? A single config file can be used to monitor several servers.