ruby_script_exporter 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
+ SHA256:
3
+ metadata.gz: 2a7c4a79deaa97a719986d2ca70f60a41e64d90c876758ed70f79ccc3689f7dc
4
+ data.tar.gz: 0cb360305395f727f7061ccc1b2a00ee026429d757210816605f9a1a60e5bd06
5
+ SHA512:
6
+ metadata.gz: 818dcf6e0ba66db57d8e3199805393c1fb71fec075db55ec1d86600bc117979f1f18614b8cabb59c808476fae56275b955fb3e833f09843248b2ff29502b84ac
7
+ data.tar.gz: 80d871bc0407e38204a52250706fa2c3c821887f1a80734f2515419a823a286306a3e32d337ffcb0ca0ace39c9c512b28d632a8a79a350d5bbc2d237b28a70e2
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.2.2
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
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'
data/Gemfile.lock ADDED
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruby_script_exporter (0.0.1)
5
+ rackup (~> 2.1.0)
6
+ sinatra (~> 4.0.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ base64 (0.2.0)
12
+ mustermann (3.0.0)
13
+ ruby2_keywords (~> 0.0.1)
14
+ rack (3.0.8)
15
+ rack-protection (4.0.0)
16
+ base64 (>= 0.1.0)
17
+ rack (>= 3.0.0, < 4)
18
+ rack-session (2.0.0)
19
+ rack (>= 3.0.0)
20
+ rackup (2.1.0)
21
+ rack (>= 3)
22
+ webrick (~> 1.8)
23
+ rake (13.0.6)
24
+ ruby2_keywords (0.0.5)
25
+ sinatra (4.0.0)
26
+ mustermann (~> 3.0)
27
+ rack (>= 3.0.0, < 4)
28
+ rack-protection (= 4.0.0)
29
+ rack-session (>= 2.0.0, < 3)
30
+ tilt (~> 2.0)
31
+ tilt (2.3.0)
32
+ webrick (1.8.1)
33
+
34
+ PLATFORMS
35
+ x86_64-linux
36
+
37
+ DEPENDENCIES
38
+ rackup
39
+ rake (~> 13.0)
40
+ ruby_script_exporter!
41
+ sinatra
42
+
43
+ BUNDLED WITH
44
+ 2.4.10
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # RubyScriptExporter
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.
6
+
7
+ ## Installation
8
+
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
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Development
24
+
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.
26
+
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).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ruby_script_exporter.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,19 @@
1
+ type :time, :counter, 'Seconds since 1970'
2
+
3
+ service 'dummy service' do
4
+ label :service_label, '1234'
5
+
6
+ probe 'dummy probe' do
7
+ run do
8
+ observe :time, Time.now
9
+ end
10
+ end
11
+
12
+ probe 'cached dummy probe' do
13
+ cache_for 30
14
+
15
+ run do
16
+ observe :time, Time.now
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require 'optparse'
6
+ require "ruby_script_exporter"
7
+
8
+ options = {
9
+ service_dir: 'example_services/',
10
+ reload_on_request: false,
11
+ port: 9100,
12
+ host: '0.0.0.0',
13
+ }
14
+
15
+ OptionParser.new do |opts|
16
+ opts.banner = "Usage: ruby_script_exporter [options]"
17
+
18
+ opts.on("-s SERVICE_DIR", "--script-directory SERVICE_DIR", "Specify where to look for service definitions") do |service_dir|
19
+ options[:service_dir] = service_dir
20
+ end
21
+
22
+ opts.on("-r", "--reload-on-request", "Reload service definitions for every request, useful for developing probes") do
23
+ options[:reload_on_request] = true
24
+ end
25
+ end.parse!
26
+
27
+ RubyScriptExporter::Server.service_directory = options[:service_dir]
28
+ RubyScriptExporter::Server.reload_on_request = options[:reload_on_request]
29
+ RubyScriptExporter::Server.set :port, options[:port]
30
+ RubyScriptExporter::Server.set :bind , options[:host]
31
+ RubyScriptExporter::Server.run!
@@ -0,0 +1,17 @@
1
+ module RubyScriptExporter
2
+ class Executor
3
+
4
+ def initialize(services)
5
+ @services = services
6
+ end
7
+
8
+ def probes
9
+ @services.map(&:probes).flatten
10
+ end
11
+
12
+ def run
13
+ probes.map(&:run).flatten
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module RubyScriptExporter
2
+ class Formatter
3
+
4
+ def initialize(measurements)
5
+ @measurements = measurements
6
+ end
7
+
8
+ def format
9
+ output = []
10
+
11
+ @measurements.group_by(&:measurement).map do |type, measurements|
12
+ type = Type.from_name(type)
13
+ output << type.format_for_open_metrics
14
+
15
+ measurements.each do |measurement|
16
+ output << measurement.format_as_open_metric
17
+ end
18
+ end
19
+
20
+ output.join("\n")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ module RubyScriptExporter
2
+ class Measurement
3
+
4
+ attr_reader :measurement
5
+ attr_reader :value
6
+
7
+ def initialize(measurement, value, timestamp:, probe:, **labels)
8
+ @measurement = measurement
9
+ @value = value
10
+ @probe = probe
11
+ @labels = labels
12
+ @timestamp = timestamp
13
+ end
14
+
15
+ def to_s
16
+ "<Measurement #{@measurement} #{@value} #{@labels.inspect}>"
17
+ end
18
+
19
+ def combined_labels
20
+ @probe.combined_labels.merge(@labels)
21
+ end
22
+
23
+ def format_as_open_metric
24
+ line = @measurement.to_s
25
+
26
+ if combined_labels.any?
27
+ line << '{'
28
+ line << combined_labels.map do |key, value|
29
+ "#{key}=\"#{value}\""
30
+ end.join(',')
31
+ line << '}'
32
+ end
33
+
34
+ line << ' '
35
+ line << @value.to_s
36
+
37
+ if @probe.caches_result?
38
+ line << ' '
39
+ line << @timestamp.to_s
40
+ end
41
+
42
+ line
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyScriptExporter
4
+ class Probe
5
+
6
+ attr_reader :name
7
+ attr_reader :labels
8
+ attr_accessor :cache_for
9
+ attr_writer :runner_proc
10
+
11
+ def initialize(name, service)
12
+ @name = name
13
+ @service = service
14
+ @labels = {}
15
+ @last_measurements = nil
16
+ @last_run_at = nil
17
+ end
18
+
19
+ def combined_labels
20
+ @service.combined_labels.merge({
21
+ probe: @name,
22
+ }).merge(@labels).compact
23
+ end
24
+
25
+ def caches_result?
26
+ !!cache_for
27
+ end
28
+
29
+ def run
30
+ if caches_result? && @last_measurements && @last_run_at > (Time.now.to_f - @cache_for)
31
+ return @last_measurements
32
+ end
33
+
34
+ raise ArgumentError, 'No runner given' unless @runner_proc
35
+
36
+ runner = Runner.new(self)
37
+ runner.instance_eval(&@runner_proc)
38
+ @last_run_at = Time.now.to_f
39
+ @last_measurements = runner.measurements
40
+ end
41
+ end
42
+
43
+ class Probe::Builder
44
+ attr_accessor :probe
45
+
46
+ def initialize(name, service)
47
+ @probe = Probe.new(name, service)
48
+ end
49
+
50
+ def cache_for(time)
51
+ @probe.cache_for = time
52
+ end
53
+
54
+ def label(key, value)
55
+ @probe.labels[key] = value
56
+ end
57
+
58
+ def run(&block)
59
+ @probe.runner_proc = block
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,17 @@
1
+ module RubyScriptExporter
2
+ class Runner
3
+
4
+ attr_reader :measurements
5
+
6
+ def initialize(probe)
7
+ @probe = probe
8
+ @measurements = []
9
+ end
10
+
11
+ def observe(measurement, value, **labels)
12
+ timestamp = Time.now.to_i * 1000
13
+ @measurements << Measurement.new(measurement, value, timestamp:, probe: @probe, **labels)
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,45 @@
1
+ module RubyScriptExporter
2
+ class ScriptLoader
3
+
4
+ attr_reader :services
5
+
6
+ def initialize
7
+ @services = []
8
+ end
9
+
10
+ def service(name, &block)
11
+ service_builder = RubyScriptExporter::Service::Builder.new(name)
12
+ service_builder.instance_eval(&block)
13
+ @services << service_builder.service
14
+ end
15
+
16
+ def type(name, type, help = nil)
17
+ Type.register_type(name, type, help)
18
+ end
19
+
20
+ def self.load_file(file)
21
+ loader = ScriptLoader.new
22
+ code = File.open(file).read
23
+ loader.instance_eval code
24
+ loader.services
25
+ end
26
+
27
+ def self.load_directory(directory)
28
+ unless directory.start_with?('/')
29
+ directory = File.join(Dir.pwd, directory)
30
+ end
31
+ directory = File.join(directory, '/*.rb')
32
+
33
+ service_files = Dir[directory]
34
+
35
+ puts "Loading service definitions ..."
36
+ Type.clear_types
37
+ services = service_files.map { load_file(_1) }.flatten
38
+ probe_count = services.map(&:probes).flatten.count
39
+ puts "Loaded #{Util.counterize('service', services.count)} with a total of #{Util.counterize('probe', probe_count)}"
40
+
41
+ services
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,28 @@
1
+ require 'sinatra'
2
+
3
+ module RubyScriptExporter
4
+ class Server < Sinatra::Base
5
+
6
+ def self.service_directory=(service_directory)
7
+ @service_directory = service_directory
8
+ end
9
+
10
+ def self.reload_on_request=(reload_on_request)
11
+ @reload_on_request = reload_on_request
12
+ end
13
+
14
+ def self.services
15
+ return @services if @services && !@reload_on_request
16
+
17
+ @services = RubyScriptExporter::ScriptLoader.load_directory(@service_directory)
18
+ end
19
+
20
+ set :default_content_type, 'text'
21
+
22
+ get '/metrics' do
23
+ measurements = Executor.new(self.class.services).run
24
+ Formatter.new(measurements).format
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyScriptExporter
4
+ class Service
5
+
6
+ attr_reader :name
7
+ attr_reader :probes
8
+ attr_reader :labels
9
+
10
+ def initialize(name)
11
+ @name = name
12
+ @probes = []
13
+ @labels = {}
14
+ end
15
+
16
+ def combined_labels
17
+ {
18
+ service: @name,
19
+ }.merge(@labels).compact
20
+ end
21
+
22
+ end
23
+
24
+ class Service::Builder
25
+ attr_reader :service
26
+
27
+ def initialize(name)
28
+ @service = Service.new(name)
29
+ end
30
+
31
+ def label(key, value)
32
+ @service.labels[key] = value
33
+ end
34
+
35
+ def probe(name, &block)
36
+ probe_builder = Probe::Builder.new(name, @service)
37
+ probe_builder.instance_eval(&block)
38
+ @service.probes << probe_builder.probe
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ module RubyScriptExporter
2
+ class Type
3
+
4
+ @types = {}
5
+
6
+ def initialize(name, type, help = nil)
7
+ @name = name
8
+ @type = type
9
+ @help = help
10
+ end
11
+
12
+ def self.register_type(name, type, help = nil)
13
+ raise ArgumentError, "Type for measurement '#{name}' already registered." if @types.key?(name)
14
+
15
+ @types[name.to_sym] = Type.new(name, type, help)
16
+ end
17
+
18
+ def self.from_name(name)
19
+ type = @types[name.to_sym]
20
+
21
+ unless type
22
+ puts "Warning type for measurement #{name} not found, defaulting to gauge."
23
+ type = register_type(name, :gauge)
24
+ end
25
+
26
+ type
27
+ end
28
+
29
+ def self.clear_types
30
+ @types = {}
31
+ end
32
+
33
+ def format_for_open_metrics
34
+ type_description = ''
35
+ type_description << "# HELP #{@name} #{@help}\n" if @help
36
+ type_description << "# TYPE #{@name} #{@type}"
37
+ type_description
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,11 @@
1
+ module RubyScriptExporter
2
+ module Util
3
+
4
+ def self.counterize(string, count)
5
+ string = string.prepend('s') if count != 1
6
+
7
+ "#{count} #{string}"
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module RubyScriptExporter
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ruby_script_exporter/version"
4
+ require_relative "ruby_script_exporter/probe"
5
+ require_relative "ruby_script_exporter/service"
6
+ require_relative "ruby_script_exporter/server"
7
+ require_relative "ruby_script_exporter/executor"
8
+ require_relative "ruby_script_exporter/runner"
9
+ require_relative "ruby_script_exporter/measurement"
10
+ require_relative "ruby_script_exporter/formatter"
11
+ require_relative "ruby_script_exporter/script_loader"
12
+ require_relative "ruby_script_exporter/util"
13
+ require_relative "ruby_script_exporter/type"
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_script_exporter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Martin Schaflitzl
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-01-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rackup
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.1.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.1.0
41
+ description:
42
+ email:
43
+ - gems@martin-sc.de
44
+ executables:
45
+ - ruby_script_exporter
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".tool-versions"
50
+ - Gemfile
51
+ - Gemfile.lock
52
+ - README.md
53
+ - Rakefile
54
+ - example_services/dummy.rb
55
+ - exe/ruby_script_exporter
56
+ - lib/ruby_script_exporter.rb
57
+ - lib/ruby_script_exporter/executor.rb
58
+ - lib/ruby_script_exporter/formatter.rb
59
+ - lib/ruby_script_exporter/measurement.rb
60
+ - lib/ruby_script_exporter/probe.rb
61
+ - lib/ruby_script_exporter/runner.rb
62
+ - lib/ruby_script_exporter/script_loader.rb
63
+ - lib/ruby_script_exporter/server.rb
64
+ - lib/ruby_script_exporter/service.rb
65
+ - lib/ruby_script_exporter/type.rb
66
+ - lib/ruby_script_exporter/util.rb
67
+ - lib/ruby_script_exporter/version.rb
68
+ homepage: https://rubygems.org/gems/ruby_script_exporter
69
+ licenses: []
70
+ metadata:
71
+ homepage_uri: https://rubygems.org/gems/ruby_script_exporter
72
+ source_code_uri: https://github.com/mschaf/ruby_script_exporter
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 2.6.0
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 3.4.10
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Export metrics to prometheus from ruby snippets.
92
+ test_files: []