build-buddy 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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