type_fusion 0.0.2 → 0.0.4

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: ce0738a57fd5c0b08679b9ea5fd5fe16ddd786326a8269aa09905acb9b7bb7ac
4
- data.tar.gz: c55387825018723f436f302d8ec6f842a9e9a635ca0c779a9a498d32a906ed91
3
+ metadata.gz: 7eaa132f18093b5b57b9db4e49130adb2f9ff41ae03559848e1334ee4f863bfe
4
+ data.tar.gz: 4bc9dcc31808f3f39707516436f28a3e73de19054f80c2d143da66dc365ae106
5
5
  SHA512:
6
- metadata.gz: 9ba2541d870cd306acf5a5c8a06367117aff283a28502940424d2931641f9405c85a053684ff73e74a163cce14d15409706f00cf69de7d0c22f8caaf30586cd4
7
- data.tar.gz: 4b929bacae1bc294fbdde9df386aa4833329252f46a438c8daae53049fb9687fca7296fa0ca3f4e4db9b576bc00241d249c62cf5b500e04561a9dc1ebb50590e
6
+ metadata.gz: '086356eb24932d3fbce347967869de8ba31672ebde52eaed096a640713ca8cb69e151ebe2a6dd71ebdb596a07cd6ea971bcf7f0147887059a344901d08e2be90'
7
+ data.tar.gz: 35dbb641e2ffc414aa0c42af6f2a61538f3a24f37e5bd35daf623bc7824b853fe8963711c10fd741e2e6155be6b73cf26fd7718f530b36e238c912f2ce372f7b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.4] - 2023-08-16
4
+
5
+ - Implement sample collection by sending samples to gem.sh endpoint
6
+ - Introduce Litejob to enqueue samples to be collected async
7
+ - Updates to `SampleCall`
8
+ - Split up `gem_and_version` to `gem` and `version` in `SampleCall`
9
+ - Also collect `application_name` and `type_fusion_version` with `SampleCall`
10
+ - Change `Tracepoint` API from `:call` to `:return` to also collect `return_value` in `SampleCall`
11
+ - Introduce `endpoint` and `application_name` configs
12
+
13
+ ## [0.0.3] - 2023-08-13
14
+
15
+ - Introduce `TypeFusion::Middleware` to allow type-sampling in Rack-powered apps
16
+ - Introduce `TypeFusion::Railtie` to automatically setup type-sampling in Rails apps
17
+ - Introduce `TypeFusion::Config` to control sampling
18
+ - Add `config.type_sample_call_rate` config
19
+ - Add `config.type_sample_request` config
20
+ - Add `config.type_sample_tracepoint_path` config
21
+
3
22
  ## [0.0.2] - 2023-08-12
4
23
 
5
24
  - Initial Proof-of-Concept release
data/Gemfile CHANGED
@@ -6,4 +6,4 @@ gemspec
6
6
 
7
7
  gem "minitest", "~> 5.0"
8
8
  gem "rake", "~> 13.0"
9
- gem "rubocop", "~> 1.21"
9
+ gem "rubocop", "~> 1.56"
data/Gemfile.lock CHANGED
@@ -1,36 +1,80 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- type_fusion (0.0.1)
4
+ type_fusion (0.0.4)
5
+ lhc (~> 15.2)
6
+ litejob (~> 0.2.3)
5
7
 
6
8
  GEM
7
9
  remote: https://rubygems.org/
8
10
  specs:
11
+ activesupport (7.0.7)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
16
+ addressable (2.8.5)
17
+ public_suffix (>= 2.0.2, < 6.0)
9
18
  ast (2.4.2)
19
+ base64 (0.1.1)
20
+ concurrent-ruby (1.2.2)
21
+ ethon (0.16.0)
22
+ ffi (>= 1.15.0)
23
+ ffi (1.15.5)
24
+ i18n (1.14.1)
25
+ concurrent-ruby (~> 1.0)
10
26
  json (2.6.3)
