/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.model; import hudson.Extension; import hudson.model.MultiStageTimeSeries.TimeScale; import hudson.model.MultiStageTimeSeries.TrendChart; import hudson.util.ColorPalette; import hudson.util.NoOverlapCategoryAxis; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.CategoryLabelPositions; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.renderer.category.LineAndShapeRenderer; import org.jfree.data.category.CategoryDataset; import org.jfree.ui.RectangleInsets; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.export.Exported; import java.awt.*; import java.io.IOException; import java.util.List; /** * Utilization statistics for a node or a set of nodes. * * <h2>Implementation Note</h2> * <p> * Instances of this class is not capable of updating the statistics itself * — instead, it's done by the {@link LoadStatisticsUpdater} timer. * This is more efficient (as it allows us a single pass to update all stats), * but it's not clear to me if the loss of autonomy is worth it. * * @author Kohsuke Kawaguchi * @see Label#loadStatistics * @see Hudson#overallLoad */ @ExportedBean public abstract class LoadStatistics { /** * Number of busy executors and how it changes over time. */ @Exported public final MultiStageTimeSeries busyExecutors; /** * Number of total executors and how it changes over time. */ @Exported public final MultiStageTimeSeries totalExecutors; /** * Number of {@link Queue.BuildableItem}s that can run on any node in this node set but blocked. */ @Exported public final MultiStageTimeSeries queueLength; protected LoadStatistics(int initialTotalExecutors, int initialBusyExecutors) { this.totalExecutors = new MultiStageTimeSeries( Messages._LoadStatistics_Legends_TotalExecutors(), ColorPalette.BLUE, initialTotalExecutors,DECAY); this.busyExecutors = new MultiStageTimeSeries( Messages._LoadStatistics_Legends_BusyExecutors(), ColorPalette.RED, initialBusyExecutors,DECAY); this.queueLength = new MultiStageTimeSeries( Messages._LoadStatistics_Legends_QueueLength(),ColorPalette.GREY, 0, DECAY); } public float getLatestIdleExecutors(TimeScale timeScale) { return totalExecutors.pick(timeScale).getLatest() - busyExecutors.pick(timeScale).getLatest(); } /** * Computes the # of idle executors right now and obtains the snapshot value. */ public abstract int computeIdleExecutors(); /** * Computes the # of total executors right now and obtains the snapshot value. */ public abstract int computeTotalExecutors(); /** * Computes the # of queue length right now and obtains the snapshot value. */ public abstract int computeQueueLength(); /** * Creates a trend chart. */ public JFreeChart createChart(CategoryDataset ds) { final JFreeChart chart = ChartFactory.createLineChart(null, // chart title null, // unused null, // range axis label ds, // data PlotOrientation.VERTICAL, // orientation true, // include legend true, // tooltips false // urls ); chart.setBackgroundPaint(Color.white); final CategoryPlot plot = chart.getCategoryPlot(); plot.setBackgroundPaint(Color.WHITE); plot.setOutlinePaint(null); plot.setRangeGridlinesVisible(true); plot.setRangeGridlinePaint(Color.black); final LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer(); renderer.setBaseStroke(new BasicStroke(3)); configureRenderer(renderer); final CategoryAxis domainAxis = new NoOverlapCategoryAxis(null); plot.setDomainAxis(domainAxis); domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90); domainAxis.setLowerMargin(0.0); domainAxis.setUpperMargin(0.0); domainAxis.setCategoryMargin(0.0); final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); // crop extra space around the graph plot.setInsets(new RectangleInsets(0, 0, 0, 5.0)); return chart; } protected void configureRenderer(LineAndShapeRenderer renderer) { renderer.setSeriesPaint(0, ColorPalette.BLUE); // total renderer.setSeriesPaint(1, ColorPalette.RED); // busy renderer.setSeriesPaint(2, ColorPalette.GREY); // queue } /** * Creates {@link CategoryDataset} which then becomes the basis * of the load statistics graph. */ public TrendChart createTrendChart(TimeScale timeScale) { return MultiStageTimeSeries.createTrendChart(timeScale,totalExecutors,busyExecutors,queueLength); } /** * Generates the load statistics graph. */ public TrendChart doGraph(@QueryParameter String type) throws IOException { return createTrendChart(TimeScale.parse(type)); } public Api getApi() { return new Api(this); } /** * With 0.90 decay ratio for every 10sec, half reduction is about 1 min. */ public static final float DECAY = Float.parseFloat(System.getProperty(LoadStatistics.class.getName()+".decay","0.9")); /** * Load statistics clock cycle in milliseconds. Specify a small value for quickly debugging this feature and node provisioning through cloud. */ public static int CLOCK = Integer.getInteger(LoadStatistics.class.getName()+".clock",10*1000); /** * Periodically update the load statistics average. */ @Extension public static class LoadStatisticsUpdater extends PeriodicWork { public long getRecurrencePeriod() { return CLOCK; } protected void doRun() { Hudson h = Hudson.getInstance(); List<hudson.model.Queue.BuildableItem> bis = h.getQueue().getBuildableItems(); // update statistics on slaves for( Label l : h.getLabels() ) { l.loadStatistics.totalExecutors.update(l.getTotalExecutors()); l.loadStatistics.busyExecutors .update(l.getBusyExecutors()); int q=0; for (hudson.model.Queue.BuildableItem bi : bis) { if(bi.task.getAssignedLabel()==l) q++; } l.loadStatistics.queueLength.update(q); } // update statistics of the entire system ComputerSet cs = new ComputerSet(); h.overallLoad.totalExecutors.update(cs.getTotalExecutors()); h.overallLoad.busyExecutors .update(cs.getBusyExecutors()); int q=0; for (hudson.model.Queue.BuildableItem bi : bis) { if(bi.task.getAssignedLabel()==null) q++; } h.overallLoad.queueLength.update(q); h.overallLoad.totalQueueLength.update(bis.size()); } } }