ruby_script_exporter 0.0.1 → 0.0.3

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
  SHA256:
3
- metadata.gz: 2a7c4a79deaa97a719986d2ca70f60a41e64d90c876758ed70f79ccc3689f7dc
4
- data.tar.gz: 0cb360305395f727f7061ccc1b2a00ee026429d757210816605f9a1a60e5bd06
3
+ metadata.gz: 88837a91632cfcfb2f16988f4b173309b21fc9b9be3a30293fe19fbd4ea73943
4
+ data.tar.gz: f727e150aceb929705fe4e5d94cf791bdd0c31b68e6fbcfb98a48434dd81db6a
5
5
  SHA512:
6
- metadata.gz: 818dcf6e0ba66db57d8e3199805393c1fb71fec075db55ec1d86600bc117979f1f18614b8cabb59c808476fae56275b955fb3e833f09843248b2ff29502b84ac
7
- data.tar.gz: 80d871bc0407e38204a52250706fa2c3c821887f1a80734f2515419a823a286306a3e32d337ffcb0ca0ace39c9c512b28d632a8a79a350d5bbc2d237b28a70e2
6
+ metadata.gz: 3952f1924fd00357a6d35ed78ed974a8d4b26da6ec1bde52e59bfc419e12217f18e754bcf3a9dbac2ba177211586540c356273e7d3fa2f203e98d6ec831eae57
7
+ data.tar.gz: bed7fb348412b2011e62fc0846978a8627b1654ee2c8451d9bd85776a1f03edf607b55e59bfda50d5fcf181ee4f3448849b47bac6880b72d5d74ee1931514139
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ ## 0.0.3
2
+
3
+ * Add MIT license
4
+
5
+ ## 0.0.2
6
+
7
+ * Added specs
8
+ * Added error handling
9
+ * Added execution time reporting
10
+
11
+ ## 0.0.1
12
+
13
+ * Initial release with the most basic functionality
data/Gemfile CHANGED
@@ -3,8 +3,4 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  # Specify your gem's dependencies in ruby_script_exporter.gemspec
6
- gemspec
7
-
8
- gem "rake", "~> 13.0"
9
- gem 'sinatra'
10
- gem 'rackup'
6
+ gemspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby_script_exporter (0.0.1)
4
+ ruby_script_exporter (0.0.3)
5
5
  rackup (~> 2.1.0)
6
6
  sinatra (~> 4.0.0)
7
7
 
@@ -9,6 +9,8 @@ GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
11
  base64 (0.2.0)
12
+ diff-lcs (1.5.0)
13
+ mqtt (0.6.0)
12
14
  mustermann (3.0.0)
13
15
  ruby2_keywords (~> 0.0.1)
14
16
  rack (3.0.8)
@@ -21,6 +23,19 @@ GEM
21
23
  rack (>= 3)
22
24
  webrick (~> 1.8)
23
25
  rake (13.0.6)
26
+ rspec (3.12.0)
27
+ rspec-core (~> 3.12.0)
28
+ rspec-expectations (~> 3.12.0)
29
+ rspec-mocks (~> 3.12.0)
30
+ rspec-core (3.12.2)
31
+ rspec-support (~> 3.12.0)
32
+ rspec-expectations (3.12.3)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.12.0)
35
+ rspec-mocks (3.12.6)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.12.0)
38
+ rspec-support (3.12.1)
24
39
  ruby2_keywords (0.0.5)
25
40
  sinatra (4.0.0)
26
41
  mustermann (~> 3.0)
@@ -29,16 +44,18 @@ GEM
29
44
  rack-session (>= 2.0.0, < 3)
30
45
  tilt (~> 2.0)
31
46
  tilt (2.3.0)
47
+ timecop (0.9.8)
32
48
  webrick (1.8.1)
33
49
 
34
50
  PLATFORMS
35
51
  x86_64-linux
36
52
 
37
53
  DEPENDENCIES
38
- rackup
54
+ mqtt
39
55
  rake (~> 13.0)
56
+ rspec (~> 3.6)
40
57
  ruby_script_exporter!
41
- sinatra
58
+ timecop (~> 0.9.8)
42
59
 
43
60
  BUNDLED WITH
