build-buddy 1.11.0 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c3ac2498d85759e142f61aead4aebf0968721a4e
4
- data.tar.gz: 6af814ee68ac2f59f23b84eeef9ca9bfd454707c
3
+ metadata.gz: 4d50fe65cd2806a970546102b56304bdae80f2b6
4
+ data.tar.gz: 7fdd7e59a6b62df8fe884ec009020bdb85ec9cdd
5
5
  SHA512:
6
- metadata.gz: c04cbb95121ccd2dafc9d0445a7c03e94999e48dfdc8421f4f89c7dea5d857446208e193f51451370a2917964032f031b0dc0d5798884b99abc87f7d7f1918ea
7
- data.tar.gz: c9f08e085110a8a733032bc01a68d824a477f7d5b3149c0c5a968a64b085c057b92961a4602e7f35428702a306434670b5841b28afc6f32d02dc461c521b7289
6
+ metadata.gz: cdd33b96f5b8e83ec9eff07b0555038c03f629dca285de77975928d264ab68a5a6942d5550d0fedb4a01b0211757a55512dd66ea883f262abc30bbe03015d617
7
+ data.tar.gz: afec166a8cce90aa46b85ff38bc3b4eddc8204afc2eeeca57a1b994dc75c151bcd6da50591d171fa772e259d65d5dfca6797a18f11cc0274d5f5e0504b362e18
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'celluloid/current'
5
- require 'celluloid/supervision'
6
- require 'celluloid/supervision/container'
7
5
  require 'methadone'
8
6
  require 'build_buddy'
9
7
 
@@ -21,12 +19,8 @@ module BuildBuddy
21
19
 
22
20
  load config_file_name
23
21
 
24
- config_path = File.dirname(config_file_name)
25
- build_log_dir = File.expand_path(Config.build_log_dir, config_path)
26
-
27
- unless Dir.exist?(build_log_dir)
28
- Dir.mkdir(build_log_dir)
29
- end
22
+ Dir.mkdir(Config.build_log_dir) unless Dir.exist?(Config.build_log_dir)
23
+ Dir.mkdir(Config.hud_image_dir) unless Dir.exist?(Config.hud_image_dir)
30
24
 
31
25
  Slack.configure do |config|
32
26
  config.token = Config.slack_api_token
@@ -34,15 +28,23 @@ module BuildBuddy
34
28
 
35
29
  Celluloid.logger = Reel::Logger.logger
36
30
 
37
- Builder.supervise as: :builder
38
- Slacker.supervise as: :slacker
39
- Gitter.supervise as: :gitter
40
- Scheduler.supervise as: :scheduler
41
- Server.supervise as: :server
42
- Recorder.supervise as: :recorder
31
+ Celluloid::Actor[:builder] = Builder.new
32
+ Celluloid::Actor[:slacker] = Slacker.new
33
+ Celluloid::Actor[:gitter] = Gitter.new
34
+ Celluloid::Actor[:scheduler] = Scheduler.new
35
+ Celluloid::Actor[:server] = Server.new
36
+ Celluloid::Actor[:recorder] = Recorder.new
37
+
38
+ Celluloid::Actor[:recorder].async.gen_charts
43
39
 
44
40
  begin
45
- sleep
41
+ loop {
42
+ sleep(5)
43
+
44
+ unless Celluloid.actor_system.running.any? {|k| k.is_a?(BuildBuddy::Slacker)}
45
+ Celluloid::Actor[:slacker] = Slacker.new
46
+ end
47
+ }
46
48
  rescue Interrupt => e
47
49
  puts
48
50
  end
@@ -9,5 +9,5 @@ require 'build_buddy/recorder'
9
9
  require 'build_buddy/build_data'
10
10
 
11
11
  module BuildBuddy
12
- VERSION = "1.11.0"
12
+ VERSION = "1.12.0"
13
13
  end
@@ -5,24 +5,27 @@ module BuildBuddy
5
5
  attr_accessor :repo_full_name
6
6
  attr_accessor :branch
7
7
  attr_accessor :pull_request
8
- attr_accessor :repo_full_name
9
8
  attr_accessor :repo_sha
10
9
  attr_accessor :termination_type # :killed or :exited
10
+ attr_accessor :started_by
11
+ attr_accessor :stopped_by
11
12
  attr_accessor :exit_code
12
13
  attr_accessor :start_time
13
14
  attr_accessor :end_time
14
- attr_accessor :log_filename
15
- attr_accessor :flags # :no_upload, :test_channel
15
+ attr_accessor :flags
16
16
  attr_accessor :metrics
