`
songzi0206
  • 浏览: 158783 次
  • 性别: Icon_minigender_1
  • 来自: 上海
博客专栏
Group-logo
All are from ...
浏览量:33800
Group-logo
Programming w...
浏览量:19676
社区版块
存档分类
最新评论

Jenkins JFR Plugin

 
阅读更多

        Jenkins JFR 插件主要是用来解析JRockit Flight Record (不了解JRockit 飞行日志可以google一下),并且以SVG图形展示CPU,MEM等情况。想进一步了解代码的可以戳这里:

https://github.com/WalseWu/jenkins-jfr

      

        言归正传,回到hudson plugin,关于hudson的介绍就戳这里: http://wiki.eclipse.org/The_Hudson_Book 。简述下:hudson的插件机制主要基于Stapler和Jelly实现,Stapler主要用于将Hudson Classes绑定到URLs,举个简单的例子: 比如有个方法Hudson.getJob(String jobName),那么URL /job/foo/ 将会和 Hudson.getJob("foo")的返回值对象(应该是一个Project对象)绑定。Jelly可以理解为类似jsp和jstl;hudson通过jelly和stapler展示页面,和获取model对象。既然是插件机制,hudson提供了很多扩展点,详细请戳这里:http://wiki.hudson-ci.org/display/HUDSON/Extension+points。下面从扩展点入手,来介绍下Jenkins JFR 插件的开发:

1. Recorder

        该扩展点(hudson.ExtensionPoint)是一种Publisher(hudson.tasks.Publisher),其类图我就不画了(其实是OEL系统上木有装画图工具)。看javadoc:

Recorder is a kind of Publisher that collects statistics from the build, and can mark builds as unstable/failure. This marking ensures that builds are marked accordingly before notifications are sent via Notifiers. Otherwise, if the build is marked failed after some notifications are sent, inconsistency ensues.

To register a custom Publisher from a plugin, put Extension on your descriptor.

 意思表述的很明确,我用代码来解释吧。

 

  1)扩展Recorder,我写一个JFRPublisher作为整个插件的入口,类的架子大概如下:

public class JFRPublisher extends Recorder
{
        @Extension
	public static class DescriptorImpl extends BuildStepDescriptor<Publishe>
	{
		@Override
		public String getDisplayName()
		{
			return Messages.Publisher_DisplayName();
		}
		@Override
		public String getHelpFile()
		{
			return "/plugin/hudson-jfr/help.html";
		}
		public List<JFRReportParserDescriptor> getParserDescriptors()
		{
			return JFRReportParserDescriptor.all();
		}
		@Override
		public boolean isApplicable(@SuppressWarnings("rawtypes") Class<? extends AbstractProject> jobType)
		{
			return true;
		}
	}
       	private List<JFRReportParser> parsers;
	@DataBoundConstructor
	public JFRPublisher(List<? extends JFRReportParser> parsers)
	{
		if (parsers == null) {
			parsers = Collections.emptyList();
		}
		this.parsers = new ArrayList<JFRReportParser>(parsers);
	}
        @Override
	public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException,
			IOException
	{
                // .......暂时省略具体代码
         }
        //省略具体代码
}

          现在来解释前面的javadoc的描述,首先看最后一句注册一个custom publisher可以简单理解为代码中@Extension public static class DescriptorImpl extends BuildStepDescriptor<Publisher>,该类的作用是将JFRPubliusher注册为一个Publisher,于是在hudson配置页面会去查找hudson.plugins.jfr.JFRPublisher.config.jelly,用以显示页面,这里的的jelly是这样的:

<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">
  <f:entry 
      field="parsers">
    <f:hetero-list name="parsers" hasHeader="true"
                   descriptors="${descriptor.getParserDescriptors()}"
                   items="${instance.parsers}"
                   addCaption="${%Add a new report}"/>
  </f:entry>
</j:jelly>

          这是一个hetero-list,descriptors="${descriptor.getParserDescriptors()}"的值就是DescriptorImpl。getDisplayName()的返回值(这里是“Publish JFR result report”),items="${instance.parsers}"就是JFRPublisher的属性private List<JFRReportParser> parsers。先看下页面显示效果是这样的:


     当选中了Publish JFR result repot后,就会弹出一个parses(来自
private List<JFRReportParser> parsers)的下拉框提供选择(标题Add a new report来自上面jelly中addCaption设置),选择JFR Report就显示页面如下:



 

 2)ExtensionPoint

     该页面来自何处呢?显然来自JFRReportParser,因为上面下拉框中的每一项都是一个JFRReportParser对象。看代码:

 

public class JFRReportParser implements Describable<JFRReportParser>, ExtensionPoint
{
     @Extension
	public static class DescriptorImpl extends JFRReportParserDescriptor
	{
		@Override
		public String getDisplayName()
		{
			return "JFRReport";
		}
	}
  
       /**
	 * All registered implementations.
	 */
	public static ExtensionList<JFRReportParser> all()
	{
		return Hudson.getInstance().getExtensionList(JFRReportParser.class);
	}

        /**
	 * GLOB patterns that specify the jfr report.
	 */
	public final String glob;

	public final String title;

	public final int width;

	public final int height;

	/**
	 * JRockit Flight Recording events values which will be show on the graphs.
	 */
	public final String jfrEventSettingStr;
       @DataBoundConstructor
	public JFRReportParser(String glob, String jfrEventSettingStr, String title, int width, int height)
	{
		this.glob = glob == null || glob.length() == 0 ? getDefaultGlobPattern() : glob;
		this.jfrEventSettingStr = jfrEventSettingStr == null || jfrEventSettingStr.length() == 0 ? getDefaultJFREventsPattern()
				: jfrEventSettingStr.trim();
		resetDisplayName();
		this.title = title == null || title.length() == 0 ? "" : title;
		this.width = width <= 0 ? DEFAULT_WIDTH : width;
		this.height = height <= 0 ? DEFAULT_HEIGHT : height;
	}
    //........省略JFR event parse的逻辑,有兴趣可以 checkout git 源码:https://github.com/WalseWu/jenkins-jfr
}

 

           虽然扩展点不是Recoder,而是直接扩展Describable<JFRReportParser>, ExtensionPoint,但是和上面的publisher也大同小异,每一项的配置都会对应一个类中的属性,通过@DataBoundConstructor从页面提交中获取设置到变量中,对应jelly如下:

<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">
  <f:entry title="${%Report files}" field="glob">
    <f:textbox />
  </f:entry>
  <f:entry title="${%JFR Events}" field="jfrEventSettingStr">
    <f:textbox />
  </f:entry>
  <f:entry title="${%Graph Title:}" help="/plugin/hudson-jfr/graph-help.html">
    	<table width="100%">
    		<tr width="100%">
    			<td width="488">
    				<f:textbox field="title" width="488"/>
    			</td>
    			<td width="10"></td>
    			<td style="vertical-align:middle">${%Width}:</td>
    			<td>
    				<f:textbox field="width" width="100"/>
    			</td>
    			<td width="10"></td>
    			<td style="vertical-align:middle">${%Height}:</td>
    			<td>
    				<f:textbox field="height" width="100"/>
    			</td>
    		</tr>
    	</table>
  </f:entry>
</j:jelly>

 

3)BuildStep

    其实recorder本身就是BuildStep的实现类,该接口主要定义了一些生命周期函数,如prebuild,perform. perform方法可以用来添加一下action以便一些report保存到build中,著名的junit plugin就是这么做的。我们也类似:

 

//JFRPublisher
@Override
	public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException,
			IOException
	{
		PrintStream logger = listener.getLogger();

		// add the report to the build object.
		JFRBuildAction buildAction = new JFRBuildAction(build, logger, parsers);
		build.addAction(buildAction);

		for (JFRReportParser parser : parsers) {
			String glob = parser.glob;
			logger.println("JFR: Recording " + parser.getReportName() + " reports '" + glob + "'");

			List<FilePath> files = locateJFRReports(build.getWorkspace(), glob, logger);

			if (files.isEmpty()) {
				// build.setResult(Result.FAILURE);
				logger.println("JFR: no " + parser.getReportName() + " files matching '" + glob
						+ "' have been found. Has the report generated?. Setting Build to " + build.getResult());
				return true;
			}
			copyReportsToMaster(build, logger, files, parser.getDisplayName());
		}

		return true;
	}

 

     上面代码,每次build完,我创建了一个JFRBuildAction并添加到build中,这样buildAction就可以做他该做的事情(本插件当然是去解析展示JFR Report了);然后我遍历jenkins配置的parsers,根据配置去相应的jfr report 文件拷贝到每个build自己统一房jfr的地方。

 

4)StaplerProxy,Action

      下面是JFRBuilgAction:

 

public class JFRBuildAction implements Action, StaplerProxy{

    private final AbstractBuild<?, ?> build;
	private final List<JFRReportParser> parsers;

	private transient final PrintStream hudsonConsoleWriter;

	private transient WeakReference<JFRReportMap> jfrReportMap;

	private static final Logger logger = Logger.getLogger(JFRBuildAction.class.getName());

	public JFRBuildAction(AbstractBuild<?, ?> pBuild, PrintStream logger, List<JFRReportParser> parsers)
	{
		build = pBuild;
		hudsonConsoleWriter = logger;
		this.parsers = parsers;
	}
    public JFRReportParser getParserByDisplayName(String displayName)
	{
		if (parsers != null) {
			for (JFRReportParser parser : parsers) {
				if (parser.getDisplayName().equals(displayName)) {
					return parser;
				}
			}
		}
		return null;
	}

       public Object getTarget()
	{
		return getJfrReportMap();
	}

	public String getUrlName()
	{
		return "JFRReport";
	}

       private JFRReportMap getJfrReportMap()
	{
		JFRReportMap reportMap = null;
		WeakReference<JFRReportMap> wr = jfrReportMap;
		if (wr != null) {
			reportMap = wr.get();
			if (reportMap != null) {
				return reportMap;
			}
		}
		try {
			reportMap = new JFRReportMap(this, new StreamTaskListener(System.err, Computer.currentComputer().getDefaultCharset()));
		}
		catch (IOException e) {
			logger.log(Level.SEVERE, "Error creating new JFRReportMap()", e);
		}
		jfrReportMap = new WeakReference<JFRReportMap>(reportMap);
		return reportMap;
	}
//。。。省略部分代码
}

 重要的几个点:

a) getDisplayName返回值(我们config中会返回JFR Report)会在每个build页面的左侧生成一个link

b) 点击这个link会跳转到 由方法getTarget()返回值对应的地方, 所以我们接下来要看JFRReportMap

 

5)an ModelObject JFRReportMap

    上面点了link跳转到JFRReportMap到底是什么页面呢?先要看hudson.plugins.jfr.JFRReportMap.index.jelly

 

<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">
  <l:layout xmlns:jm="/hudson/plugins/jfr/tags" css="/plugin/jfr-plugin/css/style.css">
  <st:include it="${it.build}" page="sidepanel.jelly" />
    <l:main-panel>
    	<h1>JFR Report</h1>
         <j:forEach var="jfrReport" items="${it.getJFRListOrdered()}">
         	<a href="./jfrEventTimeGraph?height=-1&amp;width=-1&amp;jfrReportPosition=${jfrReport.getReportFileName()}" 
         		target="_blank" title="${%Click for larger image}">
            	<h2>${jfrReport.getReportFileName()}</h2>
            </a>
    		<object data="./jfrEventTimeGraph?jfrReportPosition=${jfrReport.getReportFileName()}" width="${jfrReport.getWidth()}" height="${jfrReport.getHeight()}" />
        </j:forEach>
    </l:main-panel>
  </l:layout>
</j:jelly>

     解读下,主要是遍历jfrReportMap.getJFRListOrdered(),每个JFRReport会生成一个<a href=... />和一个<object data="..." />,什么意思呢?其实url是./jfrEventTimeGraph?,后面是参数,这个url对应一个方法jfrReportMap。doJfrEventTimeGraph(StaplerRequest,StaplerResponse),这就是Staplar的作用。看代码:

 

//in JFRReportMap.java
public void doJfrEventTimeGraph(StaplerRequest request, StaplerResponse response) throws IOException
	{
		try {
			String parameter = request.getParameter("jfrReportPosition");
			JFreeChart chart = GraphHelper.createOverlappingJFREventChart(getJFRReport(parameter));
			GraphHelper.exportChartAsSVG(chart, getJFRReport(parameter).getWidth(), getJFRReport(parameter).getHeight(), request,
					response);
		}
		catch (Exception e) {
			String newline = System.getProperty("line.separator");
			//Set exception message back for the failed test.
			StringWriter sb = new StringWriter(2048);
			sb.append(e.getMessage());
			sb.append(newline);
			e.printStackTrace(new PrintWriter(sb, true));
			logger.info(sb.toString());
		}
	}

public List<JFRReport> getJFRListOrdered()
	{
		List<JFRReport> listJFR = new ArrayList<JFRReport>(getJFRReportMap().values());
		logger.info("Ordered JFR Reports:" + listJFR.size());
		Collections.sort(listJFR);
		return listJFR;
	}

 

     这样SVG图就展示了, 大图和小图的区别是url后面的参数不一样。

到这里jenkins相关的都介绍完了,还剩下具体report的解析:

 

//JFRReportMap.java 在构造时就解析jfr了
/**
	 * Parses the reports and build a {@link JFRReportMap}.
	 * 
	 * @throws IOException
	 *             If a report fails to parse.
	 */
	JFRReportMap(final JFRBuildAction buildAction, TaskListener listener) throws IOException
	{
		this.buildAction = buildAction;
		parseReports(getBuild(), listener, null);
	}

private void parseReports(AbstractBuild<?, ?> build, TaskListener listener, final String filename) throws IOException
	{
		File repo = new File(build.getRootDir(), JFRReportMap.getJFRReportDirRelativePath());
		File[] dirs = repo.listFiles(new FileFilter() {
			public boolean accept(File f)
			{
				return f.isDirectory();
			}
		});
		// this may fail, if the build itself failed, we need to recover
		// gracefully
		if (dirs != null) {
			for (File dir : dirs) {
				JFRReportParser p = buildAction.getParserByDisplayName(dir.getName());
				if (p != null) {
					File[] listFiles = dir.listFiles(new FilenameFilter() {

						public boolean accept(File dir, String name)
						{
							if (filename == null) {
								return true;
							}
							if (name.equals(filename)) {
								return true;
							}
							return false;
						}
					});
					addAll(p.parse(build, Arrays.asList(listFiles), listener));
				}
			}
		}
	}

//下面是JFRReportParser。parse
public Collection<JFRReport> parse(AbstractBuild<?, ?> build, Collection<File> reports, TaskListener listener)
			throws IOException
	{
		Set<FileGroup> groupReports = groupFiles(reports);
		List<JFRReport> result = new LinkedList<JFRReport>();

		for (FileGroup fg : groupReports) {
			JFRReport r = new JFRReport(width, height);
			r.setGlobalName(glob, fg, getTitle());
			parseEventsReport(fg, r, getJfrEventSettings());
			result.add(r);
		}
		Collections.sort(result);
		return result;
	}

    

     具体jfr report的结构设计见下面类图,代码就不占篇幅了,有兴趣的可以去check out code: https://github.com/WalseWu/jenkins-jfr

 

 

  • 大小: 19 KB
  • 大小: 10.5 KB
  • 大小: 48.8 KB
分享到:
评论

相关推荐

    gradle-jenkins-plugin-1.1.0.zip

    《Gradle Jenkins Plugin详解及其应用》 在软件开发过程中,自动化构建工具如Gradle和持续集成服务器如Jenkins是不可或缺的组成部分。它们协同工作,帮助开发者实现高效的代码构建、测试和部署。今天我们将深入探讨...

    allure-jenkins-plugin.hpi

    jenkins集成测试allure 插件

    metersphere-jenkins-plugin-v1.19.2.hpi

    Jenkins 插件(metersphere):metersphere-jenkins-plugin-v1.19.2.hpi

    jenkins的Deploy Plugin插件

    jenkins的Deploy Plugin插件 jenkins的Deploy Plugin插件 jenkins的Deploy Plugin插件

    Jenkins常用插件大全jenkins-plugin-war.tar.gz

    "Jenkins常用插件大全jenkins-plugin-war.tar.gz"这个压缩包文件很可能包含了多个在生产环境中常用的Jenkins插件,使得用户可以快速配置出满足需求的Jenkins环境。 1. **Jenkins插件系统**:Jenkins的插件系统是其...

    jenkins android plugin

    **Jenkins Android Plugin详解** Jenkins Android Plugin是针对Android开发的一款强大的持续集成工具插件,它使得在Jenkins环境中构建、测试和部署Android项目变得更加便捷和高效。此插件不仅支持自动化构建流程,...

    jenkins-control-plugin-0.10.2016-distribution.zip

    《IDEA离线安装Jenkins Control Plugin及其使用详解》 Jenkins Control Plugin是一款强大的插件,它使得在IntelliJ IDEA(简称IDEA)中管理Jenkins任务变得极为便捷。标题中的"jenkins-control-plugin-0.10.2016-...

    Gitee-Jenkins-Plugin-master.zip

    《Gitee-Jenkins-Plugin:打造高效持续集成与版本管理》 在现代软件开发流程中,持续集成(Continuous Integration, CI)与版本控制系统扮演着至关重要的角色。Gitee-Jenkins-Plugin作为一款专为Gitee设计的Jenkins...

    aws-codebuild-jenkins-plugin, 一个插件,允许Jenkins用户使用 AWS CodeBuild运行一个构建.zip

    aws-codebuild-jenkins-plugin, 一个插件,允许Jenkins用户使用 AWS CodeBuild运行一个构建 AWS CodeBuild插件Jenkins CodeBuild插件为Jenkins项目提供了一个构建步骤。 设置 Jenkins我们已经写了一个博客帖子,以...

    jenkins-plugin-lan-svn

    标题"jenkins-plugin-lan-svn"指向的是Jenkins的一个插件,主要目的是实现本地局域网内Subversion仓库的集成。Subversion是一种集中式的版本控制系统,广泛用于软件开发团队协作中,它允许团队成员跟踪和控制代码...

    Jenkins Email Extension Plugin

    Jenkins Email Extension Plugin是Jenkins自动化构建工具中的一个重要插件,它扩展了Jenkins的核心邮件通知功能,提供了更丰富的自定义选项和灵活性,使得开发者和持续集成团队能够更有效地进行沟通和协作。...

    jenkins-plugin

    "jenkins-plugin"指的是 Jenkins 的插件系统,这些插件扩展了 Jenkins 的核心功能,使其能够更好地适应各种开发环境和项目需求。下面将详细讨论这些插件的功能及其在IT开发中的重要性。 1. **maven-plugin**: Maven...

    Jenkins subversion 插件和所有依赖说明:依赖安装顺序

    这通常意味着先安装基础组件(如Jenkins Core更新或SVNKit),然后安装特定的插件(如Jenkins Subversion Plugin)。 在配置Jenkins与Subversion的集成时,以下是一些关键步骤: 1. **配置Subversion仓库URL**:在...

    jenkins全套插件plugins.zip

    jenkins需要安装的plugins,安装方法:jenkins安装好后,直接将plugins.zip上传至jenkins工作目录,并在工作目录(和原有plugins目录同级目录)下解压即可,然后重启jenkins服务。

    Jenkins SVN Publisher Plugins下载

    Jenkins SVN Publisher Plugin是Jenkins生态系统中的一个重要组件,主要用于自动化构建过程中的版本控制集成。它允许用户在Jenkins持续集成服务器完成构建后,自动将生成的成果物发布到Subversion(SVN)仓库中,...

    phabricator-jenkins-plugin, Jenkins插件,集成 Phabricator,Harbormaster和 Uberalls.zip

    phabricator-jenkins-plugin, Jenkins插件,集成 Phabricator,Harbormaster和 Uberalls phabricator-jenkins插件 这个插件提供了与Jenkins集成的Phabricator 。 它允许Jenkins通过 Harbormaster ( 或者如果...

    jenkins Role Strategy Plugin 2.1.0

    copy the resulting ./target/credentials.hpi file to the $JENKINS_HOME/plugins directory. Don't forget to restart Jenkins afterwards. 2. or use the plugin management console ...

Global site tag (gtag.js) - Google Analytics