├── .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 | 
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 |
--------------------------------------------------------------------------------