44
61
  2.4.10
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2024 Martin Schaflitzl
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,31 +1,64 @@
1
- # RubyScriptExporter
1
+ # ruby_script_exporter
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
4
-
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ruby_script_exporter`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ruby_script_exporter is a small framework to expose metrics produced by ruby snippets to prometheus.
6
4
 
7
5
  ## Installation
8
6
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
-
11
- Install the gem and add to the application's Gemfile by executing:
12
-
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
14
-
15
- If bundler is not being used to manage dependencies, install the gem by executing:
16
-
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
7
+ `gem install ruby_script_exporter`
18
8
 
19
9
  ## Usage
20
10
 
21
- TODO: Write usage instructions here
11
+ ```
12
+ Usage: ruby_script_exporter [options]
13
+ -s SERVICE_DIR, --script-directory Specify where to look for service definitions
14
+ -r, --reload-on-request Reload service definitions for every request, useful for developing probes
15
+ ```
16
+
17
+ ## Example Probe
22
18
 
23
- ## Development
19
+ `services/example_probe.rb`:
24
20
 
25
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
21
+ ```
22
+ type :some_metric, :gauge, 'Some random metric'
26
23
 
27
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
24
+ service 'some service' do
25
+ probe 'some probe' do
26
+ label :some_label, 'Foo'
27
+
28
+ run do
29
+ observe :some_metric, 123
30
+ end
31
+ end
32
+ end
33
+ ```
34
+
35
+ Run the exporter with `ruby_script_exporter` and get `http://localhost:9100` for:
28
36
 
29
- ## Contributing
37
+ ```
38
+ # HELP cached_probe_count Count of probes which returned a cached result
39
+ # TYPE cached_probe_count gauge
40
+ cached_probe_count 0
41
+ # HELP error_probe_count Count probes witch threw an error while executing
42
+ # TYPE error_probe_count gauge
43
+ error_probe_count 0
44
+ # HELP probe_execution_time Execution time per probe
45
+ # TYPE probe_execution_time gauge
46
+ probe_execution_time{service="some service",probe="some probe",some_label="Foo"} 7.915019523352385e-06
47
+ # HELP some_metric Some random metric
48
+ # TYPE some_metric gauge
49
+ some_metric{service="some service",probe="some probe",some_label="Foo"} 123
50
+ # HELP successful_probe_count Count of probes which ran successfully
51
+ # TYPE successful_probe_count gauge
52
+ successful_probe_count 1
53
+ # HELP timeout_probe_count Count of probes which timed out
54
+ # TYPE timeout_probe_count gauge
55
+ timeout_probe_count 0
56
+ # HELP total_execution_time Total execution time
57
+ # TYPE total_execution_time gauge
58
+ total_execution_time 6.38000201433897e-05
59
+ # HELP total_probe_count Total probe count
60
+ # TYPE total_probe_count gauge
61
+ total_probe_count 1
62
+ ```
30
63
 
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ruby_script_exporter.
64
+ Next to `some_metric` there are also a number of internal metrics exposed.
@@ -1,8 +1,10 @@
1
1
  module RubyScriptExporter
2
2
  class Executor
3
3
 
4
- def initialize(services)
4
+ def initialize(services, report_execution_time: false, report_counts: false)
5
5
  @services = services
6
+ @report_execution_time = report_execution_time
7
+ @report_counts = report_counts
6
8
  end
7
9
 
8
10
  def probes
@@ -10,7 +12,59 @@ module RubyScriptExporter
10
12
  end
11
13
 
12
14
  def run
13
- probes.map(&:run).flatten
15
+ total_count = 0
16
+ successful_count = 0
17
+ cached_count = 0
18
+ error_count = 0
19
+ timeout_count = 0
20
+ measurements = []
21
+
22
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
23
+
24
+ probes.each do |probe|
25
+ total_count += 1
26
+
27
+ begin
28
+ probe_measurements, execution_time = probe.run
29
+ rescue Probe::ScriptError
30
+ error_count += 1
31
+ next
32
+ rescue Probe::ScriptTimeout
33
+ timeout_count += 1
34
+ next
35
+ end
36
+
37
+ measurements.concat(probe_measurements)
38
+
39
+ successful_count += 1
40
+ if execution_time == :cached
41
+ execution_time = 0
42
+ cached_count += 1
43
+ end
44
+
45
+ if @report_execution_time
46
+ measurements << Measurement.new(:probe_execution_time, execution_time,
47
+ probe: probe,
48
+ )
49
+ end
50
+ end
51
+
52
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
53
+ total_execution_time = end_time - start_time
54
+
55
+ if @report_execution_time
56
+ measurements << Measurement.new(:total_execution_time, total_execution_time)
57
+ end
58
+
59
+ if @report_counts
60
+ measurements << Measurement.new(:total_probe_count, total_count)
61
+ measurements << Measurement.new(:successful_probe_count, successful_count)
62
+ measurements << Measurement.new(:cached_probe_count, cached_count)
63
+ measurements << Measurement.new(:error_probe_count, error_count)
64
+ measurements << Measurement.new(:timeout_probe_count, timeout_count)
65
+ end
66
+
67
+ measurements
14
68
  end
