提交 afddd51c 编写于 作者: K kohsuke

improved the abstraction around trend charting of MultiStageTimeSeries, and...

improved the abstraction around trend charting of MultiStageTimeSeries, and used that to generate charts for memory usage tracking

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@16863 71c3de6d-444a-0410-be80-ed276b4c234a
上级 1e69a24c
......@@ -24,9 +24,12 @@
package hudson.diagnosis;
import hudson.util.TimeUnit2;
import hudson.util.ColorPalette;
import hudson.Extension;
import hudson.model.PeriodicWork;
import hudson.model.MultiStageTimeSeries;
import hudson.model.MultiStageTimeSeries.TrendChart;
import hudson.model.MultiStageTimeSeries.TimeScale;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
......@@ -34,6 +37,9 @@ import java.lang.management.MemoryUsage;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.ArrayList;
import java.io.IOException;
import org.kohsuke.stapler.QueryParameter;
/**
* Monitors the memory usage of the system in OS specific way.
......@@ -52,11 +58,11 @@ public final class MemoryUsageMonitor extends PeriodicWork {
* Trend of the memory usage, after GCs.
* So this shows the accurate snapshot of the footprint of live objects.
*/
public final MultiStageTimeSeries used = new MultiStageTimeSeries(0,0);
public final MultiStageTimeSeries used = new MultiStageTimeSeries(Messages._MemoryUsageMonitor_USED(), ColorPalette.RED, 0,0);
/**
* Trend of the maximum memory size, after GCs.
*/
public final MultiStageTimeSeries max = new MultiStageTimeSeries(0,0);
public final MultiStageTimeSeries max = new MultiStageTimeSeries(Messages._MemoryUsageMonitor_TOTAL(), ColorPalette.BLUE, 0,0);
private MemoryGroup(List<MemoryPoolMXBean> pools, MemoryType type) {
for (MemoryPoolMXBean pool : pools) {
......@@ -90,6 +96,13 @@ public final class MemoryUsageMonitor extends PeriodicWork {
//
// return String.format("%d/%d/%d (%d%%)",used,cur,max,used*100/max);
}
/**
* Generates the memory usage statistics graph.
*/
public TrendChart doGraph(@QueryParameter String type) throws IOException {
return MultiStageTimeSeries.createTrendChart(TimeScale.parse(type),used,max);
}
}
public final MemoryGroup heap;
......
......@@ -23,11 +23,11 @@
*/
package hudson.model;
import hudson.Extension;
import hudson.model.MultiStageTimeSeries.TimeScale;
import hudson.util.ChartUtil;
import hudson.model.MultiStageTimeSeries.TrendChart;
import hudson.util.ColorPalette;
import hudson.util.NoOverlapCategoryAxis;
import hudson.Extension;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
......@@ -36,17 +36,12 @@ 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.DefaultCategoryDataset;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleInsets;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import java.awt.*;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
/**
......@@ -80,9 +75,12 @@ public abstract class LoadStatistics {
public final MultiStageTimeSeries queueLength;
protected LoadStatistics(int initialTotalExecutors, int initialBusyExecutors) {
this.totalExecutors = new MultiStageTimeSeries(initialTotalExecutors,DECAY);
this.busyExecutors = new MultiStageTimeSeries(initialBusyExecutors,DECAY);
this.queueLength = new MultiStageTimeSeries(0,DECAY);
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) {
......@@ -156,47 +154,15 @@ public abstract class LoadStatistics {
* Creates {@link CategoryDataset} which then becomes the basis
* of the load statistics graph.
*/
public CategoryDataset createDataset(TimeScale timeScale) {
return createDataset(timeScale,
new float[][]{
totalExecutors.pick(timeScale).getHistory(),
busyExecutors.pick(timeScale).getHistory(),
queueLength.pick(timeScale).getHistory()
},
new String[]{
Messages.LoadStatistics_Legends_TotalExecutors(),
Messages.LoadStatistics_Legends_BusyExecutors(),
Messages.LoadStatistics_Legends_QueueLength()
});
}
protected final DefaultCategoryDataset createDataset(TimeScale timeScale, float[][] dataPoints, String[] legends) {
assert dataPoints.length==legends.length;
int dataLength = dataPoints[0].length;
for (float[] dataPoint : dataPoints)
assert dataLength ==dataPoint.length;
DefaultCategoryDataset ds = new DefaultCategoryDataset();
DateFormat format = timeScale.createDateFormat();
Date dt = new Date(System.currentTimeMillis()-timeScale.tick*dataLength);
for (int i = dataLength-1; i>=0; i--) {
dt = new Date(dt.getTime()+timeScale.tick);
String l = format.format(dt);
for(int j=0; j<dataPoints.length; j++)
ds.addValue(dataPoints[j][i],legends[j],l);
}
return ds;
public TrendChart createTrendChart(TimeScale timeScale) {
return MultiStageTimeSeries.createTrendChart(timeScale,totalExecutors,busyExecutors,queueLength);
}
/**
* Generates the load statistics graph.
*/
public void doGraph(StaplerRequest req, StaplerResponse rsp, @QueryParameter String type) throws IOException {
if(type==null) type=TimeScale.MIN.name();
TimeScale scale = Enum.valueOf(TimeScale.class, type.toUpperCase());
ChartUtil.generateGraph(req, rsp, createChart(createDataset(scale)), 500, 400);
public TrendChart doGraph(@QueryParameter String type) throws IOException {
return createTrendChart(TimeScale.parse(type));
}
/**
......
......@@ -24,9 +24,34 @@
package hudson.model;
import hudson.util.TimeUnit2;
import hudson.util.NoOverlapCategoryAxis;
import hudson.util.ChartUtil;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;
import java.awt.*;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.ui.RectangleInsets;
import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletException;
/**
* Maintains several {@link TimeSeries} with different update frequencies to satisfy three goals;
......@@ -37,6 +62,16 @@ import java.text.SimpleDateFormat;
* @author Kohsuke Kawaguchi
*/
public class MultiStageTimeSeries {
/**
* Name of this data series.
*/
public final Localizable title;
/**
* Used to render a line in the trend chart.
*/
public final Color color;
/**
* Updated every 10 seconds. Keep data up to 1 hour.
*/
......@@ -52,12 +87,22 @@ public class MultiStageTimeSeries {
private int counter;
public MultiStageTimeSeries(float initialValue, float decay) {
public MultiStageTimeSeries(Localizable title, Color color, float initialValue, float decay) {
this.title = title;
this.color = color;
this.sec10 = new TimeSeries(initialValue,decay,6*60);
this.min = new TimeSeries(initialValue,decay,60*24);
this.hour = new TimeSeries(initialValue,decay,28*24);
}
/**
* @deprecated
* Use {@link #MultiStageTimeSeries(Localizable, Color, float, float)}
*/
public MultiStageTimeSeries(float initialValue, float decay) {
this(Messages._MultiStageTimeSeries_EMPTY_STRING(), Color.WHITE, initialValue,decay);
}
/**
* Call this method every 10 sec and supply a new data point.
*/
......@@ -117,5 +162,112 @@ public class MultiStageTimeSeries {
default: throw new AssertionError();
}
}
/**
* Parses the {@link TimeScale} from the query parameter.
*/
public static TimeScale parse(String type) {
if(type==null) return TimeScale.MIN;
return Enum.valueOf(TimeScale.class, type.toUpperCase());
}
}
/**
* Represents the trend chart that consists of several {@link MultiStageTimeSeries}.
*
* <p>
* This object is renderable as HTTP response.
*/
public static final class TrendChart implements HttpResponse {
public final TimeScale timeScale;
public final List<MultiStageTimeSeries> series;
public final DefaultCategoryDataset dataset;
public TrendChart(TimeScale timeScale, MultiStageTimeSeries... series) {
this.timeScale = timeScale;
this.series = new ArrayList<MultiStageTimeSeries>(Arrays.asList(series));
this.dataset = createDataset();
}
/**
* Creates a {@link DefaultCategoryDataset} for rendering a graph from a set of {@link MultiStageTimeSeries}.
*/
private DefaultCategoryDataset createDataset() {
float[][] dataPoints = new float[series.size()][];
for (int i = 0; i < series.size(); i++)
dataPoints[i] = series.get(i).pick(timeScale).getHistory();
int dataLength = dataPoints[0].length;
for (float[] dataPoint : dataPoints)
assert dataLength ==dataPoint.length;
DefaultCategoryDataset ds = new DefaultCategoryDataset();
DateFormat format = timeScale.createDateFormat();
Date dt = new Date(System.currentTimeMillis()-timeScale.tick*dataLength);
for (int i = dataLength-1; i>=0; i--) {
dt = new Date(dt.getTime()+timeScale.tick);
String l = format.format(dt);
for(int j=0; j<dataPoints.length; j++)
ds.addValue(dataPoints[j][i],series.get(j).title.toString(),l);
}
return ds;
}
/**
* Draws a chart into {@link JFreeChart}.
*/
public JFreeChart createChart() {
final JFreeChart chart = ChartFactory.createLineChart(null, // chart title
null, // unused
null, // range axis label
dataset, // 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));
for (int i = 0; i < series.size(); i++)
renderer.setSeriesPaint(i, series.get(i).color);
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;
}
/**
* Renders this object as an image.
*/
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
ChartUtil.generateGraph(req, rsp, createChart(), 500, 400);
}
}
public static TrendChart createTrendChart(TimeScale scale, MultiStageTimeSeries... data) {
return new TrendChart(scale,data);
}
}
......@@ -24,7 +24,8 @@
package hudson.model;
import hudson.model.MultiStageTimeSeries.TimeScale;
import org.jfree.data.category.DefaultCategoryDataset;
import hudson.model.MultiStageTimeSeries.TrendChart;
import hudson.util.ColorPalette;
/**
* {@link LoadStatistics} for the entire system (the master and all the slaves combined.)
......@@ -41,7 +42,8 @@ public class OverallLoadStatistics extends LoadStatistics {
/**
* Number of total {@link Queue.BuildableItem}s that represents blocked builds.
*/
public final MultiStageTimeSeries totalQueueLength = new MultiStageTimeSeries(0,DECAY);
public final MultiStageTimeSeries totalQueueLength = new MultiStageTimeSeries(
Messages._LoadStatistics_Legends_QueueLength(), ColorPalette.GREY, 0,DECAY);
/*package*/ OverallLoadStatistics() {
super(0,0);
......@@ -66,17 +68,7 @@ public class OverallLoadStatistics extends LoadStatistics {
* When drawing the overall load statistics, use the total queue length,
* not {@link #queueLength}, which just shows jobs that are to be run on the master.
*/
protected DefaultCategoryDataset createOverallDataset(TimeScale timeScale) {
return createDataset(timeScale,
new float[][]{
busyExecutors.pick(timeScale).getHistory(),
totalExecutors.pick(timeScale).getHistory(),
totalQueueLength.pick(timeScale).getHistory()
},
new String[]{
Messages.LoadStatistics_Legends_TotalExecutors(),
Messages.LoadStatistics_Legends_BusyExecutors(),
Messages.LoadStatistics_Legends_QueueLength()
});
protected TrendChart createOverallTrendChart(TimeScale timeScale) {
return MultiStageTimeSeries.createTrendChart(timeScale,busyExecutors,totalExecutors,totalQueueLength);
}
}
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
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.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<l:layout title="${it.displayName} Memory Usage">
<!-- TODO: where in the tree structure should this really belong? -->
<!-- TODO: st:include page="sidepanel.jelly" /-->
<l:main-panel>
<h1>
<img src="${imagesURL}/48x48/monitor.gif" alt=""/>
${%JVM Memory Usage}
</h1>
<j:set var="type" value="${request.getParameter('type') ?: 'min'}" />
<div>
${%Timespan}:
<j:choose>
<j:when test="${type != 'sec10'}">
<a href="?type=sec10">${%Short}</a>
</j:when>
<j:otherwise>
${%Short}
</j:otherwise>
</j:choose>
<st:nbsp />
<j:choose>
<j:when test="${type != 'min'}">
<a href="?type=min">${%Medium}</a>
</j:when>
<j:otherwise>
${%Medium}
</j:otherwise>
</j:choose>
<st:nbsp />
<j:choose>
<j:when test="${type != 'hour'}">
<a href="?type=hour">${%Long}</a>
</j:when>
<j:otherwise>
${%Long}
</j:otherwise>
</j:choose>
</div>
<img src="graph?type=${type}&amp;width=500&amp;height=300" />
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
MemoryUsageMonitor.USED=Used
MemoryUsageMonitor.TOTAL=Total
\ No newline at end of file
......@@ -111,6 +111,7 @@ Job.NoRecentBuildFailed=No recent builds failed.
Job.Pronoun=Project
Job.minutes=mins
MultiStageTimeSeries.EMPTY_STRING=
Queue.AllNodesOffline=All nodes of label ''{0}'' are offline
Queue.BlockedBy=Blocked by {0}
Queue.HudsonIsAboutToShutDown=Hudson is about to shut down
......
......@@ -63,7 +63,7 @@ public class LoadStatisticsTest extends TestCase {
ls.queueLength.update(1);
}
JFreeChart chart = ls.createChart(ls.createDataset(TimeScale.SEC10));
JFreeChart chart = ls.createTrendChart(TimeScale.SEC10).createChart();
BufferedImage image = chart.createBufferedImage(400,200);
ImageIO.write(image, "PNG", new FileOutputStream("chart.png"));
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册