27
+ language_server-protocol (3.17.0.3)
28
+ lhc (15.2.1)
29
+ activesupport (>= 5.2)
30
+ addressable
31
+ local_uri
32
+ typhoeus (>= 0.11)
33
+ litedb (0.2.1)
34
+ litescheduler (>= 0.2.0)
35
+ sqlite3 (>= 1.5.0)
36
+ litejob (0.2.3)
37
+ litequeue (>= 0.2.1)
38
+ litescheduler (>= 0.2.1)
39
+ litequeue (0.2.1)
40
+ litedb (>= 0.2.1)
41
+ litescheduler (0.2.1)
42
+ local_uri (1.2.0)
43
+ activesupport
44
+ rack
11
45
  minitest (5.19.0)
12
46
  parallel (1.23.0)
13
47
  parser (3.2.2.3)
14
48
  ast (~> 2.4.1)
15
49
  racc
50
+ public_suffix (5.0.3)
16
51
  racc (1.7.1)
52
+ rack (3.0.8)
17
53
  rainbow (3.1.1)
18
54
  rake (13.0.6)
19
55
  regexp_parser (2.8.1)
20
56
  rexml (3.2.6)
21
- rubocop (1.48.1)
57
+ rubocop (1.56.0)
58
+ base64 (~> 0.1.1)
22
59
  json (~> 2.3)
60
+ language_server-protocol (>= 3.17.0)
23
61
  parallel (~> 1.10)
24
- parser (>= 3.2.0.0)
62
+ parser (>= 3.2.2.3)
25
63
  rainbow (>= 2.2.2, < 4.0)
26
64
  regexp_parser (>= 1.8, < 3.0)
27
65
  rexml (>= 3.2.5, < 4.0)
28
- rubocop-ast (>= 1.26.0, < 2.0)
66
+ rubocop-ast (>= 1.28.1, < 2.0)
29
67
  ruby-progressbar (~> 1.7)
30
68
  unicode-display_width (>= 2.4.0, < 3.0)
31
69
  rubocop-ast (1.29.0)
32
70
  parser (>= 3.2.1.0)
33
71
  ruby-progressbar (1.13.0)
72
+ sqlite3 (1.6.3-x86_64-darwin)
73
+ sqlite3 (1.6.3-x86_64-linux)
74
+ typhoeus (1.4.0)
75
+ ethon (>= 0.9.0)
76
+ tzinfo (2.0.6)
77
+ concurrent-ruby (~> 1.0)
34
78
  unicode-display_width (2.4.2)
35
79
 
36
80
  PLATFORMS
@@ -40,7 +84,7 @@ PLATFORMS
40
84
  DEPENDENCIES
41
85
  minitest (~> 5.0)
42
86
  rake (~> 13.0)
43
- rubocop (~> 1.21)
87
+ rubocop (~> 1.56)
44
88
  type_fusion!
45
89
 
46
90
  BUNDLED WITH
data/README.md CHANGED
@@ -14,16 +14,101 @@ If bundler is not being used to manage dependencies, install the gem by executin
14
14
  gem install type_fusion
15
15
  ```
16
16
 
17
- ## Usage
17
+ #### Rack
18
18
 
19
19
  ```ruby
20
+ require "type_fusion/rack/middleware"
21
+
22
+ use TypeFusion::Middleware
23
+ ```
24
+
25
+ #### Rails
26
+
27
+ Adding the gem to your applications Gemfile will automatically setup `type_fusion`.
28
+
29
+
30
+ ## Configuration
31
+
32
+ Setup `TypeFusion` in an initializer
33
+
34
+ ```ruby
35
+ # config/initializers/type_fusion.rb
36
+
20
37
  require "type_fusion"