15
69
 
16
70
  end
@@ -8,14 +8,17 @@ module RubyScriptExporter
8
8
  def format
9
9
  output = []
10
10
 
11
- @measurements.group_by(&:measurement).map do |type, measurements|
12
- type = Type.from_name(type)
13
- output << type.format_for_open_metrics
11
+ @measurements
12
+ .group_by(&:measurement)
13
+ .sort_by { |key, _| key }
14
+ .map do |type, measurements|
15
+ type = Type.from_name(type)
16
+ output << type.format_for_open_metrics
14
17
 
15
- measurements.each do |measurement|
16
- output << measurement.format_as_open_metric
18
+ measurements.each do |measurement|
19
+ output << measurement.format_as_open_metric
20
+ end
17
21
  end
18
- end
19
22
 
20
23
  output.join("\n")
21
24
  end
@@ -4,7 +4,7 @@ module RubyScriptExporter
4
4
  attr_reader :measurement
5
5
  attr_reader :value
6
6
 
7
- def initialize(measurement, value, timestamp:, probe:, **labels)
7
+ def initialize(measurement, value, timestamp: nil, probe: nil, **labels)
8
8
  @measurement = measurement
9
9
  @value = value
10
10
  @probe = probe
@@ -17,6 +17,8 @@ module RubyScriptExporter
17
17
  end
18
18
 
19
19
  def combined_labels
20
+ return @labels unless @probe
21
+
20
22
  @probe.combined_labels.merge(@labels)
21
23
  end
22
24
 
@@ -34,7 +36,7 @@ module RubyScriptExporter
34
36
  line << ' '
35
37
  line << @value.to_s
36
38
 
37
- if @probe.caches_result?
39
+ if @probe&.caches_result?
38
40
  line << ' '
39
41
  line << @timestamp.to_s
40
42
  end
@@ -3,10 +3,15 @@
3
3
  module RubyScriptExporter
4
4
  class Probe
5
5
 
6
+ class ScriptError < StandardError; end
7
+ class ScriptTimeout < StandardError; end
8
+
6
9
  attr_reader :name
7
10
  attr_reader :labels
8
11
  attr_accessor :cache_for
12
+ attr_accessor :timeout
9
13
  attr_writer :runner_proc
14
+ attr_reader :service
10
15
 
11
16
  def initialize(name, service)
12
17
  @name = name
@@ -14,6 +19,7 @@ module RubyScriptExporter
14
19
  @labels = {}
15
20
  @last_measurements = nil
16
21
  @last_run_at = nil
22
+ @timeout = 1
17
23
  end
18
24
 
19
25
  def combined_labels
@@ -28,15 +34,32 @@ module RubyScriptExporter
28
34
 
29
35
  def run
30
36
  if caches_result? && @last_measurements && @last_run_at > (Time.now.to_f - @cache_for)
31
- return @last_measurements
37
+ return [@last_measurements, :cached]
32
38
  end
33
39
 
34
40
  raise ArgumentError, 'No runner given' unless @runner_proc
35
41
 
36
42
  runner = Runner.new(self)
37
- runner.instance_eval(&@runner_proc)
43
+
44
+ start_time = nil
45
+ end_time = nil
46
+ begin
47
+ Timeout::timeout(@timeout) do
48
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
49
+ runner.instance_eval(&@runner_proc)
50
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
51
+ end
52
+ rescue Timeout::Error
53
+ raise ScriptTimeout
54
+ rescue StandardError
55
+ raise ScriptError
56
+ end
57
+
58
+ execution_time = end_time - start_time
59
+
38
60
  @last_run_at = Time.now.to_f
39
61
  @last_measurements = runner.measurements