17
17
 
18
18
  def initialize(args)
19
19
  args.each do |key, value|
20
- setter = self.method((key.to_s + '=').to_sym)
21
-
22
- unless setter.nil?
23
- setter.call(value)
20
+ begin
21
+ self.method((key.to_s + '=').to_sym).call(value)
22
+ rescue NameError
23
+ # Ignore fields in the database we don't understand
24
24
  end
25
25
  end
26
+
27
+ # Set the id here so that we can use it to refer to this build_data in queue
28
+ @_id = BSON::ObjectId.new
26
29
  end
27
30
 
28
31
  def to_h
@@ -30,5 +33,26 @@ module BuildBuddy
30
33
  instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var) }
31
34
  hash
32
35
  end
36
+
37
+ def server_log_uri
38
+ Config.server_base_uri + '/log/' + @_id.to_s
39
+ end
40
+
41
+ def status_verb
42
+ if @termination_type == :killed
43
+ 'was stopped'
44
+ else
45
+ @exit_code != 0 ? 'failed' : 'succeeded'
46
+ end
47
+ end
48
+
49
+ def pull_request_uri
50
+ "https://github.com/#{@repo_full_name}/pull/#{@pull_request}"
51
+ end
52
+
53
+ def log_file_name
54
+ return nil if @start_time.nil?
55
+ File.join(Config.build_log_dir, "#{@start_time.strftime('%Y%m%d-%H%M%S')}.log")
56
+ end
33
57
  end
34
58
  end
@@ -27,7 +27,7 @@ module BuildBuddy
27
27
  @metrics_tempfile = Tempfile.new('build-metrics')
28
28
  @metrics_tempfile.close()
29
29
 
30
- repo_parts = build_data.repo_full_name.split('/')
30
+ repo_parts = @build_data.repo_full_name.split('/')
31
31
  git_repo_owner = repo_parts[0]
32
32
  git_repo_name = repo_parts[1]
33
33
  env = {}
@@ -71,6 +71,8 @@ fi
71
71
  cd ${GIT_REPO_NAME}
72
72
  )
73
73
 
74
+ build_root_dir = nil
75
+
74
76
  case build_data.type
75
77
  when :pull_request
76
78
  build_root_dir = expand_vars(Config.pull_request_root_dir)
@@ -113,14 +115,12 @@ source ${BUILD_SCRIPT}
113
115
 
114
116
  unless build_data.flags.nil?
115
117
  build_data.flags.each do |flag|
116
- env["BUILD_FLAG_#{flag.to_s.upcase}"] = '1'
118
+ env["BUILD_FLAG_#{key.to_s.upcase}"] = 1
117
119
  end
118
120
  end
119
121
 
120
122
  @build_data.start_time = Time.now.utc
121
- log_filename = File.join(File.expand_path(Config.build_log_dir),
122
- "build_#{build_data.type.to_s}_#{build_data.start_time.strftime('%Y%m%d_%H%M%S')}.log")
123
- @build_data.log_filename = log_filename
123
+ log_file_name = @build_data.log_file_name
124
124
 
125
125
  # Ensure build root and git user directory exists
126
126
  clone_dir = File.join(build_root_dir, git_repo_owner)
@@ -130,12 +130,19 @@ source ${BUILD_SCRIPT}
130
130
  script_filename = File.join(clone_dir, "build-buddy-bootstrap.sh")
131
131
  File.write(script_filename, build_script)
132
132
 
133
+ # Notify GitHub that build has started
134
+ if @build_data.type == :pull_request
135
+ Celluloid::Actor[:gitter].async.set_status(
136
+ build_data.repo_full_name, build_data.repo_sha, :pending, "Build has started",
137
+ build_data.server_log_uri)
138
+ end
139
+
133
140
  # Run the build script
134
141
  Bundler.with_clean_env do
135
- @pid = Process.spawn(env, "bash #{script_filename}", :pgroup => true, :chdir => clone_dir, [:out, :err] => log_filename)
142
+ @pid = Process.spawn(env, "bash #{script_filename}", :pgroup => true, :chdir => clone_dir, [:out, :err] => log_file_name)
136
143
  @gid = Process.getpgid(@pid)
137
144
  end
138
- info "Running build script (pid #{@pid}, gid #{@gid}) : Log #{log_filename}"
145
+ info "Running build script (pid #{@pid}, gid #{@gid}) : Log #{log_file_name}"
139
146
 