38
+
39
+ TypeFusion.config do |config|
40
+
41
+ # === application_name
42
+ #
43
+ # Set application_name to a string which is used to know where the samples
44
+ # came from. Set application_name to an empty string if you wish to not
45
+ # send the application name alongside the samples.
46
+ #
47
+ # Default: "TypeFusion"
48
+ # Default when using Rails: Rails.application.class.module_parent_name
49
+ #
50
+ # config.application_name = "YourApplication"
51
+
52
+
53
+ # === endpoint
54
+ #
55
+ # Set endpoint to an URL where TypeFusion should send the samples to.
56
+ #
57
+ # Default: "https://gem.sh/api/v1/types/samples"
58
+ #
59
+ # config.endpoint = "https://your-domain.com/api/v1/types/samples"
60
+
61
+
62
+ # === type_sample_request
63
+ #
64
+ # Set type_sample_request to a lambda which resolves to true/false
65
+ # to set if type sampling should be enabled for the whole rack request.
66
+ #
67
+ # Default: ->(rack_env) { [true, false, false, false].sample }
68
+ #
69
+ # config.type_sample_request = ->(rack_env) { [true, false, false, false].sample }
70
+
71
+
72
+ # === type_sample_tracepoint_path
73
+ #
74
+ # Set type_sample_tracepoint_path to a lambda which resolves
75
+ # to true/false to check if a tracepoint_path should be sampled
76
+ # or not.
77
+ #
78
+ # This can be useful when you want to only sample method calls for
79
+ # certain gems or want to exclude a gem from being sampled.
80
+ #
81
+ # Example:
82
+ # config.type_sample_tracepoint_path = ->(tracepoint_path) {
83
+ # return false if tracepoint_path.include?("activerecord")
84
+ # return false if tracepoint_path.include?("sprockets")
85
+ # return false if tracepoint_path.include?("some-private-gem")
86
+ #
87
+ # true
88
+ # }
89
+ #
90
+ # Default: ->(tracepoint_path) { true }
91
+ #
92
+ # config.type_sample_tracepoint_path = ->(tracepoint_path) { true }
93
+
94
+
95
+ # === type_sample_call_rate
96
+ #
97
+ # Set type_sample_call_rate to 1.0 to capture 100% of method calls
98
+ # within a rack request.
99
+ #
100
+ # Default: 0.001
101
+ #
102
+ # config.type_sample_call_rate = 0.001
103
+ end
21
104
  ```
22
105
 
106
+ ## Usage
107
+
23
108
  #### Type sample inside a block
24
109
 
25
110
  ```ruby
26
- TypeFusion::Sampler.instance.with_sampling do
111
+ TypeFusion.with_sampling do
27
112
  # run code you want to type sample here
28
113
  end
29
114
  ```
@@ -31,22 +116,22 @@ end
31
116
  #### Type sample globally
32
117
 
33
118
  ```ruby
34
- TypeFusion::Sampler.instance.trace.enable
119
+ TypeFusion.start
35
120
 
36
121
  # run code you want to type sample here
37
122
 
38
- TypeFusion::Sampler.instance.trace.disable
123
+ TypeFusion.stop
39
124
  ```
40
125
 
41
126
  #### Retrieve the samples
42
127
 
43
128
  ```ruby
44
- TypeFusion::Sampler.instance.trace.samples
129
+ TypeFusion::Sampler.instance.samples
45
130
  # => [...]
46
131
  ```
47
132
 
48
133
  ```ruby
49
- TypeFusion::Sampler.instance.trace.samples.first
134
+ TypeFusion::Sampler.instance.samples.first
50
135
 
51
136
  # => #<struct TypeFusion::SampleCall
52
137
  # gem_and_version="nokogiri-1.15.4-x86_64-darwin",
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module TypeFusion
6
+ class << self
7
+ def config
8
+ @config ||= Config.instance
9
+
10
+ yield @config if block_given?
11
+
12
+ @config
13
+ end
14
+ alias configure config
15
+ end
16
+
17
+ class Config
18
+ include Singleton
19
+
20
+ attr_accessor :type_sample_call_rate, :type_sample_request, :type_sample_tracepoint_path, :endpoint, :application_name
21
+
22
+ def initialize
23
+ @type_sample_call_rate = 0.001
24
+ @type_sample_request = ->(_env) { [true, false, false, false].sample }
25
+ @type_sample_tracepoint_path = ->(_tracepoint_path) { true }
26
+ @endpoint = "http://localhost:3000/api/v1/types/samples"
27
+ @application_name = "TypeFusion"
28
+ end
29
+
30
+ def type_sample_request?(env)
31
+ type_sample_request&.call(env)
32
+ end
33
+
34
+ def type_sample_tracepoint_path?(env)
35
+ type_sample_tracepoint_path&.call(env)
36
+ end
37
+
38
+ def type_sample_call?
39
+ type_sample_call_rate > rand
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "litejob"
4
+
5
+ # Litequeue.configure do |config|
6
+ # config.path = "db/type_fusion/queue.sqlite3"
7
+ # end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "type_fusion/sampler"
4
+
5
+ module TypeFusion
6
+ class Middleware
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ if TypeFusion.config.type_sample_request?(env)
13
+ puts "[TypeFusion] Type-sampling this request"
14
+ TypeFusion.start
15
+ end
16
+
17
+ @app.call(env)
18
+ ensure
19
+ TypeFusion.stop
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypeFusion
4
+ class Railtie < ::Rails::Railtie
5
+ initializer "type_fusion.middleware" do |app|
6
+ require "type_fusion/rack/middleware"
7
+
8
+ TypeFusion.config.application_name = app.class.module_parent_name
9
+
10
+ app.config.middleware.use TypeFusion::Middleware
11
+ end
12
+ end
13
+ end
@@ -1,9 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module TypeFusion
4
- SampleCall = Struct.new(:gem_and_version, :receiver, :method_name, :location, :parameters) do
6
+ SampleCall = Struct.new(:gem_name, :gem_version, :receiver, :method_name, :application_name, :location, :type_fusion_version, :parameters, :return_value) do
5
7
  def to_s