62
+ [@last_measurements, execution_time]
40
63
  end
41
64
  end
42
65
 
@@ -51,6 +74,10 @@ module RubyScriptExporter
51
74
  @probe.cache_for = time
52
75
  end
53
76
 
77
+ def timeout(timeout)
78
+ @probe.timeout = timeout
79
+ end
80
+
54
81
  def label(key, value)
55
82
  @probe.labels[key] = value
56
83
  end
@@ -17,13 +17,17 @@ module RubyScriptExporter
17
17
  Type.register_type(name, type, help)
18
18
  end
19
19
 
20
- def self.load_file(file)
20
+ def self.load_string(string)
21
21
  loader = ScriptLoader.new
22
- code = File.open(file).read
23
- loader.instance_eval code
22
+ Type.reset_types
23
+ loader.instance_eval string
24
24
  loader.services
25
25
  end
26
26
 
27
+ def self.load_file(file)
28
+ load_string File.open(file).read
29
+ end
30
+
27
31
  def self.load_directory(directory)
28
32
  unless directory.start_with?('/')
29
33
  directory = File.join(Dir.pwd, directory)
@@ -33,7 +37,6 @@ module RubyScriptExporter
33
37
  service_files = Dir[directory]
34
38
 
35
39
  puts "Loading service definitions ..."
36
- Type.clear_types
37
40
  services = service_files.map { load_file(_1) }.flatten
38
41
  probe_count = services.map(&:probes).flatten.count
39
42
  puts "Loaded #{Util.counterize('service', services.count)} with a total of #{Util.counterize('probe', probe_count)}"
@@ -20,7 +20,7 @@ module RubyScriptExporter
20
20
  set :default_content_type, 'text'
21
21
 
22
22
  get '/metrics' do
23
- measurements = Executor.new(self.class.services).run
23
+ measurements = Executor.new(self.class.services, report_execution_time: true, report_counts: true).run
24
24
  Formatter.new(measurements).format
25
25
  end
26
26
  end
@@ -26,8 +26,15 @@ module RubyScriptExporter
26
26
  type
27
27
  end
28
28
 
29
- def self.clear_types
29
+ def self.reset_types
30
30
  @types = {}
31
+ register_type(:cached_probe_count, :gauge, 'Count of probes which returned a cached result')
32
+ register_type(:error_probe_count, :gauge, 'Count probes witch threw an error while executing')
33
+ register_type(:successful_probe_count, :gauge, 'Count of probes which ran successfully')
34
+ register_type(:timeout_probe_count, :gauge, 'Count of probes which timed out')
35
+ register_type(:total_probe_count, :gauge, 'Total probe count')
36
+ register_type(:probe_execution_time, :gauge, 'Execution time per probe')
37
+ register_type(:total_execution_time, :gauge, 'Total execution time')
31
38
  end
32
39
 
33
40
  def format_for_open_metrics
@@ -1,3 +1,3 @@
1
1
  module RubyScriptExporter
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_script_exporter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Schaflitzl
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-28 00:00:00.000000000 Z
11
+ date: 2024-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -38,6 +38,48 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 2.1.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.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.9.8
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.9.8
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
41
83
  description:
42
84
  email:
43
85
  - gems@martin-sc.de
@@ -46,9 +88,12 @@ executables:
46
88
  extensions: []
47
89
  extra_rdoc_files: []
48
90
  files:
91
+ - ".rspec"
49
92
  - ".tool-versions"
93
+ - CHANGELOG.md
50
94
  - Gemfile
51
95
  - Gemfile.lock
96
+ - LICENSE
52
97
  - README.md
53
98
  - Rakefile
54
99
  - example_services/dummy.rb
@@ -66,10 +111,12 @@ files:
66
111
  - lib/ruby_script_exporter/util.rb
67
112
  - lib/ruby_script_exporter/version.rb
68
113
  homepage: https://rubygems.org/gems/ruby_script_exporter
69
- licenses: []
114
+ licenses:
115
+ - MIT
70
116
  metadata:
71
117
  homepage_uri: https://rubygems.org/gems/ruby_script_exporter
72
118
  source_code_uri: https://github.com/mschaf/ruby_script_exporter
119
+ changelog_uri: https://github.com/mschaf/ruby_script_exporter/blob/main/CHANGELOG.md
73
120
  post_install_message:
74
121
  rdoc_options: []
75
122
  require_paths: