mobile_metrics 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66ed534e226cd7151aba4d7c2bae9ca551af7361
4
+ data.tar.gz: d9833bebadf111fab41104b16bfe15ebb321c52f
5
+ SHA512:
6
+ metadata.gz: 130948656a1dcf4546ffe2a2fb85d53ec06952409fe17c3118679903680801fe0f5cb8ad5553f730e1621a507b47022d6654be8b3cf9082a7e11c8b8f2d263b7
7
+ data.tar.gz: b9a3295ba27805917a25cd024e6f51818ce5a330af93497a1503ed4c456344d15d561ba4cedd52f62779a0b03e84e88be2922c1197abe8e04e68db9808feb8ab
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.0
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in mobile_metrics.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mobile_metrics (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (10.5.0)
11
+ rspec (3.7.0)
12
+ rspec-core (~> 3.7.0)
13
+ rspec-expectations (~> 3.7.0)
14
+ rspec-mocks (~> 3.7.0)
15
+ rspec-core (3.7.1)
16
+ rspec-support (~> 3.7.0)
17
+ rspec-expectations (3.7.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.7.0)
20
+ rspec-mocks (3.7.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.7.0)
23
+ rspec-support (3.7.1)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (~> 1.16)
30
+ mobile_metrics!
31
+ rake (~> 10.0)
32
+ rspec (~> 3.0)
33
+
34
+ BUNDLED WITH
35
+ 1.16.1
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "mobile_metrics"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,330 @@
1
+ require "mobile_metrics/version"
2
+
3
+ require 'time'
4
+ require 'json'
5
+ require 'securerandom'
6
+ require 'fileutils'
7
+ require 'pathname'
8
+
9
+ # Metric types
10
+ METRIC_TIMER = 'x.mobile_metric.timer'
11
+ METRIC_COUNTER = 'x.mobile_metric.counter'
12
+ METRIC_RATIO = 'x.mobile_metric.ratio'
13
+ METRIC_SIZE = 'x.mobile_metric.size'
14
+
15
+ # Misc Constants
16
+ UNKNOWN = 'UNKNOWN'
17
+ LOG_PREFIX = 'CI-METRICS:'
18
+ LOGGING_DIR = '/tmp/cimetricslogs'
19
+ UPLOAD_ENV_VAR = 'MOBILE_METRICS_UPLOAD_LOCATION'
20
+
21
+ module MobileMetrics
22
+ class Log
23
+
24
+ @@in_progress_timers = {}
25
+ @@log_file = nil
26
+ @@env_values = nil
27
+ @@verbose = false
28
+ @@upload_location = nil
29
+
30
+ #========================================
31
+ # Configuration
32
+ #=========================================
33
+
34
+ # Named params, with ability to override / leave out stuff we don't care about.
35
+ def self.set_default_values(
36
+ # Required params
37
+ project:,
38
+ # These params either aren't required, or have sensible defaults.
39
+ level: "info",
40
+ platform: "iOS",
41
+ buildUrl: ENV['BUILD_URL'],
42
+ gitUrl: ENV['GIT_URL'],
43
+ gitBranch: ENV['DOTCI_BRANCH'],
44
+ gitSha: ENV['DOTCI_SHA'],
45
+ # no sensible defaults for these, we'll strip them later
46
+ buildType: UNKNOWN,
47
+ brand: UNKNOWN,
48
+ # Controls log levels within this class
49
+ verbose: false,
50
+ # Upload location for logs
51
+ upload_location: nil
52
+ )
53
+
54
+ #TODO: May be able to support overridding these at some point in the future.
55
+ # It won't be threadsafe, but our builds aren't parallelized at the build script level anyway.
56
+ if @@env_values
57
+ print 'Can only override default values once! Aborting!'
58
+ return
59
+ end
60
+
61
+ if project.nil?
62
+ print 'Project value for logging MUST be non-nil, failing build.'
63
+ exit 14
64
+ return
65
+ end
66
+
67
+ @@verbose = verbose
68
+
69
+ # Upload location
70
+ @@upload_location = (upload_location || ENV[UPLOAD_ENV_VAR])
71
+ if @@upload_location.nil?
72
+ print 'Upload location value for logging MUST not be nil, exiting.'
73
+ exit 15
74
+ return
75
+ end
76
+
77
+ # Create the logging dir + filename
78
+ FileUtils.mkdir_p(LOGGING_DIR)
79
+ filename = "metrics_#{project}_#{build_number}.log"
80
+ @@log_file = Pathname.new(LOGGING_DIR) + filename
81
+ print @@log_file if @@verbose
82
+
83
+ # Populate our env values
84
+ @@env_values = {}
85
+
86
+ # Required
87
+ @@env_values[:project] = project
88
+ # Optional
89
+ @@env_values[:level] = level
90
+ @@env_values[:platform] = platform
91
+ @@env_values[:gitUrl] = gitUrl
92
+ @@env_values[:gitBranch] = gitBranch
93
+ @@env_values[:gitSha] = gitSha
94
+ @@env_values[:buildUrl] = buildUrl
95
+
96
+ #TODO: Should any of these be required?
97
+ @@env_values[:buildType] = buildType if buildType != UNKNOWN
98
+ @@env_values[:brand] = brand if brand != UNKNOWN
99
+ end
100
+
101
+ # =begin
102
+ # # Sample timer metric
103
+ # {
104
+ # "time": "<yyyy-MM-ddThh:mm:ss[.sss]Z>",
105
+ # "name": "x.mobile_metric.<metric_type>",
106
+ # "level": "info",
107
+ # "data": {
108
+ # "name": "<metric_name>",
109
+ # … // additional data per metric type
110
+ # }
111
+ # "context": {
112
+ # "platform": "<ios_or_android>",
113
+ # "project": "<project_name>",
114
+ # "gitUrl": "<git_url>",
115
+ # "gitBranch": "<git_branch>",
116
+ # "gitSha": "<git_sha>",
117
+ # "buildType": "<build_type>",
118
+ # "brand": "<brand>",
119
+ # "host": "<machine_dns_name_or_device_name>",
120
+ # "processId": "<process_id_or_build_number>"
121
+ # }
122
+ # }
123
+ # =end
124
+
125
+ #=========================================
126
+ # Public Logging Methods
127
+ #=========================================
128
+
129
+ def self.log_ratio_metric(name:, ratio:)
130
+ overrides = {name: METRIC_RATIO, data: {name: name, ratio: ratio.to_f }}
131
+ metric = merge_hashes(default_metric, overrides).to_json
132
+ append_to_log(metric)
133
+ end
134
+
135
+ def self.log_counter_metric(name:, count:)
136
+ overrides = {name: METRIC_COUNTER, data: {name: name, count: count.to_i }}
137
+ metric = merge_hashes(default_metric, overrides).to_json
138
+ append_to_log(metric)
139
+ end
140
+
141
+ def self.log_size_metric(name:, sizeInBytes:, filename:, artifactUrl:)
142
+ overrides = {name: METRIC_SIZE, data: {name: name, filename: filename, sizeInBytes: sizeInBytes, artifactUrl: artifactUrl }}
143
+ metric = merge_hashes(default_metric, overrides).to_json
144
+ append_to_log(metric)
145
+ end
146
+
147
+ def self.start_timer_metric(name:)
148
+ if @@in_progress_timers.has_key?(name)
149
+ print "WARNING: #{name} already has a running timer, refusing to start a new timer"
150
+ return
151
+ end
152
+ @@in_progress_timers[name] = monotonic_timestamp
153
+ end
154
+
155
+ def self.end_timer_metric(name:)
156
+ if !@@in_progress_timers.has_key?(name)
157
+ print "WARNING: #{name} does not have a running timer, the end_timer_metric call has no effect"
158
+ return
159
+ end
160
+
161
+ # Calculate delta
162
+ start = @@in_progress_timers[name]
163
+ now = monotonic_timestamp
164
+ delta_in_ms = ((now - start) * 1000).to_i
165
+
166
+ # remove existing timer
167
+ @@in_progress_timers.delete(name)
168
+
169
+ # log to file
170
+ overrides = {name: METRIC_TIMER, data: {name: name, duration: delta_in_ms }}
171
+ metric = merge_hashes(default_metric, overrides).to_json
172
+ append_to_log(metric)
173
+ end
174
+
175
+ # Block based timer. This is the recommended way to timer operations.
176
+ def self.time(name:, &block)
177
+ self.start_timer_metric(name: name)
178
+ yield
179
+ self.end_timer_metric(name: name)
180
+ end
181
+
182
+ def self.upload_logs()
183
+ # Already called upload logs before, second time is a no-op
184
+ if @@log_file.nil? || !@@log_file.file?
185
+ print "WARN: Log file is empty or doesn't exist. Was upload_logs called previously?"
186
+ return
187
+ end
188
+
189
+ # Skip uploads for local dev machines
190
+ if !should_upload_logs()
191
+ print 'Detected local machine, refusing to upload build metrics. Removing intermediate log file'
192
+ remove_log_file()
193
+ return
194
+ end
195
+
196
+ # Warn for any open timers
197
+ if @@in_progress_timers.size > 0
198
+ @@in_progress_timers.each { |k,v|
199
+ print "WARN: Timer not closed when upload_logs was called: #{k}"
200
+ }
201
+ end
202
+
203
+ # Upload
204
+ upload_log_file()
205
+
206
+ # Remove log file
207
+ remove_log_file()
208
+ end
209
+
210
+ def self.log_file()
211
+ @@log_file
212
+ end
213
+
214
+ def self.upload_location()
215
+ @@upload_location
216
+ end
217
+
218
+ # Tears down logging instance, removes any intermediate log files.
219
+ # Does not finalize or upload any in-progress logs.
220
+ # To use the logger again, you'll have to call set_default_values before any other methods.
221
+ def self.reset()
222
+ if @@log_file
223
+ remove_log_file()
224
+ end
225
+ @@log_file = nil
226
+ @@env_values = nil
227
+ @@in_progress_timers = {}
228
+ @@verbose = false
229
+ @@upload_location = nil
230
+ end
231
+
232
+ def self.remove_all_log_files()
233
+ FileUtils.rm_rf(LOGGING_DIR)
234
+ end
235
+
236
+ private
237
+
238
+ # Creates a stubbed metric with common fields
239
+ def self.default_metric()
240
+ if !@@env_values
241
+ print 'default_metric called before env_values initialized, aborting!'
242
+ exit 12
243
+ end
244
+
245
+ context = {
246
+ platform: @@env_values[:platform],
247
+ project: @@env_values[:project],
248
+ # name is overridden per metric, so not present here.
249
+ gitUrl: @@env_values[:gitUrl],
250
+ gitBranch: @@env_values[:gitBranch],
251
+ gitSha: @@env_values[:gitSha],
252
+ buildType: @@env_values[:buildType],
253
+ brand: @@env_values[:brand],
254
+ buildUrl: @@env_values[:buildUrl],
255
+ host: hostname(),
256
+ processId: build_number()
257
+ }.select { |k,v| !v.nil? }
258
+ {time: timestamp(), level:@@env_values[:level], context: context}
259
+ end
260
+
261
+ def self.append_to_log(value)
262
+ # Create the log file if it doesn't exist
263
+ if !@@log_file.file?
264
+ FileUtils.touch(@@log_file)
265
+ end
266
+ File.open(@@log_file, 'a+') { |f| f.puts(value + "\n") }
267
+ end
268
+
269
+ def self.remove_log_file()
270
+ FileUtils.rm_rf(@@log_file)
271
+ end
272
+
273
+ def self.upload_log_file()
274
+ destination = @@upload_location + "metrics_#{@@env_values[:project]}_#{build_number}.log"
275
+ command = "scp -v -o StrictHostKeyChecking=no #{@@log_file} #{destination}"
276
+ # This feels icky
277
+ print `#{command}`
278
+ end
279
+
280
+ # Returns a timestamp as a string in the correct logging format.
281
+ # Note: This format doesn't currently add milliseconds.
282
+ # Example: "2018-03-27T21:12:21Z"
283
+ # Note: DO NOT use this for benchmarking / measuring timers, see monotonic_timestamp instead.
284
+ def self.timestamp()
285
+ Time.now.utc.iso8601.to_s
286
+ end
287
+
288
+ # Returns a floating point monotonic timestamp suitable for timers
289
+ # Monotonic means we won't ever go back for things like leap seconds
290
+ # Unit is a floating point in seconds.
291
+ def self.monotonic_timestamp()
292
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
293
+ end
294
+
295
+ # Util method for merging the default hash values and overrides recursively as we want them
296
+ def self.merge_hashes(default, overrides)
297
+ default.merge(overrides) { |key, oldval, newval|
298
+ if key == :data || key == :contenxt
299
+ oldval.merge(newval)
300
+ else
301
+ newval
302
+ end
303
+ }
304
+ end
305
+
306
+ # Returns the host name of the machine we are currently running on
307
+ def self.hostname()
308
+ ENV['NODE_NAME'] || 'local-dev'
309
+ end
310
+
311
+ def self.build_number()
312
+ ENV['BUILD_NUMBER']
313
+ end
314
+
315
+ # This method checks against a bunch of default environment variables available through DOTCI
316
+ # The intent is never to upload timing logs from a local machine, so we make it unlikely that these
317
+ # env variables are present on a dev's local machine
318
+ def self.should_upload_logs()
319
+ have_workspace = ENV['WORKSPACE'] && !ENV['WORKSPACE'].empty?
320
+ have_build_number = ENV['BUILD_NUMBER'] && !ENV['BUILD_NUMBER'].empty?
321
+ have_jenkins_url = ENV['JENKINS_URL'] && !ENV['JENKINS_URL'].empty?
322
+ have_ci_var = ENV['CI'] && !ENV['CI'].empty?
323
+ have_workspace && have_build_number && have_jenkins_url && have_ci_var
324
+ end
325
+
326
+ def self.print(val)
327
+ puts("#{LOG_PREFIX} #{val}")
328
+ end
329
+ end
330
+ end
@@ -0,0 +1,3 @@
1
+ module MobileMetrics
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,25 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "mobile_metrics/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mobile_metrics"
8
+ spec.version = MobileMetrics::VERSION
9
+ spec.authors = ["dbeard"]
10
+ spec.email = ["dbeard@groupon.com"]
11
+
12
+ spec.summary = %q{Mobile logging format library}
13
+ # spec.homepage = "TODO: Put your gem's website or public repo URL here."
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features|README|scripts)/}) || f.match(%r{^(.ci|README.md)})
17
+ end
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.16"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec", "~> 3.0"
25
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mobile_metrics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - dbeard
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-06-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description:
56
+ email:
57
+ - dbeard@groupon.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - Rakefile
68
+ - bin/console
69
+ - bin/setup
70
+ - lib/mobile_metrics.rb
71
+ - lib/mobile_metrics/version.rb
72
+ - mobile_metrics.gemspec
73
+ homepage:
74
+ licenses: []
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.6.14
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Mobile logging format library
96
+ test_files: []