ruby_script_exporter 0.0.1 → 0.0.2

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: c519baa82ed0393d7b038669bf035a4bc05018a0ecb8da173ead0ad9d2b58d70
4
+ data.tar.gz: '0876858a6b675cbc71f0c52f39584dfe3fd5b21850ec54080694416339780a20'
5
5
  SHA512:
6
- metadata.gz: 818dcf6e0ba66db57d8e3199805393c1fb71fec075db55ec1d86600bc117979f1f18614b8cabb59c808476fae56275b955fb3e833f09843248b2ff29502b84ac
7
- data.tar.gz: 80d871bc0407e38204a52250706fa2c3c821887f1a80734f2515419a823a286306a3e32d337ffcb0ca0ace39c9c512b28d632a8a79a350d5bbc2d237b28a70e2
6
+ metadata.gz: e97359edfa445da493e7cf9a3fe75716aed59ecef67b387b2b9b733f2696d709e030e9b51fa56433ea1acddbb65abb0e3afeaae167ccb943b64b8f6214c598d8
7
+ data.tar.gz: ab958039e3494ef2141bb10da35596368fd63957d92e4ce65013ba1ea9c18f9dd94a44106806404d009ac194bb9362ab0a6a19a577e8afc8789aa0a956b054a1
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ ## 0.0.2
2
+
3
+ * Added specs
4
+ * Added error handling
5
+ * Added execution time reporting
6
+
7
+ ## 0.0.1
8
+
9
+ * 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.2)
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/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.2"
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.2
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,7 +88,9 @@ 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
52
96
  - README.md