140
147
  if @watcher
141
148
  @watcher.terminate
@@ -164,10 +171,28 @@ source ${BUILD_SCRIPT}
164
171
 
165
172
  @build_data.metrics = metrics
166
173
 
174
+ # Send status to GitHub if pull request
175
+ if @build_data.type == :pull_request
176
+ git_state = (@build_data.termination_type == :killed ? :failure : @build_data.exit_code != 0 ? :error : :success)
177
+ status_verb = @build_data.status_verb
178
+ Celluloid::Actor[:gitter].async.set_status(
179
+ @build_data.repo_full_name, @build_data.repo_sha, git_state, "Build #{status_verb}",
180
+ @build_data.server_log_uri)
181
+ end
182
+
183
+ # Log the build completion and clean-up
167
184
  info "Process #{status.pid} #{@build_data.termination_type == :killed ? 'was terminated' : "exited (#{@build_data.exit_code})"}"
168
185
  Celluloid::Actor[:scheduler].async.on_build_completed(@build_data)
169
186
  @watcher.terminate
170
187
  @watcher = nil
188
+
189
+ # Delete older log files
190
+ log_file_names = Dir.entries(Config.build_log_dir).select { |f| !f.match(/\d{8}-\d{6}\.log/).nil? }.sort()
191
+ while log_file_names.count > Config.build_log_limit
192
+ file_name = log_file_names.shift
193
+ FileUtils.rm(File.join(Config.build_log_dir, file_name))
194
+ info "Removing oldest log file #{file_name}"
195
+ end
171
196
  end
172
197
 
173
198
  def stop_build
@@ -2,33 +2,44 @@ module BuildBuddy
2
2
  module Config
3
3
  extend self
4
4
 
5
- attr_accessor :github_webhook_port
6
- attr_accessor :github_webhook_secret_token
7
- attr_accessor :github_webhook_repo_full_name
8
- attr_accessor :github_api_token
9
- attr_accessor :slack_api_token
10
- attr_accessor :slack_build_channel
11
- attr_accessor :slack_test_channel
12
- attr_accessor :slack_builders
13
- attr_accessor :xcode_workspace
14
- attr_accessor :xcode_test_scheme
15
- attr_accessor :build_log_dir
16
- attr_accessor :pull_request_build_script
17
- attr_accessor :branch_build_script
18
- attr_accessor :pull_request_root_dir
19
- attr_accessor :branch_root_dir
20
- attr_accessor :allowed_build_branches
21
- attr_accessor :kill_build_after_mins
22
- attr_accessor :server_base_uri
23
- attr_accessor :mongo_uri
5
+ ATTRS = [
6
+ :github_webhook_port,
7
+ :github_webhook_secret_token,
8
+ :github_webhook_repo_full_name,
9
+ :github_api_token,
10
+ :slack_api_token,
11
+ :slack_build_channel,
12
+ :slack_builders,
13
+ :build_log_dir,
14
+ :build_log_limit,
15
+ :pull_request_build_script,
16
+ :branch_build_script,
17
+ :pull_request_root_dir,
18
+ :branch_root_dir,
19
+ :allowed_build_branches,
20
+ :kill_build_after_mins,
21
+ :server_base_uri,
22
+ :mongo_uri,
23
+ :hud_secret_token,
24
+ :hud_image_dir,
25
+ ]
26
+ attr_accessor(*ATTRS)
24
27
  end
25
28
 
26
29
  class << self
27
30
  def configure
28
- Config.github_webhook_port = 4567
29
- Config.kill_build_after_mins = 30
30
- Config.mongo_uri = 'mongodb://localhost:27017/build-buddy'
31
+ config.github_webhook_port = 4567
32
+ config.kill_build_after_mins = 30
33
+ config.mongo_uri = 'mongodb://localhost:27017/build-buddy'
34
+ config.build_log_limit = 30
31
35
  block_given? ? yield(Config) : Config
36
+ config.build_log_dir = File.expand_path(Config.build_log_dir.gsub(/\$(\w+)/) { ENV[$1] })
37
+ config.hud_image_dir = File.expand_path(Config.hud_image_dir.gsub(/\$(\w+)/) { ENV[$1] })
38
+ Config::ATTRS.map {|attr| ('@' + attr.to_s).to_sym }.each {|var|
39
+ if config.instance_variable_get(var).nil?
40
+ raise "Config value '#{var.to_s.delete('@')}' not set"
41
+ end
42
+ }
32
43
  end
33
44
 
34
45
  def config
@@ -14,14 +14,13 @@ module BuildBuddy
14
14
  info "Connected to Github"
15
15
  end
16
16
 
17
+ # state is one of :pending, :killed, :failure, :error, :success
17
18
  def set_status(repo_full_name, repo_sha, state, description, target_url)
18
19
  options = {
19
- :description => description.length > 140 ? "#{description[0..136]}..." : description,
20
- :context => 'build-buddy'
20
+ :description => description.length > 140 ? "#{description[0..136]}..." : description,
21
+ :context => 'build-buddy',
22
+ :target_url => target_url || ''
21
23
  }
22
- unless target_url.nil?
23
- options[:target_url] = target_url
24
- end
25
24
  @gh_client.create_status(repo_full_name, repo_sha, state.to_s, options)
26
25
  end
27
26
  end
@@ -2,6 +2,7 @@ require 'rubygems'
2
2
  require 'bundler'
3
3
  require 'celluloid'
4
4
  require 'mongo'
5
+ require 'gruff'
5
6
  require_relative './config.rb'
6
7
 
7
8
  module BuildBuddy
@@ -9,6 +10,8 @@ module BuildBuddy
9
10
  include Celluloid
10
11
  include Celluloid::Internals::Logger
11
12
 
13
+ LIMIT = 50
14
+
12
15
  def initialize()
13
16
  Mongo::Logger.logger.level = ::Logger::FATAL
14
17
  mongo_uri = BuildBuddy::Config.mongo_uri
@@ -20,28 +23,261 @@ module BuildBuddy
20
23
  def record_build_data(build_data)
21
24
  builds = @mongo[:builds]
22
25
  result = builds.insert_one(build_data.to_h)
23
- build_data._id = result.inserted_id
26
+ if build_data._id.nil?
27
+ build_data._id = result.inserted_id
28
+ end
24
29
  end
25
30
 
26
31
  def update_build_data(build_data)
27
- unless build_data._id.nil?
28
- builds = @mongo[:builds]
29
- builds.replace_one({ :_id => build_data._id }, build_data.to_h)
32
+ if build_data._id.nil?
33
+ return
30
34
  end
35
+
36
+ builds = @mongo[:builds]
37
+ builds.replace_one({ :_id => build_data._id }, build_data.to_h)
38
+
39
+ gen_charts
31
40
  end
32
41
 
33
42
  def get_build_data(id)
34
- document = @mongo[:builds].find({ :_id => BSON::ObjectId.from_string(id) }, { :limit => 1 }).first
35
- if document.nil?
43
+ doc = @mongo[:builds].find({ :_id => BSON::ObjectId.from_string(id) }, { :limit => 1 }).first
44
+ if doc.nil?
36
45
  return nil
37
46
  end
38
- BuildData.new(document)
47
+ BuildData.new(doc)
39
48
  end
40
49
 
41
50
  def get_build_data_history(limit)
42
- @mongo[:builds].find().sort(:start_time => -1).limit(limit).map do |document|
43
- BuildData.new(document)
51
+ @mongo[:builds].find().sort(:start_time => -1).limit(limit).map do |doc|
52
+ BuildData.new(doc)
53
+ end
54
+ end
55
+
56
+ def gen_charts
57
+ gen_daily_builds_chart
58
+ gen_coverage_chart
59
+ gen_lines_chart
60
+ gen_localized_chart
61
+ gen_times_chart
62
+ gen_warnings_chart
63
+ end
64
+
65
+ def gen_daily_builds_chart
66
+ # Generates a chart showing builds per day
67
+ data = []
68
+ keys = []
69
+ @mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).each do |doc|
70
+ next if doc['start_time'].nil?
71
+ start_data_components = doc['start_time'].to_s.split(' ')[0].split('-')
72
+ date_string = start_data_components[1] + "/" + start_data_components[2]
73
+
74
+ if keys.include?(date_string)
75
+ data.each { |arr|
76
+ if arr[0] == date_string
77
+ if doc['pull_request'] != nil
78
+ arr[1] = arr[1] + 1
79
+ else
80
+ arr[2] = arr[2] + 1
81
+ end
82
+ end
83
+ }
84
+ else
85
+ keys.push(date_string)
86
+ if doc['pull_request'] != nil
87
+ data.insert(0, [date_string, 1, 0])
88
+ else
89
+ data.insert(0, [date_string, 0, 1])
90
+ end
91
+ end
92
+ end
93
+
94
+ g = Gruff::StackedBar.new
95
+ g.title = "Builds per Day"
96
+ g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
97
+ g.theme = {
98
+ :colors => ['#12a702', '#aedaa9'],
99
+ :font_color => 'black',
100
+ :background_colors => 'white'
101
+ }
102
+ g.font = '/Library/Fonts/Verdana.ttf'
103
+ g.data("Pull Request (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
104
+ g.data("Other (#{data.size > 0 ? data[data.size - 1][2] : 0})", data.map {|d| d[2]})
105
+ g.x_axis_label = 'Date'
106
+ g.y_axis_label = 'Number of Builds'
107
+ g.y_axis_increment = 2
108
+ g.write File.join(Config.hud_image_dir, "daily_builds.png")
109
+ end
110
+
111
+ def gen_coverage_chart
112
+ # Generate code coverage numbers
113
+ data = []
114
+ keys = []
115
+ @mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).map do |doc|
116
+ next if doc['start_time'].nil?
117
+ start_date_components = doc['start_time'].to_s.split(' ')[0].split('-')
118
+ date_string = start_date_components[1] + "/" + start_date_components[2]
119
+ pull_number = doc['pull_request'].to_i
120
+ if pull_number == 0 && doc['metrics']['coverage'] != nil && !keys.include?(date_string)
121
+ keys.push(date_string)
122
+
123
+ missed_lines = doc['metrics']['coverage']['swift_files']['missed_lines'] + doc['metrics']['coverage']['m_files']['missed_lines']
124
+ total_lines = doc['metrics']['coverage']['swift_files']['total_lines'] + doc['metrics']['coverage']['m_files']['total_lines']
125
+ coverage_percent = ((1.0 * (total_lines - missed_lines) / total_lines) * 100).to_i
126
+ data.insert(0, [date_string, coverage_percent])
127
+ end
128
+ end
129
+
130
+ g = Gruff::Bar.new
131
+ g.minimum_value = 0
132
+ g.maximum_value = 100
133
+ g.title = "Coverage"
134
+ g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
135
+ g.theme = {
136
+ :colors => ['#822F92'],
137
+ :font_color => 'black',
138
+ :background_colors => 'white'
139
+ }
140
+ g.font = '/Library/Fonts/Verdana.ttf'
141
+ g.data("covered % (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
142
+ g.x_axis_label = 'Date'
143
+ g.y_axis_label = 'Coverage %'
144
+ g.write File.join(Config.hud_image_dir, "code_coverage.png")
145
+ end
146
+
147
+ def gen_lines_chart
148
+ # Generates a chart displaying number of lines of code by Swift/Objective-C.
149
+ data = []
150
+ keys = []
151
+ @mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).map do |doc|
152
+ next if doc['start_time'].nil?
153
+ start_date_components = doc['start_time'].to_s.split(' ')[0].split('-')
154
+ date_string = start_date_components[1] + "/" + start_date_components[2]
155
+ pull_number = doc['pull_request'].to_i
156
+ if pull_number == 0 && doc['metrics']['coverage'] != nil && !keys.include?(date_string)
157
+ keys.push(date_string)
158
+ data.insert(0, [date_string, doc['metrics']['coverage']['swift_files']['total_lines'], doc['metrics']['coverage']['m_files']['total_lines']])
159
+ end
160
+ end
161
+
162
+ g = Gruff::StackedBar.new
163
+ g.title = "Lines of Code"
164
+ g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
165
+ g.theme = {
166
+ :colors => ['#C17B3A', '#C1BA3A'],
167
+ :font_color => 'black',
168
+ :background_colors => 'white'
169
+ }
170
+ g.font = '/Library/Fonts/Verdana.ttf'
171
+ g.data("Swift (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
172
+ g.data("Objective-C (#{data.size > 0 ? data[data.size - 1][2] : 0})", data.map {|d| d[2]})
173
+ g.x_axis_label = 'Day'
174
+ g.y_axis_label = 'Lines of Code'
175
+ g.write File.join(Config.hud_image_dir, "lines_of_code.png")
176
+ end
177
+
178
+ def gen_localized_chart
179
+ # Generates a chart displaying localization information for past pull requests.
180
+ # Displays # strings, # descriptions, and # words.
181
+ data = []
182
+ keys = []
183
+ @mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).map do |doc|
184
+ pull_number = doc['pull_request'].to_i
185
+ if pull_number != 0 && !keys.include?(pull_number)
186
+ keys.push(pull_number)
187
+ data.insert(0, [pull_number, doc['metrics']['strings'], doc['metrics']['descriptions'], doc['metrics']['words']])
188
+ end
44
189
  end
