├── .gitignore ├── configuration.png ├── src └── main │ ├── resources │ ├── index.jelly │ └── com │ │ └── getbase │ │ └── jenkins │ │ └── plugins │ │ └── metrics │ │ └── history │ │ └── influxdb │ │ └── InfluxDbConfig │ │ └── config.jelly │ └── java │ └── com │ └── getbase │ └── jenkins │ └── plugins │ └── metrics │ └── history │ └── influxdb │ ├── generators │ ├── PointGenerator.java │ └── JenkinsBasePointGenerator.java │ ├── listeners │ └── InfluxDdPublisher.java │ └── InfluxDbConfig.java ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | *.iml 4 | work 5 | .meghanada -------------------------------------------------------------------------------- /configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zendesk/build-history-metrics/master/configuration.png -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Sends basic build statistics to influxdb 4 |
5 | -------------------------------------------------------------------------------- /src/main/java/com/getbase/jenkins/plugins/metrics/history/influxdb/generators/PointGenerator.java: -------------------------------------------------------------------------------- 1 | package com.getbase.jenkins.plugins.metrics.history.influxdb.generators; 2 | 3 | import org.influxdb.dto.Point; 4 | 5 | public interface PointGenerator { 6 | Point[] generate(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/resources/com/getbase/jenkins/plugins/metrics/history/influxdb/InfluxDbConfig/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jenkins-build-history-metrics-plugin 2 | ==================================== 3 | 4 | Jenkins plugin to send simple build statistics to InfluxDB 5 | 6 | ## InfluxDB configuration 7 | Before sending data to InfluxDB connection to InfluxDB needs to be 8 | configured in global jenkins configuration 9 | 10 | ![image](configuration.png) 11 | 12 | ## Informations send to InfluxDB about jenkins builds 13 | Plugin sends data to an InfluxDB measurement `jenkins_build_data` 14 | 15 | Following fields are being send to InfluxDB database 16 | 17 | | Field | Description | 18 | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | 19 | | job_name | jenkins job full display name | 20 | | build_number | | 21 | | build_result | SUCCESS, UNSTABLE, FAILURE, NOT_BUILT, ABORTED | 22 | | build_timestamp | when the build is scheduled for execution | 23 | | build_duration | miliseconds it took to build a job | 24 | | queuing_duration | miliseconds a job spent in a build queue | 25 | | total_duration | build_duration + queuing_duration | 26 | | build_status_message | stable, unstable, aborted, not built, back to normal, broken for a long time, broken since this build, broken since build {#build_number} | 27 | | job_owner | job's primary owner email | 28 | | job_score | the percentage health score (from 0 to 100 inclusive). | 29 | 30 | 31 | Following tags are being send to InfluxDB database 32 | 33 | | Tag | 34 | | -------------------------- | 35 | | job_name | 36 | | build_result | 37 | | job_owner | 38 | | job_score | -------------------------------------------------------------------------------- /src/main/java/com/getbase/jenkins/plugins/metrics/history/influxdb/listeners/InfluxDdPublisher.java: -------------------------------------------------------------------------------- 1 | package com.getbase.jenkins.plugins.metrics.history.influxdb.listeners; 2 | 3 | import com.getbase.jenkins.plugins.metrics.history.influxdb.InfluxDbConfig; 4 | import com.getbase.jenkins.plugins.metrics.history.influxdb.generators.JenkinsBasePointGenerator; 5 | import com.getbase.jenkins.plugins.metrics.history.influxdb.generators.PointGenerator; 6 | import hudson.Extension; 7 | import hudson.model.Run; 8 | import hudson.model.TaskListener; 9 | import hudson.model.listeners.RunListener; 10 | import org.apache.commons.lang.StringUtils; 11 | import org.influxdb.InfluxDB; 12 | import org.influxdb.InfluxDBFactory; 13 | import org.influxdb.dto.BatchPoints; 14 | import org.influxdb.dto.Point; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | import java.util.List; 19 | import java.util.logging.Level; 20 | import java.util.logging.Logger; 21 | 22 | /** 23 | * Send Jenkins build information to InfluxDB 24 | */ 25 | @Extension 26 | public class InfluxDdPublisher extends RunListener> { 27 | private static final Logger logger = Logger.getLogger(InfluxDdPublisher.class.getName()); 28 | 29 | @Extension 30 | public static final InfluxDbConfig dbConfig = new InfluxDbConfig(); 31 | 32 | @Override 33 | public void onCompleted(Run build, TaskListener listener){ 34 | 35 | List pointsToWrite = new ArrayList<>(); 36 | 37 | JenkinsBasePointGenerator jGen = new JenkinsBasePointGenerator(build); 38 | addPoints(pointsToWrite, jGen, listener); 39 | 40 | InfluxDB influxDB = StringUtils.isBlank(dbConfig.getUsername()) ? InfluxDBFactory.connect(dbConfig.getUrl()) : 41 | InfluxDBFactory.connect(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword()); 42 | writeToInflux(influxDB, pointsToWrite); 43 | listener.getLogger().println("[InfluxDB Plugin] Completed."); 44 | } 45 | 46 | private void addPoints(List pointsToWrite, PointGenerator generator, TaskListener listener) { 47 | try { 48 | pointsToWrite.addAll(Arrays.asList(generator.generate())); 49 | } catch (Exception e) { 50 | listener.getLogger().println("[InfluxDB Plugin] Failed to collect data. Ignoring Exception:" + e); 51 | } 52 | } 53 | 54 | private void writeToInflux(InfluxDB influxDB, List pointsToWrite) { 55 | /** 56 | * build batchpoints for a single write. 57 | */ 58 | try { 59 | BatchPoints batchPoints = BatchPoints 60 | .database(dbConfig.getDatabase()) 61 | .points(pointsToWrite.toArray(new Point[0])) 62 | .retentionPolicy(dbConfig.getRetentionPolicy()) 63 | .consistency(InfluxDB.ConsistencyLevel.ANY) 64 | .build(); 65 | influxDB.write(batchPoints); 66 | } catch (Exception e) { 67 | logger.log(Level.WARNING, "Could not report to InfluxDB. Ignoring Exception.", e); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/getbase/jenkins/plugins/metrics/history/influxdb/InfluxDbConfig.java: -------------------------------------------------------------------------------- 1 | package com.getbase.jenkins.plugins.metrics.history.influxdb; 2 | 3 | import hudson.util.FormValidation; 4 | import jenkins.model.GlobalConfiguration; 5 | import net.sf.json.JSONObject; 6 | import org.apache.commons.lang.StringUtils; 7 | import org.kohsuke.stapler.QueryParameter; 8 | import org.kohsuke.stapler.StaplerRequest; 9 | 10 | public class InfluxDbConfig extends GlobalConfiguration { 11 | public static final String PROTOCOL_ERROR_MESSAGE = "Only http and https protocols are supported"; 12 | 13 | private String description; 14 | private String url; 15 | private String username; 16 | private String password; 17 | private String database; 18 | private String retentionPolicy; 19 | 20 | public InfluxDbConfig(){ 21 | load(); 22 | } 23 | 24 | public static InfluxDbConfig get() { 25 | return GlobalConfiguration.all().get(InfluxDbConfig.class); 26 | } 27 | 28 | public String getDescription() { 29 | return description; 30 | } 31 | 32 | public void setDescription(String description) { 33 | this.description = description; 34 | } 35 | 36 | public String getUrl() { 37 | return url; 38 | } 39 | 40 | public void setUrl(String url) { 41 | this.url = url; 42 | } 43 | 44 | public String getUsername() { 45 | return username; 46 | } 47 | 48 | public void setUsername(String username) { 49 | this.username = username; 50 | } 51 | 52 | public String getPassword() { 53 | return password; 54 | } 55 | 56 | public void setPassword(String password) { 57 | this.password = password; 58 | } 59 | 60 | public String getDatabase() { 61 | return database; 62 | } 63 | 64 | public void setDatabase(String database) { 65 | this.database = database; 66 | } 67 | 68 | public String getRetentionPolicy() { 69 | return retentionPolicy; 70 | } 71 | 72 | public void setRetentionPolicy(String retentionPolicy) { 73 | this.retentionPolicy = retentionPolicy; 74 | } 75 | 76 | @Override 77 | public boolean configure(StaplerRequest request, JSONObject json) throws FormException { 78 | request.bindJSON(this, json); 79 | save(); 80 | return true; 81 | } 82 | @Override 83 | public String toString() { 84 | return "[url=" + this.url + ", description=" + this.description + ", username=" + this.username 85 | + ", password=*****, database=" + this.database + "]"; 86 | } 87 | 88 | public FormValidation doCheckUrl(@QueryParameter("url") final String url) { 89 | if (StringUtils.isBlank(url)) { 90 | return FormValidation.error("Provide valid InfluxDB URL. " + "For ex: \"http://localhost:8086\""); 91 | } 92 | if (validateProtocolUsed(url)) 93 | return FormValidation.error(PROTOCOL_ERROR_MESSAGE); 94 | return FormValidation.ok(); 95 | } 96 | 97 | private boolean validateProtocolUsed(String url) { 98 | return !(url.startsWith("http://") || url.startsWith("https://")); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.jenkins-ci.plugins 6 | plugin 7 | 2.33 8 | 9 | 10 | 11 | com.getbase.jenkins.plugins 12 | build-history-metrics 13 | 1.1.2 14 | Build history metrics plugin 15 | Jenkins plugin to publish build metrics to external storage 16 | hpi 17 | 18 | 19 | 8 20 | 21 | 22 | 2.32.1 23 | 24 | 25 | 1.26 26 | 3.1.2.10 27 | 0.10.0 28 | 29 | 2.7 30 | 31 | 32 | 33 | 34 | MIT License 35 | http://opensource.org/licenses/MIT 36 | 37 | 38 | 45 | 46 | 47 | repo.jenkins-ci.org 48 | https://repo.jenkins-ci.org/public/ 49 | 50 | 51 | 52 | 53 | repo.jenkins-ci.org 54 | https://repo.jenkins-ci.org/public/ 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.influxdb 62 | influxdb-java 63 | ${influxdb-java.version} 64 | 65 | 66 | 67 | 68 | org.jenkins-ci.plugins 69 | script-security 70 | ${jenkins-script-security.version} 71 | 72 | 73 | org.jenkins-ci.plugins 74 | metrics 75 | ${jenkins-metrics.version} 76 | 77 | 78 | com.synopsys.jenkinsci 79 | ownership 80 | ${jenkins-ownership.version} 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/main/java/com/getbase/jenkins/plugins/metrics/history/influxdb/generators/JenkinsBasePointGenerator.java: -------------------------------------------------------------------------------- 1 | package com.getbase.jenkins.plugins.metrics.history.influxdb.generators; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import com.synopsys.arc.jenkins.plugins.ownership.jobs.JobOwnerJobAction; 8 | import hudson.model.ParameterValue; 9 | import hudson.model.ParametersAction; 10 | import hudson.model.Result; 11 | import hudson.model.Run; 12 | import jenkins.metrics.impl.TimeInQueueAction; 13 | import org.influxdb.dto.Point; 14 | 15 | public class JenkinsBasePointGenerator implements PointGenerator { 16 | private static final String MEASUREMENT_NAME = "jenkins_build_data"; 17 | private static final String JOB_NAME = "job_name"; 18 | private static final String BUILD_NUMBER = "build_number"; 19 | private static final String BUILD_RESULT = "build_result"; 20 | private static final String BUILD_RESULT_INT = "build_result_int"; 21 | private static final String BUILD_TIMESTAMP = "build_timestamp"; 22 | private static final String BUILD_DURATION = "build_duration"; 23 | private static final String QUEUING_DURATION = "queuing_duration"; 24 | private static final String TOTAL_DURATION = "total_duration"; 25 | private static final String BUILD_STATUS_MESSAGE = "build_status_message"; 26 | private static final String JOB_OWNER = "job_owner"; 27 | private static final String JOB_SCORE = "job_score"; 28 | private static final String BUILD_URL = "build_url"; 29 | private static final String JOB_URL = "job_url"; 30 | 31 | private final Run build; 32 | 33 | public JenkinsBasePointGenerator(Run build) { 34 | this.build = build; 35 | } 36 | 37 | public Map getBuildParameters(Run build) { 38 | List actions = build.getActions(ParametersAction.class); 39 | if (actions != null) { 40 | Map parametersMap = new HashMap<>(); 41 | for (ParametersAction action : actions) { 42 | List parameters = action.getParameters(); 43 | if (parameters != null) { 44 | for (ParameterValue parameter : parameters) { 45 | String name = parameter.getName(); 46 | Object value = parameter.getValue(); 47 | parametersMap.put(name, value); 48 | } 49 | } 50 | } 51 | return parametersMap; 52 | } 53 | return null; 54 | } 55 | 56 | public Point[] generate() { 57 | // Build is not finished when running with pipelines. Duration must be calculated manually 58 | long startTime = build.getTimeInMillis(); 59 | long currTime = System.currentTimeMillis(); 60 | long dt = currTime - startTime; 61 | long duration = build.getDuration() == 0 ? dt : build.getDuration(); 62 | 63 | TimeInQueueAction action = build.getAction(TimeInQueueAction.class); 64 | String owner = build.getParent().getAction(JobOwnerJobAction.class).getOwnership().getPrimaryOwnerEmail(); 65 | int score = build.getParent().getBuildHealth().getScore(); 66 | final Result result = build.getResult(); 67 | final String resultStr = result != null ? result.toString() : "UNKNOWN"; 68 | final Integer resultInt = resultStr.equals("SUCCESS") ? 1 : 0; 69 | final String jobAbsoluteURL = build.getParent().getAbsoluteUrl(); 70 | final String buildAbsoluteURL = jobAbsoluteURL + build.getNumber(); 71 | final Run.Summary buildSummary = build.getBuildStatusSummary(); 72 | final Map buildParameters = getBuildParameters(build); 73 | 74 | Point.Builder point = Point 75 | .measurement(MEASUREMENT_NAME) 76 | .addField(JOB_NAME, build.getParent().getFullName()) 77 | .tag(JOB_NAME, build.getParent().getFullName()) 78 | .addField(JOB_URL, jobAbsoluteURL) 79 | .tag(JOB_URL, jobAbsoluteURL) 80 | .addField(JOB_OWNER, owner) 81 | .tag(JOB_OWNER, owner) 82 | .addField(BUILD_NUMBER, build.getNumber()) 83 | .addField(BUILD_RESULT, resultStr) 84 | .addField(BUILD_RESULT_INT, resultInt) 85 | .addField(JOB_SCORE, score) 86 | .addField(BUILD_URL, buildAbsoluteURL) 87 | .addField(BUILD_TIMESTAMP, build.getTimeInMillis()) 88 | .addField(BUILD_DURATION, duration) 89 | .addField(QUEUING_DURATION, action.getQueuingDurationMillis()) 90 | .addField(TOTAL_DURATION, duration + action.getQueuingDurationMillis()) 91 | .fields(buildParameters); 92 | 93 | if (buildSummary != null) { 94 | point.addField(BUILD_STATUS_MESSAGE, buildSummary.message); 95 | } 96 | return new Point[] {point.build()}; 97 | } 98 | } 99 | --------------------------------------------------------------------------------