6
8
  JSON.pretty_generate(to_h)
7
9
  end
10
+
11
+ def inspect
12
+ "#<TypeFusion::SampleCall receiver=#{receiver.inspect} method_name=#{method_name.inspect} gem_name=#{gem_name.inspect}>"
13
+ end
8
14
  end
9
15
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lhc"
4
+
5
+ module TypeFusion
6
+ class SampleJob
7
+ include Litejob
8
+
9
+ queue_as :default
10
+
11
+ def perform(sample)
12
+ LHC.json.post(TypeFusion.config.endpoint, body: { sample: JSON.parse(sample) })
13
+ rescue StandardError => e
14
+ puts e.inspect
15
+ end
16
+ end
17
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "singleton"
4
- require "json"
5
4
 
6
5
  module TypeFusion
7
6
  class Sampler
@@ -11,6 +10,7 @@ module TypeFusion
11
10
 
12
11
  def initialize
13
12
  @samples = []
13
+ @litejob_server ||= Litejob::Server.new([["default", 1]])
14
14
  end
15
15
 
16
16
  def with_sampling
@@ -22,8 +22,8 @@ module TypeFusion
22
22
  end
23
23
 
24
24
  def trace
25
- @trace ||= TracePoint.trace(:call) do |tracepoint|
26
- if tracepoint.path.start_with?(gem_path)
25
+ @trace ||= TracePoint.trace(:return) do |tracepoint|
26
+ if sample?(tracepoint.path)
27
27
  receiver = begin
28
28
  tracepoint.binding.receiver.name
29
29
  rescue StandardError
@@ -31,18 +31,27 @@ module TypeFusion
31
31
  end
32
32
 
33
33
  method_name = tracepoint.method_id
34
- location = tracepoint.binding.source_location
35
- gem_and_version = location.first.gsub(gem_path, "").split("/").first
36
- args = tracepoint.parameters.map(&:reverse).to_h
34
+ location = tracepoint.binding.source_location.join(":")
35
+ gem_and_version = location.gsub(gem_path, "").split("/").first
36
+ gem, version = gem_and_version_from(gem_and_version)
37
+ args = tracepoint.parameters.to_h(&:reverse)
37
38
  parameters = extract_parameters(args, tracepoint.binding)
39
+ return_value = type_for_object(tracepoint.return_value)
38
40
 
