/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation, and/or the GNU Lesser * General Public License, version 2.1, also as published by the Free * Software Foundation. * * This program 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 General Public License and the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.rhq.plugins.platform; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.hyperic.sigar.Cpu; import org.hyperic.sigar.CpuInfo; import org.rhq.core.domain.measurement.AvailabilityType; import org.rhq.core.domain.measurement.MeasurementDataNumeric; import org.rhq.core.domain.measurement.MeasurementDataTrait; import org.rhq.core.domain.measurement.MeasurementReport; import org.rhq.core.domain.measurement.MeasurementScheduleRequest; import org.rhq.core.pluginapi.inventory.ResourceComponent; import org.rhq.core.pluginapi.inventory.ResourceContext; import org.rhq.core.pluginapi.measurement.MeasurementFacet; import org.rhq.core.pluginapi.util.ObjectUtil; import org.rhq.core.system.CpuInformation; /** * A Resource component representing a CPU core. */ public class CpuComponent implements ResourceComponent<PlatformComponent>, MeasurementFacet { private CpuInformation cpuInformation = null; private CpuEntry startCpuEntry = null; /** * A Map of cpu metric names to the last (Sigar) Cpu record used in calculations for that metric in getValues. * This allows us to take proper cpu usage deltas for each metric, on different schedules. See * RHQ-245 for why this is necessary. */ private Map<String, CpuEntry> cpuCache; public void start(ResourceContext<PlatformComponent> resourceContext) { if (resourceContext.getSystemInformation().isNative()) { cpuInformation = resourceContext.getSystemInformation().getCpu( Integer.parseInt(resourceContext.getResourceKey())); cpuCache = new HashMap<String, CpuEntry>(); startCpuEntry = new CpuEntry(cpuInformation.getCpu()); } return; } public void stop() { return; } public AvailabilityType getAvailability() { if (this.cpuInformation != null) { this.cpuInformation.refresh(); return (this.cpuInformation.isEnabled()) ? AvailabilityType.UP : AvailabilityType.DOWN; } else { return AvailabilityType.UP; } } public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> metrics) { if (cpuInformation != null) { cpuInformation.refresh(); Cpu cpu = null; CpuEntry currentCpu = null; CpuInfo cpuInfo = null; // this is probably gonna be used for traits only for (MeasurementScheduleRequest request : metrics) { String property = request.getName(); if (property.startsWith("Cpu.")) { // Grab the current cpu info from SIGAR, only once for this cpu for all processed schedules if (cpu == null) { cpu = cpuInformation.getCpu(); } property = property.substring(property.indexOf(".") + 1); Long longValue = (Long) ObjectUtil.lookupAttributeProperty(cpu, property); // A value of -1 indicates SIGAR does not support the metric on the Agent platform type. if (longValue != null && longValue != -1) { report.addData(new MeasurementDataNumeric(request, longValue.doubleValue())); } } else if (property.startsWith("CpuPerc.")) { /* * ! we no longer use the SIGAR CpuPerc object to report cpu percentage metrics. See * ! RHQ-245 for an explanation. We now calculate our own percentages using * ! the current raw cpu info from Sigar and the previous cpu numbers, cached per metric. * ! This allows us to avoid the problem in RHQ-245 while handling perCpu-perMetric schedule * ! granularity. */ // Grab the current cpu info from SIGAR, only once for this cpu for all processed schedules if (null == cpu) { cpu = cpuInformation.getCpu(); } // Create a Cpu cacheEntry only once for this cpu for all processed schedules if (null == currentCpu) { currentCpu = new CpuEntry(cpu); } // Get the previous cpu numbers to be used for this metric and update the cache for the next go. CpuEntry previousCpu = cpuCache.put(property, currentCpu); previousCpu = (null == previousCpu) ? startCpuEntry : previousCpu; // if for some reason the delta time is excessive then toss the metric, since it depends // on a reasonable interval between prev and curr. This can happen due to avail down or a newly // activated metric. Allow up to twice the metric interval. If the metric interval is // 0 (for a live data request) then just use a 10 minute interval. Number num = null; long deltaTime = currentCpu.getTimestamp() - previousCpu.getTimestamp(); long metricInterval = (0 < request.getInterval()) ? request.getInterval() : 600000L; if (deltaTime <= (2 * metricInterval)) { // Use the same calculation that SIGAR uses to generate the percentages. The difference is that // we use a safe "previous" cpu record. num = getPercentage(previousCpu.getCpu(), cpu, property); } // Uncomment to see details about the calculations. //System.out.println("\nCPU-" + cpuInformation.getCpuIndex() + " Interval=" // + ((currentCpu.getTimestamp() - previousCpu.getTimestamp()) / 1000) + " " + property + "=" // + num + "\n Prev=" + previousCpu + "\n Curr=" + currentCpu); if (num != null) { report.addData(new MeasurementDataNumeric(request, num.doubleValue())); } } else if (property.startsWith("CpuInfo.")) { if (cpuInfo == null) { cpuInfo = cpuInformation.getCpuInfo(); } property = property.substring(property.indexOf(".") + 1); Number num = ((Number) ObjectUtil.lookupAttributeProperty(cpuInfo, property)); if (num != null) { report.addData(new MeasurementDataNumeric(request, num.doubleValue())); } } else if (property.startsWith("CpuTrait.")) { if (cpuInfo == null) { cpuInfo = cpuInformation.getCpuInfo(); } property = property.substring(property.indexOf(".") + 1); Object o = ObjectUtil.lookupAttributeProperty(cpuInfo, property); if (o != null) { String res; if ("model".equals(property) || "vendor".equals(property)) { res = (String) o; } else { res = String.valueOf(o); } report.addData(new MeasurementDataTrait(request, res)); } } } } return; } /** * Calculate the percentage of CPU taken up by the requested cpu property (metric) for the interval * defined by the prev and curr cpu numbers. This algorithm is taken from SIGAR. See * http://svn.hyperic.org/projects/sigar/trunk/src/sigar_format.c ( sigar_cpu_perc_calculate() ) */ private Number getPercentage(Cpu prev, Cpu curr, String property) { Number result = 0.0; double diff_user, diff_sys, diff_nice, diff_idle; double diff_wait, diff_irq, diff_soft_irq, diff_stolen; double diff_total; diff_user = curr.getUser() - prev.getUser(); diff_sys = curr.getSys() - prev.getSys(); diff_nice = curr.getNice() - prev.getNice(); diff_idle = curr.getIdle() - prev.getIdle(); diff_wait = curr.getWait() - prev.getWait(); diff_irq = curr.getIrq() - prev.getIrq(); diff_soft_irq = curr.getSoftIrq() - prev.getSoftIrq(); diff_stolen = curr.getStolen() - prev.getStolen(); // It's not exactly clear to me what SIGAR is doing here. It may be to handle a rollover of // the cumulative cpu usage numbers. If so, this code could maybe be improved to still // determine the total usage. diff_user = diff_user < 0 ? 0 : diff_user; diff_sys = diff_sys < 0 ? 0 : diff_sys; diff_nice = diff_nice < 0 ? 0 : diff_nice; diff_idle = diff_idle < 0 ? 0 : diff_idle; diff_wait = diff_wait < 0 ? 0 : diff_wait; diff_irq = diff_irq < 0 ? 0 : diff_irq; diff_soft_irq = diff_soft_irq < 0 ? 0 : diff_soft_irq; diff_stolen = diff_stolen < 0 ? 0 : diff_stolen; diff_total = diff_user + diff_sys + diff_nice + diff_idle + diff_wait + diff_irq + diff_soft_irq + diff_stolen; property = property.substring(property.lastIndexOf(".") + 1, property.length()); if ("idle".equals(property)) { result = diff_idle / diff_total; } else if ("sys".equals(property)) { result = diff_sys / diff_total; } else if ("user".equals(property)) { result = diff_user / diff_total; } else if ("wait".equals(property)) { result = diff_wait / diff_total; } else if ("nice".equals(property)) { result = diff_nice / diff_total; } else if ("irq".equals(property)) { result = diff_irq / diff_total; } else if ("softIrq".equals(property)) { result = diff_soft_irq / diff_total; } else if ("stolen".equals(property)) { result = diff_stolen / diff_total; } return result; } /** * Just a private, immutable, utility class for associating a CPU and timestamp with the raw Cpu object from SIGAR. */ private class CpuEntry { private Cpu cpu; private long timestamp; public CpuEntry(Cpu cpu) { this.cpu = cpu; this.timestamp = System.currentTimeMillis(); } public Cpu getCpu() { return cpu; } public long getTimestamp() { return timestamp; } public String toString() { return "CPU-" + cpuInformation.getCpuIndex() + "[" + new SimpleDateFormat("HH:mm:ss").format(timestamp) + "] = " + cpu; } } }