190
+
191
+ g = Gruff::Line.new
192
+ g.title = 'Localization'
193
+ g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
194
+ g.dot_style = :square
195
+ g.font = '/Library/Fonts/Verdana.ttf'
196
+ g.data("# strings (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
197
+ g.data("# descriptions (#{data.size > 0 ? data[data.size - 1][2] : 0})", data.map {|d| d[2]})
198
+ g.data("# words (#{data.size > 0 ? data[data.size - 1][3] : 0})", data.map {|d| d[3]})
199
+ g.x_axis_label = 'Pull Request'
200
+
201
+ g.theme = {
202
+ :colors => ['#e65954', '#0a2154', '#2a7b20'],
203
+ :font_color => 'black',
204
+ :background_colors => 'white'
205
+ }
206
+
207
+ g.write(File.join(Config.hud_image_dir, 'localization_data.png'))
208
+ end
209
+
210
+ def gen_times_chart
211
+ # Generates a chart displaying build time information for past pull requests.
212
+ data = []
213
+ keys = []
214
+ @mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).map do |doc|
215
+ next if doc['metrics'].nil?
216
+ pull_number = doc['pull_request'].to_i
217
+ if pull_number != 0 && !keys.include?(pull_number) && doc['metrics']['test_build_end'] != nil && doc['metrics']['test_build_start'] != nil
218
+ end_time_components = doc['metrics']['test_build_end'].to_s.split(' ')[1].split(':')
219
+ end_date_components = doc['metrics']['test_build_end'].to_s.split(' ')[0].split('-')
220
+ end_time = Time.new(end_date_components[0], end_date_components[1], end_date_components[2], end_time_components[0], end_time_components[1], end_time_components[2])
221
+
222
+ start_time_components = doc['metrics']['test_build_start'].to_s.split(' ')[1].split(':')
223
+ start_date_components = doc['metrics']['test_build_start'].to_s.split(' ')[0].split('-')
224
+ start_time = Time.new(start_date_components[0], start_date_components[1], start_date_components[2], start_time_components[0], start_time_components[1], start_time_components[2])
225
+
226
+ build_time_mins = ((end_time - start_time) / 60).to_f.round(2)
227
+
228
+ keys.push(pull_number)
229
+ data.insert(0, [pull_number, build_time_mins])
230
+ end
231
+ end
232
+
233
+ g = Gruff::Line.new
234
+ g.minimum_value = 0
235
+ g.maximum_value = 10
236
+ g.title = 'Build Times'
237
+ g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
238
+ g.dot_style = :square
239
+ g.font = '/Library/Fonts/Verdana.ttf'
240
+ g.data("minutes (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
241
+ g.x_axis_label = 'Pull Request'
242
+ g.y_axis_label = 'Time (mins)'
243
+ g.theme = {
244
+ :colors => ['#38c0df'],
245
+ :font_color => 'black',
246
+ :background_colors => 'white'
247
+ }
248
+
249
+ g.write(File.join(Config.hud_image_dir, 'build_times.png'))
250
+ end
251
+
252
+ def gen_warnings_chart
253
+ # Script that generates a chart displaying warning count information for past pull requests.
254
+ data = []
255
+ keys = []
256
+ @mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).map do |doc|
257
+ next if doc['start_time'].nil?
258
+ start_date_components = doc['start_time'].to_s.split(' ')[0].split('-')
259
+ date_string = start_date_components[1] + "/" + start_date_components[2]
260
+ pull_number = doc['pull_request'].to_i
261
+ if pull_number == 0 && doc['metrics']['test_build_start'] != nil && !keys.include?(date_string)
262
+ keys.push(date_string)
263
+ data.insert(0, [date_string, doc['metrics']['warning_count']])
264
+ end
265
+ end
266
+
267
+ g = Gruff::Line.new
268
+ g.title = 'Warnings'
269
+ g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
270
+ g.dot_style = :square
271
+ g.font = '/Library/Fonts/Verdana.ttf'
272
+ g.data("# warnings (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
273
+ g.x_axis_label = 'Date'
274
+ g.y_axis_label = 'Number of Warnings'
275
+ g.theme = {
276
+ :colors => ['#c3a00b'],
277
+ :font_color => 'black',
278
+ :background_colors => 'white'
279
+ }
280
+ g.write(File.join(Config.hud_image_dir, 'warning_count.png'))
45
281
  end
46
282
  end
47
283
  end