39
- samples << SampleCall.new(
40
- gem_and_version: gem_and_version,
41
+ sample = SampleCall.new(
42
+ gem_name: gem,
43
+ gem_version: version,
41
44
  receiver: receiver,
42
45
  method_name: method_name,
43
46
  location: location,
47
+ type_fusion_version: VERSION,
44
48
  parameters: parameters,
49
+ application_name: TypeFusion.config.application_name,
50
+ return_value: return_value,
45
51
  )
52
+
53
+ samples << sample
54
+ SampleJob.perform_async(sample)
46
55
  end
47
56
  end.tap(&:disable)
48
57
  end
@@ -62,6 +71,12 @@ module TypeFusion
62
71
 
63
72
  private
64
73
 
74
+ def sample?(tracepoint_path)
75
+ TypeFusion.config.type_sample_call? &&
76
+ TypeFusion.config.type_sample_tracepoint_path?(tracepoint_path) &&
77
+ tracepoint_path.start_with?(gem_path)
78
+ end
79
+
65
80
  def type_for_object(object)
66
81
  case object
67
82
  when Hash
@@ -73,6 +88,31 @@ module TypeFusion
73
88
  end
74
89
  end
75
90
 
91
+ def gem_and_version_from(gem_and_version)
92
+ return [] if gem_and_version.nil?
93
+
94
+ splits = gem_and_version.split("-")
95
+
96
+ if splits.length == 1
97
+ [splits.first, nil]
98
+ elsif splits.length == 2
99
+ splits
100
+ else
101
+ *name, version = splits
102
+
103
+ # TODO: there must be a better way to do this
104
+ if ["darwin", "java", "linux", "ucrt", "mingw32"].include?(version)
105
+ amount = (version == "ucrt") ? 3 : 2
106
+
107
+ version = [*name.pop(amount), version].join("-")
108
+ end
109
+
110
+ gem = name.join("-")
111
+
112
+ [gem, version]
113
+ end
114
+ end
115
+
76
116
  def extract_parameters(args, binding)
77
117
  args.map do |name, kind|
78
118
  variable = name.to_s.gsub("*", "").gsub("&", "").to_sym
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypeFusion
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.4"
5
5
  end
data/lib/type_fusion.rb CHANGED
@@ -1,8 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "type_fusion/version"
4
+ require_relative "type_fusion/config"
5
+ require_relative "type_fusion/litejob"
4
6
  require_relative "type_fusion/sample_call"
7
+ require_relative "type_fusion/sample_job"
5
8
  require_relative "type_fusion/sampler"
6
9
 
7
10
  module TypeFusion
11
+ class << self
12
+ def start
13
+ return if Sampler.instance.trace.enabled?
14
+
15
+ puts "[TypeFusion] Starting Sampler..."
16
+ Sampler.instance.trace.enable
17
+ end
18
+
19
+ def stop
20
+ return unless Sampler.instance.trace.enabled?
21
+
22
+ puts "[TypeFusion] Stopping Sampler..."
23
+ Sampler.instance.trace.disable
24
+ end
25
+
26
+ def with_sampling
27
+ start
28
+
29
+ yield if block_given?
30
+
31
+ stop
32
+ end
33
+ end
8
34
  end
35
+
36
+ require_relative "type_fusion/rails/railtie" if defined?(Rails::Railtie)
data/test.rb CHANGED
@@ -3,9 +3,17 @@
3
3
  require "nokogiri"
4
4
  require_relative "lib/type_fusion"
5
5
 
6
- TypeFusion::Sampler.instance.with_sampling do
6
+ TypeFusion.config do |config|
7
+ config.type_sample_call_rate = 1.0
8
+
9
+ config.type_sample_tracepoint_path = lambda do |tracepoint_path|
10
+ tracepoint_path.include?("nokogiri")
11
+ end
12
+ end
13
+
14
+ TypeFusion.with_sampling do
7
15
  Nokogiri.parse("<h1></h1>")
8
16
  end
9
17
 
10
18
  puts TypeFusion::Sampler.instance.samples
11
- puts TypeFusion::Sampler.instance.to_s
19
+ puts TypeFusion::Sampler.instance
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: type_fusion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Roth
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-12 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2023-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lhc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '15.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '15.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: litejob
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.3
13
41
  description: Community-contributed sample data for Ruby types
14
42
  email:
15
43
  - marco.roth@intergga.ch
@@ -26,7 +54,12 @@ files:
26
54
  - README.md
27
55
  - Rakefile
28
56
  - lib/type_fusion.rb
57
+ - lib/type_fusion/config.rb
58
+ - lib/type_fusion/litejob.rb
59
+ - lib/type_fusion/rack/middleware.rb
60
+ - lib/type_fusion/rails/railtie.rb
29
61
  - lib/type_fusion/sample_call.rb
62
+ - lib/type_fusion/sample_job.rb
30
63
  - lib/type_fusion/sampler.rb
31
64
  - lib/type_fusion/version.rb
32
65
  - sig/type_fusion.rbs