/* * Copyright (c) 2010 Ecole des Mines de Nantes. * * This file is part of Entropy. * * Entropy is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Entropy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Entropy. If not, see <http://www.gnu.org/licenses/>. */ package entropy.monitoring.ganglia; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import entropy.configuration.Configuration; import entropy.configuration.DefaultConfiguration; import entropy.configuration.DefaultManagedElementSet; import entropy.configuration.DefaultNode; import entropy.configuration.DefaultVirtualMachine; import entropy.configuration.ManagedElementSet; import entropy.configuration.Node; import entropy.configuration.VirtualMachine; import entropy.monitoring.ConfigurationAdapter; import entropy.monitoring.Monitor; /** * A SAX2 Parser to create a configuration from a XML Stream. The XML schema corresponds to * a GMetad XML output. The GMeta XML must contains some specific metrics in order to identify * containers (nodes that host vitual machines) and virtual machines. * <p/> * <p/> * A HOST is considered as a container if all the following metrics are defined. The CPU_CAPACITY * of a node by computing the frequency with the number of physical CPUs. The result is rounded using ConfigurationAdapter.round(...). * <p/> * <table> * <tr> * <th>Identifier</th> * <th>Description</th> * <th>Type</th> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_NB_CPU}</td> * <td>Number of physical CPUs</td> * <td>uint16</td> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_TYPE}</td> * <td>The type of the container (see <i>supported hypervisors</i>)</td> * <td>string</td> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_MEMORY_TOTAL}</td> * <td>total of memory of the container in KB</td> * <td>float</td> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_LIST_VMS}</td> * <td>List of each virtual machine name, separated by a space character</td> * <td>string</td> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_STARTUP_DRIVER}</td> * <td>The identifier of the driver to use to boot the node</td> * <td>string</td> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_SHUTDOWN_DRIVER}</td> * <td>The identifier of the driver to use to shutdown the node</td> * <td>string</td> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_MIGRATION_DRIVER}</td> * <td>The identifier of the driver to use to migrate a virtual machine</td> * <td>string</td> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_RUN_DRIVER}</td> * <td>The identifier of the driver to use to run a virtual machine</td> * <td>string</td> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_STOP_DRIVER}</td> * <td>The identifier of the driver to use to stop a virtual machine</td> * <td>string</td> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_RESUME_DRIVER}</td> * <td>The identifier of the driver to use to resume a virtual machine</td> * <td>string</td> * </tr> * <tr> * <td>{@value #METRIC_CONTAINER_SUSPEND_DRIVER}</td> * <td>The identifier of the driver to use to suspend a virtual machine</td> * <td>string</td> * </tr> * <tr> * <td>{@value #METRIC_CPU_FREQUENCY} (<i>standard gmond metric</i>)</td> * <td>The frequency of each CPU of the container (in MHz)</td> * <td>uint32</td> * </tr> * <caption>Metrics required by a container</caption> * </table> * <br/><br/> * In order to identify a supposed virtual machine, the HOST must contains the following metrics. All of them * are standard gmond metrics. * <table> * <tr> * <th>Identifier</th> * <th>Description</th> * <th>Type</th> * </tr> * <tr> * <td>{@value #METRIC_NB_CPU}</td> * <td>Number of VCPUs used by the virtual machine</td> * <td>uint16</td> * </tr> * <tr> * <td>{@value #METRIC_VM_MEMORY_CONSUMPTION}</td> * <td>The memory requirement of the virtual machine in KB</td> * <td>float</td> * </tr> * <tr> * <td>{@value #METRIC_CPU_FREQUENCY}</td> * <td>The frequency of each CPU allocated to the host (in MHz)</td> * <td>uint32</td> * </tr> * <caption>List of metrics required by a virtual machine</caption> * </table> * <p/> * In order to be added into the configuration, each HOST considered as a virtual machine * must be hosted on one host that is considered as a container. * * @author Fabien Hermenier */ public class GangliaMetaXMLParser extends DefaultHandler { /** * Indicates the beginning of the description of a sleeping VM. */ public static final String BEGIN_SLEEPING = "("; /** * Indicates the end of the description of a sleeping VM. */ public static final String END_SLEEPING = ")"; /** * Metric that indicates the identifier of the driver used to migrate virtual machines. */ public static final String METRIC_CONTAINER_MIGRATION_DRIVER = "container.driver.migration"; /** * Metric that indicates the identifier of the driver used to resume virtual machines. */ public static final String METRIC_CONTAINER_RESUME_DRIVER = "container.driver.resume"; /** * Metric that indicates the identifier of the driver used to suspend virtual machines. */ public static final String METRIC_CONTAINER_SUSPEND_DRIVER = "container.driver.suspend"; /** * Metric that indicates the identifier of the driver used to run virtual machines. */ public static final String METRIC_CONTAINER_RUN_DRIVER = "container.driver.run"; /** * Metric that indicates the identifier of the driver used to migrate virtual machines. */ public static final String METRIC_CONTAINER_STOP_DRIVER = "container.driver.stop"; /** * Metric that indicates the identifier of the driver to use to boot the node. */ public static final String METRIC_CONTAINER_STARTUP_DRIVER = "container.driver.startup"; /** * Metric that indicates the identifier of the driver to use to shutdown the node. */ public static final String METRIC_CONTAINER_SHUTDOWN_DRIVER = "container.driver.shutdown"; /** * Metric that indicates the number of physical CPUs of the host. */ public static final String METRIC_CONTAINER_NB_CPU = "container.cpu_num"; /** * Metric that indicates the amount of memory (in KB) of the host. */ public static final String METRIC_CONTAINER_MEMORY_TOTAL = "container.mem_total"; /** * Metric that indicates the virtual machines hosted by the host. */ public static final String METRIC_CONTAINER_LIST_VMS = "container.vms"; /** * Metric that indicates the type of the container. */ public static final String METRIC_CONTAINER_TYPE = "container.type"; /** * Metric that indicates the frequency of the CPUs allocated to a host. */ public static final String METRIC_CPU_FREQUENCY = "cpu_speed"; /** * Metric that indicates the memory requirement of a host. */ public static final String METRIC_VM_MEMORY_CONSUMPTION = "mem_total"; /** * Metric that indicates the number of CPUs allocated to a host. */ public static final String METRIC_NB_CPU = "cpu_num"; /** * Metric that indicates the percentage of CPU consumed at user level. */ public static final String METRIC_CPU_PCT_USER = "cpu_user"; /** * Metric that indicates the percentage of CPU consumed at a nice level. */ public static final String METRIC_CPU_PCT_NICE = "cpu_nice"; /** * Metric that indicates the percentage of CPU consumed at system level. */ public static final String METRIC_CPU_PCT_SYSTEM = "cpu_system"; /** * The list of all the virtual machines. */ private ManagedElementSet<VirtualMachine> allVMs; /** * The current configuration that is build. */ private Configuration currentConfiguration; /** * The IP of the current host. */ private String currentIp; /** * The hostname of the current host. */ private String currentHostname; /** * The metrics of the current host. */ private Map<String, String> currentMetrics; /** * The list of VMs assigned to each node. */ private Map<Node, String> assigns; /** * The percentage of CPUs used by all supposed VMs. */ private Map<VirtualMachine, Float> cpuPcts; /** * Contains the identifier of each online host. */ private Set<String> onlines; /** * The associated configuration adapter. */ private ConfigurationAdapter parent; /** * Make a new parser. * * @param cfgAdapter the associated configuration adapter */ public GangliaMetaXMLParser(ConfigurationAdapter cfgAdapter) { this.parent = cfgAdapter; } /** * Test is a String describes a sleeping VM * * @param str the String to analyze * @return true if the described VM is sleeping */ private boolean isSleeping(String str) { return str.startsWith(BEGIN_SLEEPING) && str.endsWith(END_SLEEPING); } /** * Extract the name of a sleeping VM. * * @param str the string that identify the VM. * @return the name of the VM. */ private String extractVMFromSleeping(String str) { return str.substring(1, str.length() - 1); } @Override public void endDocument() throws SAXException { /** * We make the assignment. */ for (Node n : this.assigns.keySet()) { String vms = this.assigns.get(n); StringTokenizer st = new StringTokenizer(vms, " "); while (st.hasMoreElements()) { String buf = st.nextToken(); if (isSleeping(buf)) { String name = extractVMFromSleeping(buf); VirtualMachine vm = allVMs.get(name); if (vm == null) { //The configuration is incoherent and skipping the virtual machine is not a viable solution. throw new SAXException("Unknown virtual machine '" + name + "'"); } else { vm.setCPUConsumption(ConfigurationAdapter.getCPUConsumption(cpuPcts.get(vm), n)); //We make the assignment this.currentConfiguration.setSleepOn(vm, n); } } else { String name = buf; VirtualMachine vm = allVMs.get(name); if (vm == null) { //The configuration is incoherent and skipping the virtual machine is not a viable solution. throw new SAXException("Unknown virtual machine '" + name + "'"); } else { // We set the CPU consumption vm.setCPUConsumption(ConfigurationAdapter.getCPUConsumption(cpuPcts.get(vm), n)); //We make the assignment this.currentConfiguration.setRunOn(vm, n); } } } } } @Override public void endElement(String uri, String localName, String name) throws SAXException { if (name.equals("HOST")) { if (isContainer(currentMetrics.keySet())) { if (this.parent.getNodesBlackList().contains(this.currentHostname)) { Monitor.getLogger().debug("Ignoring '" + this.currentHostname + "': belong to the nodes black list"); } else if (this.parent.getNodesWhiteList().size() > 0 && !this.parent.getNodesWhiteList().contains(this.currentHostname)) { Monitor.getLogger().debug("Ignoring '" + this.currentHostname + "': do not belong to the nodes white list"); } else if (onlines.contains(this.currentHostname)) { Monitor.getLogger().debug(currentHostname + " is considered as a container"); float memKb = Integer.parseInt(currentMetrics.get(METRIC_CONTAINER_MEMORY_TOTAL)); int cpuCapacity = ConfigurationAdapter.getCPUCapacity(Integer.parseInt(currentMetrics.get(METRIC_CPU_FREQUENCY)), Integer.parseInt(currentMetrics.get(METRIC_CONTAINER_NB_CPU))); Node n = new DefaultNode(this.currentHostname, Integer.parseInt(currentMetrics.get(METRIC_CONTAINER_NB_CPU)), cpuCapacity, (int) memKb / 1000); //Memory in MB n.setHypervisorID(currentMetrics.get(METRIC_CONTAINER_TYPE)); n.setIPAddress(this.currentIp); n.setStartupDriverID(currentMetrics.get(METRIC_CONTAINER_STARTUP_DRIVER)); n.setShutdownDriverID(currentMetrics.get(METRIC_CONTAINER_SHUTDOWN_DRIVER)); n.setMigrationDriverID(currentMetrics.get(METRIC_CONTAINER_MIGRATION_DRIVER)); n.setRunDriverID(currentMetrics.get(METRIC_CONTAINER_RUN_DRIVER)); n.setResumeDriverID(currentMetrics.get(METRIC_CONTAINER_RESUME_DRIVER)); n.setSuspendDriverID(currentMetrics.get(METRIC_CONTAINER_SUSPEND_DRIVER)); n.setStopDriverID(currentMetrics.get(METRIC_CONTAINER_STOP_DRIVER)); this.currentConfiguration.addOnline(n); this.assigns.put(n, currentMetrics.get(METRIC_CONTAINER_LIST_VMS)); } else { float memKb = Integer.parseInt(currentMetrics.get(METRIC_CONTAINER_MEMORY_TOTAL)); int cpuCapacity = ConfigurationAdapter.getCPUCapacity(Integer.parseInt(currentMetrics.get(METRIC_CPU_FREQUENCY)), Integer.parseInt(currentMetrics.get(METRIC_CONTAINER_NB_CPU))); Node n = new DefaultNode(this.currentHostname, Integer.parseInt(currentMetrics.get(METRIC_CONTAINER_NB_CPU)), cpuCapacity, (int) memKb / 1000); //Memory in MB n.setHypervisorID(currentMetrics.get(METRIC_CONTAINER_TYPE)); n.setIPAddress(this.currentIp); n.setStartupDriverID(currentMetrics.get(METRIC_CONTAINER_STARTUP_DRIVER)); n.setShutdownDriverID(currentMetrics.get(METRIC_CONTAINER_SHUTDOWN_DRIVER)); n.setMigrationDriverID(currentMetrics.get(METRIC_CONTAINER_MIGRATION_DRIVER)); n.setRunDriverID(currentMetrics.get(METRIC_CONTAINER_RUN_DRIVER)); n.setResumeDriverID(currentMetrics.get(METRIC_CONTAINER_RESUME_DRIVER)); n.setSuspendDriverID(currentMetrics.get(METRIC_CONTAINER_SUSPEND_DRIVER)); n.setStopDriverID(currentMetrics.get(METRIC_CONTAINER_STOP_DRIVER)); this.currentConfiguration.addOffline(n); } } else if (isVirtualMachine(currentMetrics.keySet())) { Monitor.getLogger().debug(currentHostname + " is considered as a virtual machine"); //We just compute the pct of CPU consumption of the virtual machine and store it //We compute the exact value during the assignment float cpuPct = Float.parseFloat(this.currentMetrics.get(METRIC_CPU_PCT_NICE)) + Float.parseFloat(this.currentMetrics.get(METRIC_CPU_PCT_SYSTEM)) + Float.parseFloat(this.currentMetrics.get(METRIC_CPU_PCT_USER)); VirtualMachine vm = new DefaultVirtualMachine(this.currentHostname, Integer.parseInt(this.currentMetrics.get(METRIC_NB_CPU)), 0, Math.round(Float.parseFloat(this.currentMetrics.get(METRIC_VM_MEMORY_CONSUMPTION)) / 1000)); this.cpuPcts.put(vm, cpuPct); this.allVMs.add(vm); } else { Monitor.getLogger().debug(currentHostname + " is ignored"); } }/* else if (name.equals("HOST") && !this.isHostOnline) { logger.debug(" Host '" + this.currentHostname + "' is considered offline"); }*/ } /** * Test if the metrics related to a host correspond to a container. * * @param metrics the metrics * @return true if the host is a container */ private boolean isContainer(Set<String> metrics) { return metrics.contains(METRIC_CONTAINER_TYPE) && metrics.contains(METRIC_CONTAINER_LIST_VMS) && metrics.contains(METRIC_CONTAINER_MEMORY_TOTAL) && metrics.contains(METRIC_CONTAINER_NB_CPU) && metrics.contains(METRIC_CONTAINER_MIGRATION_DRIVER) /* && metrics.contains(METRIC_CONTAINER_SUSPEND_DRIVER) && metrics.contains(METRIC_CONTAINER_RESUME_DRIVER) && metrics.contains(METRIC_CONTAINER_RUN_DRIVER) && metrics.contains(METRIC_CONTAINER_STOP_DRIVER)*/ /* * No usuable at the moment, will be used in futur release && metrics.contains(METRIC_CONTAINER_STARTUP_DRIVER) && metrics.contains(METRIC_CONTAINER_SHUTDOWN_DRIVER)*/; } /** * Test if the metrics related to a host correspond to a virtual machine. * * @param metrics the metrics * @return true if the host is a virtual machine */ private boolean isVirtualMachine(Set<String> metrics) { return metrics.contains(METRIC_VM_MEMORY_CONSUMPTION) && metrics.contains(METRIC_NB_CPU); } @Override public void startDocument() throws SAXException { this.allVMs = new DefaultManagedElementSet<VirtualMachine>(); this.assigns = new HashMap<Node, String>(); this.currentConfiguration = new DefaultConfiguration(); this.cpuPcts = new HashMap<VirtualMachine, Float>(); this.onlines = new HashSet<String>(); } @Override public void startElement(String uri, String localName, String name, Attributes atts) throws SAXException { if (name.equals("HOST")) { this.currentMetrics = new HashMap<String, String>(); this.currentHostname = atts.getValue("NAME"); this.currentIp = atts.getValue("IP"); int tn = Integer.parseInt(atts.getValue("TN")); int tmax = Integer.parseInt(atts.getValue("TMAX")); if (isMetricViable(tn, tmax)) { this.onlines.add(this.currentHostname); } } else if (name.equals("METRIC")) { this.currentMetrics.put(atts.getValue("NAME"), atts.getValue("VAL")); } } /** * Get the configuration after the parsing of the XML Stream. * * @return a valid configuration */ public Configuration getConfiguration() { return this.currentConfiguration; } /** * Indicates wheter a metric if viable or not, given a tolerance and the delay since the last heartbeat. * * @param tn the delay since the last hearbeat * @param tmax the maximum theorical delay between heartbeat. * @return true if the metric is not viable (or up-to-date) */ private boolean isMetricViable(int tn, int tmax) { return tn < 4 * tmax; } }