duel-rails 0.1.0
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 +7 -0
- data/CHANGELOG.md +99 -0
- data/LICENSE +21 -0
- data/README.md +456 -0
- data/lib/duel/rails/bundle.rb +45 -0
- data/lib/duel/rails/counter.rb +122 -0
- data/lib/duel/rails/database.rb +54 -0
- data/lib/duel/rails/jobs.rb +63 -0
- data/lib/duel/rails/railtie.rb +19 -0
- data/lib/duel/rails/request.rb +125 -0
- data/lib/duel/rails/version.rb +7 -0
- data/lib/duel/rails.rb +152 -0
- data/lib/generators/duel/install/install_generator.rb +126 -0
- data/lib/generators/duel/install/templates/benchmark.rb.tt +27 -0
- data/lib/generators/duel/install/templates/boot_duel.rb.tt +9 -0
- data/lib/generators/duel/install/templates/duel.rb.tt +23 -0
- data/lib/generators/duel/install/templates/perf.yml.tt +109 -0
- data/lib/generators/duel/install/templates/requests_duel.rb.tt +13 -0
- data/sig/duel/rails.rbs +245 -0
- metadata +106 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# The environment Duel benchmarks the app in (see .duel.rb): production's,
|
|
2
|
+
# run locally, without what needs a deployment (SSL, a real secret, other
|
|
3
|
+
# databases) and without logging.
|
|
4
|
+
require_relative "production"
|
|
5
|
+
|
|
6
|
+
Rails.application.configure do
|
|
7
|
+
config.secret_key_base = "duel-benchmark" * 4
|
|
8
|
+
config.require_master_key = false
|
|
9
|
+
|
|
10
|
+
config.logger = ActiveSupport::Logger.new(nil)
|
|
11
|
+
config.log_level = :fatal
|
|
12
|
+
|
|
13
|
+
# Requests come from Duel, without a server in front.
|
|
14
|
+
config.force_ssl = false
|
|
15
|
+
config.assume_ssl = false
|
|
16
|
+
config.hosts.clear
|
|
17
|
+
config.action_controller.allow_forgery_protection = false
|
|
18
|
+
config.public_file_server.enabled = true
|
|
19
|
+
|
|
20
|
+
# Each worker has one database of its own (DATABASE_URL): the cache and
|
|
21
|
+
# jobs stay in the worker. (While a benchmark runs, duel-rails performs
|
|
22
|
+
# the jobs it enqueues, and measures them with it: see .duel.rb.)
|
|
23
|
+
config.cache_store = :memory_store
|
|
24
|
+
config.active_job.queue_adapter = :test if config.respond_to?(:active_job)
|
|
25
|
+
config.solid_queue.connects_to = nil if config.respond_to?(:solid_queue)
|
|
26
|
+
config.action_mailer.delivery_method = :test if config.respond_to?(:action_mailer)
|
|
27
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# How long the app takes to boot, in the benchmark environment: every run
|
|
4
|
+
# is a fresh `bin/rails runner`, and each sample the fastest of a few runs
|
|
5
|
+
# of each version (one, for a boot that takes a second or more), judged on
|
|
6
|
+
# its CPU time (its children's too) and its peak memory. It boots without a
|
|
7
|
+
# database of its own, so it measures boot alone: if your app's boot
|
|
8
|
+
# queries the database, leave this benchmark out.
|
|
9
|
+
duel_process "boot: the app", "bin/rails", "runner", "1"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Duel compares this app's benchmarks (duel/) on two versions of it, each
|
|
4
|
+
# booted in the benchmark environment (config/environments/benchmark.rb),
|
|
5
|
+
# with a bundle of its own and a database of its own, which its schema
|
|
6
|
+
# (and data) is loaded into. See
|
|
7
|
+
# https://github.com/sferik/duel-ruby/tree/main/plugins/rails.
|
|
8
|
+
Duel.configure do |c|
|
|
9
|
+
<% if version_file? -%>
|
|
10
|
+
# Each version runs on the Ruby that its own .ruby-version names, so a
|
|
11
|
+
# change that upgrades Ruby is compared too. Each Ruby must be
|
|
12
|
+
# installed, with rbenv, chruby, mise or asdf, or be the one running
|
|
13
|
+
# Duel.
|
|
14
|
+
c.ruby = :version_file
|
|
15
|
+
<% end -%>
|
|
16
|
+
<% if fixtures? -%>
|
|
17
|
+
c.use :rails, database: <%= database ? ":#{database}" : "nil" %>, fixtures: :all
|
|
18
|
+
<% else -%>
|
|
19
|
+
# To load test/fixtures, or db/seeds.rb, into each worker's database:
|
|
20
|
+
# fixtures: :all, or seeds: true.
|
|
21
|
+
c.use :rails, database: <%= database ? ":#{database}" : "nil" %>
|
|
22
|
+
<% end -%>
|
|
23
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
name: Perf
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "0 4 * * *"
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pull-requests: write # to comment on a pull request with the report
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
duel:
|
|
17
|
+
name: Duel
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
<% if working_directory -%>
|
|
20
|
+
# The app is in a subdirectory of the repository: Duel runs there, and
|
|
21
|
+
# checks out the whole repository for the baseline.
|
|
22
|
+
defaults:
|
|
23
|
+
run:
|
|
24
|
+
working-directory: <%= working_directory %>
|
|
25
|
+
<% end -%>
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v7
|
|
28
|
+
with:
|
|
29
|
+
fetch-depth: 0 # the baseline comes from history, tags included
|
|
30
|
+
<% if database == "postgresql" -%>
|
|
31
|
+
# Each worker starts a PostgreSQL server of its own (duel-postgresql).
|
|
32
|
+
- run: sudo apt-get install -y postgresql libpq-dev
|
|
33
|
+
<% elsif %w[mysql2 trilogy].include?(database) -%>
|
|
34
|
+
# Each worker starts a MySQL server of its own (duel-mysql), with its
|
|
35
|
+
# data in a directory AppArmor would keep Ubuntu's mysqld out of.
|
|
36
|
+
- run: sudo apt-get install -y mysql-server
|
|
37
|
+
- run: |
|
|
38
|
+
if [ -f /etc/apparmor.d/usr.sbin.mysqld ]; then
|
|
39
|
+
sudo ln -sf /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/
|
|
40
|
+
sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld
|
|
41
|
+
fi
|
|
42
|
+
<% end -%>
|
|
43
|
+
<% if version_file? -%>
|
|
44
|
+
# Each version runs on the Ruby its own .ruby-version names (see
|
|
45
|
+
# .duel.rb): for a pull request that upgrades Ruby, the main branch's
|
|
46
|
+
# too, linked where Duel finds it (as chruby would). It's set up
|
|
47
|
+
# before the app's own Ruby, which runs Duel.
|
|
48
|
+
- name: Find the baseline's Ruby
|
|
49
|
+
id: baseline
|
|
50
|
+
if: github.event_name == 'pull_request'
|
|
51
|
+
run: echo "ruby=$(git show "origin/$GITHUB_BASE_REF:./.ruby-version" 2>/dev/null)" >> "$GITHUB_OUTPUT"
|
|
52
|
+
- uses: ruby/setup-ruby@v1
|
|
53
|
+
id: baseline-ruby
|
|
54
|
+
if: steps.baseline.outputs.ruby != ''
|
|
55
|
+
with:
|
|
56
|
+
ruby-version: ${{ steps.baseline.outputs.ruby }}
|
|
57
|
+
- name: Link the baseline's Ruby where Duel finds it
|
|
58
|
+
if: steps.baseline.outputs.ruby != ''
|
|
59
|
+
run: |
|
|
60
|
+
prefix="${{ steps.baseline-ruby.outputs.ruby-prefix }}"
|
|
61
|
+
mkdir -p ~/.rubies
|
|
62
|
+
ln -sfn "$prefix" ~/.rubies/ruby-"$("$prefix/bin/ruby" -e 'print RUBY_VERSION')"
|
|
63
|
+
<% end -%>
|
|
64
|
+
- uses: ruby/setup-ruby@v1
|
|
65
|
+
with:
|
|
66
|
+
<% if working_directory -%>
|
|
67
|
+
working-directory: <%= working_directory %>
|
|
68
|
+
<% end -%>
|
|
69
|
+
<% if ruby_version -%>
|
|
70
|
+
ruby-version: "<%= ruby_version %>"
|
|
71
|
+
<% end -%>
|
|
72
|
+
bundler-cache: true
|
|
73
|
+
# Against the main branch for a pull request, what was there before
|
|
74
|
+
# for a push, and the last release on a schedule (slow creep). Duel
|
|
75
|
+
# adds its report to the job's summary, and writes it to duel.md for
|
|
76
|
+
# the comment.
|
|
77
|
+
- name: Compare
|
|
78
|
+
env:
|
|
79
|
+
DUEL_BASE: ${{ github.event_name == 'schedule' && 'last-release' || github.event_name == 'push' && github.event.before || '' }}
|
|
80
|
+
# (Run by hand, it compares anyway: with nothing changed, the code
|
|
81
|
+
# with itself, which checks the setup.)
|
|
82
|
+
DUEL_FORCE: ${{ github.event_name == 'workflow_dispatch' && '1' || '' }}
|
|
83
|
+
run: |
|
|
84
|
+
# (A branch's first push, or one that rewrote history, has no
|
|
85
|
+
# commit before it here to compare against.)
|
|
86
|
+
if [ "$GITHUB_EVENT_NAME" = push ] && ! git cat-file -e "$DUEL_BASE^{commit}" 2>/dev/null; then
|
|
87
|
+
echo "::notice::No commit before this push to compare against"
|
|
88
|
+
exit 0
|
|
89
|
+
fi
|
|
90
|
+
bundle exec duel --markdown "$RUNNER_TEMP/duel.md"
|
|
91
|
+
# The report, as a comment on the pull request: one comment, updated
|
|
92
|
+
# on every push. (A pull request from a fork gets no comment, as its
|
|
93
|
+
# token can't write one: the job's summary has the report.)
|
|
94
|
+
- name: Comment on the pull request
|
|
95
|
+
if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }}
|
|
96
|
+
env:
|
|
97
|
+
GH_TOKEN: ${{ github.token }}
|
|
98
|
+
PR: ${{ github.event.pull_request.number }}
|
|
99
|
+
run: |
|
|
100
|
+
[ -f "$RUNNER_TEMP/duel.md" ] || exit 0
|
|
101
|
+
marker="<!-- duel<%= " #{working_directory}" if working_directory %> -->"
|
|
102
|
+
{ echo "$marker"; cat "$RUNNER_TEMP/duel.md"; } > "$RUNNER_TEMP/comment.md"
|
|
103
|
+
ids=$(gh api --paginate "repos/$GITHUB_REPOSITORY/issues/$PR/comments" \
|
|
104
|
+
--jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$marker\"))) | .id")
|
|
105
|
+
if [ -n "$ids" ]; then
|
|
106
|
+
gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/comments/${ids%%$'\n'*}" -F body=@"$RUNNER_TEMP/comment.md" > /dev/null
|
|
107
|
+
else
|
|
108
|
+
gh api --method POST "repos/$GITHUB_REPOSITORY/issues/$PR/comments" -F body=@"$RUNNER_TEMP/comment.md" > /dev/null
|
|
109
|
+
fi
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Benchmarks of the app's requests. Each sends its request through the
|
|
4
|
+
# whole Rack stack, performs the jobs it enqueued, and rolls back what it
|
|
5
|
+
# wrote. A version fails that's slower at any of them, or that queries
|
|
6
|
+
# the database, renders views or allocates more. Add the requests that
|
|
7
|
+
# matter most, with what they send:
|
|
8
|
+
#
|
|
9
|
+
# duel_request "posts: create", "POST /posts", params: {post: {title: "Hi"}}
|
|
10
|
+
<% routes.each do |name, request| -%>
|
|
11
|
+
|
|
12
|
+
duel_request <%= name.inspect %>, <%= request.inspect %>
|
|
13
|
+
<% end -%>
|
data/sig/duel/rails.rbs
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
module Duel
|
|
2
|
+
# Duel for a Rails app: each version booted in the benchmark
|
|
3
|
+
# environment, with a bundle and a database of its own, and benchmarks
|
|
4
|
+
# of requests (DSL#duel_request), with the jobs they enqueue.
|
|
5
|
+
module Rails
|
|
6
|
+
VERSION: String
|
|
7
|
+
|
|
8
|
+
# What .configure gave .reset_data to load again, or nil (no database).
|
|
9
|
+
self.@data: {fixtures: Array[Symbol | String] | :all, seeds: bool, seed: Integer}?
|
|
10
|
+
|
|
11
|
+
# What a change must touch to be compared.
|
|
12
|
+
WATCH: Array[String]
|
|
13
|
+
|
|
14
|
+
# The environment each version boots in.
|
|
15
|
+
ENVIRONMENT: String
|
|
16
|
+
|
|
17
|
+
# What random numbers are seeded with in each worker.
|
|
18
|
+
SEED: Integer
|
|
19
|
+
|
|
20
|
+
# An around_iteration hook: each iteration rolled back after it.
|
|
21
|
+
ROLLBACK: ^(Iteration iteration) -> untyped
|
|
22
|
+
|
|
23
|
+
# Configures Duel for the app, c.use :rails (see the README for the
|
|
24
|
+
# options).
|
|
25
|
+
def self.configure: (Config config, ?database: Symbol?, ?environment: String, ?bundle: bool,
|
|
26
|
+
?fixtures: Array[Symbol | String] | :all, ?seeds: bool, ?seed: Integer, ?reset: bool, ?jobs: bool,
|
|
27
|
+
?active_record: bool, ?query_time: bool, **untyped database_options) -> Counter
|
|
28
|
+
|
|
29
|
+
# Boots the version at +root+ in +environment+, from its own tree.
|
|
30
|
+
def self.boot: (String root, String environment) -> bool
|
|
31
|
+
|
|
32
|
+
# Loads the worker's data: the schema, fixtures, and seeds.
|
|
33
|
+
def self.load_worker_data: (fixtures: Array[Symbol | String] | :all, seeds: bool, seed: Integer) -> void
|
|
34
|
+
|
|
35
|
+
# Loads fixtures and seeds, with random numbers seeded.
|
|
36
|
+
def self.load_data: (fixtures: Array[Symbol | String] | :all, seeds: bool, seed: Integer) -> void
|
|
37
|
+
|
|
38
|
+
# Empties every table, and loads its fixtures and seeds again.
|
|
39
|
+
def self.reset_data: () -> void
|
|
40
|
+
|
|
41
|
+
# Runs the block with what it writes to the database rolled back.
|
|
42
|
+
def self.rolled_back: [T] () { () -> T } -> T
|
|
43
|
+
|
|
44
|
+
# Seeds Ruby's random numbers, and Faker's.
|
|
45
|
+
def self.seed_random: (Integer seed) -> void
|
|
46
|
+
|
|
47
|
+
# Each version runs its own bundle.
|
|
48
|
+
module Bundle
|
|
49
|
+
def self.configure: (Config config) -> void
|
|
50
|
+
|
|
51
|
+
# The environment for the bundle of the version, installed first.
|
|
52
|
+
def self.environment: (Resource version) -> Hash[String, String?]
|
|
53
|
+
|
|
54
|
+
# What a bundle installed for a lockfile, on a Ruby, is kept as.
|
|
55
|
+
def self.key: (String lockfile, String ruby) -> String
|
|
56
|
+
|
|
57
|
+
# The runner's environment without Bundler's part.
|
|
58
|
+
def self.unbundled: () -> Hash[String, String?]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Each worker's own database, with its version's schema and data.
|
|
62
|
+
module Database
|
|
63
|
+
# The plugin that gives each worker a server, by database.
|
|
64
|
+
SERVERS: Hash[Symbol, Symbol]
|
|
65
|
+
|
|
66
|
+
# The URL's scheme, by database, where the plugin's own isn't it.
|
|
67
|
+
SCHEMES: Hash[Symbol, String]
|
|
68
|
+
|
|
69
|
+
def self.configure: (Config config, Symbol database, **untyped options) -> untyped
|
|
70
|
+
|
|
71
|
+
# Loads the version's schema into the worker's database.
|
|
72
|
+
def self.load_schema: () -> void
|
|
73
|
+
|
|
74
|
+
# Empties every table of the worker's database.
|
|
75
|
+
def self.clear: () -> void
|
|
76
|
+
|
|
77
|
+
# Loads fixtures, by name, or every one.
|
|
78
|
+
def self.load_fixtures: (Array[Symbol | String] | :all names, ?path: path) -> void
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# A request a benchmark sends the app, through its whole Rack stack.
|
|
82
|
+
class Request
|
|
83
|
+
METHODS: Array[String]
|
|
84
|
+
|
|
85
|
+
# The statuses a request is expected to answer with, unless told.
|
|
86
|
+
OK: Range[Integer]
|
|
87
|
+
|
|
88
|
+
@options: Hash[untyped, untyped]
|
|
89
|
+
@status: Integer | Range[Integer]
|
|
90
|
+
@rollback: bool
|
|
91
|
+
|
|
92
|
+
attr_reader verb: String
|
|
93
|
+
attr_reader path: String
|
|
94
|
+
|
|
95
|
+
def initialize: (String request, ?params: Hash[untyped, untyped]?, ?json: untyped, ?headers: Hash[String | Symbol, String],
|
|
96
|
+
?status: Integer | Range[Integer], ?rollback: bool) -> void
|
|
97
|
+
|
|
98
|
+
# A fresh Rack environment for the request.
|
|
99
|
+
def env: () -> Hash[String, untyped]
|
|
100
|
+
|
|
101
|
+
# Sends the request; fails unless its status is as expected.
|
|
102
|
+
def call: (?Hash[String, untyped] rack_env) -> Integer
|
|
103
|
+
|
|
104
|
+
# Its benchmark's own hooks: ROLLBACK, unless it isn't rolled back.
|
|
105
|
+
def hooks: () -> Hash[Symbol, untyped]
|
|
106
|
+
|
|
107
|
+
# Defines a benchmark of the request, on a file of benchmarks' DSL.
|
|
108
|
+
def self.duel: (::Duel::DSL dsl, String name, String request, ?only: only?, ?peak_memory: bool, **untyped options) ?{ (Hash[String, untyped] env) [self: untyped] -> void } -> void
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def respond: (Hash[String, untyped] env) -> Integer
|
|
113
|
+
|
|
114
|
+
def header: (String | Symbol name) -> String
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# What files of benchmarks get, as .duel.rb uses the plugin.
|
|
118
|
+
module DSL : ::Duel::DSL
|
|
119
|
+
# Defines a benchmark of a request to the app.
|
|
120
|
+
def duel_request: (String name, ?String request, **untyped options) ?{ (Hash[String, untyped] env) [self: untyped] -> void } -> void
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# What the app's views and its cache did for each benchmark.
|
|
124
|
+
class Counter
|
|
125
|
+
# A notification's payload, as Rails gives it.
|
|
126
|
+
type payload = Hash[Symbol, untyped]
|
|
127
|
+
|
|
128
|
+
MEASURES: Hash[String, Metric::settings]
|
|
129
|
+
|
|
130
|
+
# What each event it subscribes to is.
|
|
131
|
+
EVENTS: Hash[String, Symbol]
|
|
132
|
+
|
|
133
|
+
# The most views shown, explaining a regression.
|
|
134
|
+
SHOWN: Integer
|
|
135
|
+
|
|
136
|
+
@views: Integer
|
|
137
|
+
@reads: Integer
|
|
138
|
+
@misses: Integer
|
|
139
|
+
@writes: Integer
|
|
140
|
+
@rendered: Hash[String?, Integer]
|
|
141
|
+
@subscriptions: Array[untyped]
|
|
142
|
+
@root: String
|
|
143
|
+
|
|
144
|
+
def self.configure: (Config config) -> Counter
|
|
145
|
+
|
|
146
|
+
def initialize: () -> void
|
|
147
|
+
|
|
148
|
+
def subscribe: () -> void
|
|
149
|
+
|
|
150
|
+
def unsubscribe: () -> void
|
|
151
|
+
|
|
152
|
+
# The totals so far, by name.
|
|
153
|
+
def totals: () -> Hash[String, Integer]
|
|
154
|
+
|
|
155
|
+
# Each view rendered so far, by its template's path.
|
|
156
|
+
def views: () -> Hash[String?, Integer]
|
|
157
|
+
|
|
158
|
+
# What explains the views a sample rendered, or nil.
|
|
159
|
+
def details: (Array[[Snapshot::Reading[Integer, Hash[String?, Integer]], Snapshot::Reading[Integer, Hash[String?, Integer]]]] stretches,
|
|
160
|
+
Integer n) -> Hash[String, String]?
|
|
161
|
+
|
|
162
|
+
def start: (String name, String id, payload payload) -> nil
|
|
163
|
+
|
|
164
|
+
def finish: (String name, String id, payload payload) -> void
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def view: (payload payload, Integer count) -> void
|
|
169
|
+
|
|
170
|
+
def read: (Integer keys, Integer hits) -> void
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
module Rails
|
|
175
|
+
# Active Job's queue while benchmarking: jobs performed after the
|
|
176
|
+
# iterations (an after_iterations hook).
|
|
177
|
+
class Jobs
|
|
178
|
+
@queue: Array[Hash[String, untyped]]
|
|
179
|
+
|
|
180
|
+
def self.configure: (Config config) -> Jobs
|
|
181
|
+
|
|
182
|
+
def initialize: () -> void
|
|
183
|
+
|
|
184
|
+
# Makes it Active Job's queue adapter, if the app has Active Job.
|
|
185
|
+
def use: () -> void
|
|
186
|
+
|
|
187
|
+
def enqueue: (untyped job) -> void
|
|
188
|
+
|
|
189
|
+
def enqueue_at: (untyped job, untyped timestamp) -> void
|
|
190
|
+
|
|
191
|
+
def enqueue_after_transaction_commit?: () -> true
|
|
192
|
+
|
|
193
|
+
def stopping?: () -> false
|
|
194
|
+
|
|
195
|
+
# The jobs waiting to be performed.
|
|
196
|
+
def size: () -> Integer
|
|
197
|
+
|
|
198
|
+
# Performs the jobs waiting, and those they enqueue.
|
|
199
|
+
def perform: () -> void
|
|
200
|
+
|
|
201
|
+
# Forgets the jobs waiting.
|
|
202
|
+
def clear: () -> void
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
module Rails
|
|
207
|
+
# In the app: `rake duel`.
|
|
208
|
+
class Railtie < ::Rails::Railtie
|
|
209
|
+
# Defines `rake duel`, unless there's one.
|
|
210
|
+
def self.define_task: () -> void
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
module Generators
|
|
215
|
+
# `rails generate duel:install`.
|
|
216
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
217
|
+
ADAPTERS: Hash[String, String]
|
|
218
|
+
PLUGINS: Hash[String, String]
|
|
219
|
+
ROUTES: Integer
|
|
220
|
+
FRAMEWORK: Regexp
|
|
221
|
+
|
|
222
|
+
def create_config: () -> void
|
|
223
|
+
def create_environment: () -> void
|
|
224
|
+
def create_benchmarks: () -> void
|
|
225
|
+
def create_boot_benchmark: () -> void
|
|
226
|
+
def create_workflow: () -> void
|
|
227
|
+
def add_plugin: () -> void
|
|
228
|
+
def add_stackprof: () -> void
|
|
229
|
+
|
|
230
|
+
private
|
|
231
|
+
|
|
232
|
+
# The app's Gemfile.
|
|
233
|
+
def gemfile: () -> String
|
|
234
|
+
|
|
235
|
+
def database: () -> String?
|
|
236
|
+
def adapter: () -> String?
|
|
237
|
+
def routes: () -> Array[[String, String]]
|
|
238
|
+
def fixtures?: () -> bool
|
|
239
|
+
def version_file?: () -> bool
|
|
240
|
+
def ruby_version: () -> String?
|
|
241
|
+
def repository: () -> String
|
|
242
|
+
def working_directory: () -> String?
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: duel-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Erik Berlin
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: duel
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: duel-activerecord
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: railties
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '7.2'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '7.2'
|
|
54
|
+
description: 'A Duel plugin for Rails apps: each version of the app booted like production,
|
|
55
|
+
with its own bundle and database, and benchmarks of its requests, judged on time,
|
|
56
|
+
allocations, queries, views and cache use.'
|
|
57
|
+
email:
|
|
58
|
+
- sferik@gmail.com
|
|
59
|
+
executables: []
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- CHANGELOG.md
|
|
64
|
+
- LICENSE
|
|
65
|
+
- README.md
|
|
66
|
+
- lib/duel/rails.rb
|
|
67
|
+
- lib/duel/rails/bundle.rb
|
|
68
|
+
- lib/duel/rails/counter.rb
|
|
69
|
+
- lib/duel/rails/database.rb
|
|
70
|
+
- lib/duel/rails/jobs.rb
|
|
71
|
+
- lib/duel/rails/railtie.rb
|
|
72
|
+
- lib/duel/rails/request.rb
|
|
73
|
+
- lib/duel/rails/version.rb
|
|
74
|
+
- lib/generators/duel/install/install_generator.rb
|
|
75
|
+
- lib/generators/duel/install/templates/benchmark.rb.tt
|
|
76
|
+
- lib/generators/duel/install/templates/boot_duel.rb.tt
|
|
77
|
+
- lib/generators/duel/install/templates/duel.rb.tt
|
|
78
|
+
- lib/generators/duel/install/templates/perf.yml.tt
|
|
79
|
+
- lib/generators/duel/install/templates/requests_duel.rb.tt
|
|
80
|
+
- sig/duel/rails.rbs
|
|
81
|
+
homepage: https://github.com/sferik/duel-ruby/tree/main/plugins/rails
|
|
82
|
+
licenses:
|
|
83
|
+
- MIT
|
|
84
|
+
metadata:
|
|
85
|
+
source_code_uri: https://github.com/sferik/duel-ruby/tree/main/plugins/rails
|
|
86
|
+
changelog_uri: https://github.com/sferik/duel-ruby/blob/main/plugins/rails/CHANGELOG.md
|
|
87
|
+
bug_tracker_uri: https://github.com/sferik/duel-ruby/issues
|
|
88
|
+
rubygems_mfa_required: 'true'
|
|
89
|
+
rdoc_options: []
|
|
90
|
+
require_paths:
|
|
91
|
+
- lib
|
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '3.4'
|
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
requirements: []
|
|
103
|
+
rubygems_version: 4.0.23
|
|
104
|
+
specification_version: 4
|
|
105
|
+
summary: Performance regression guards for Rails apps, with Duel
|
|
106
|
+
test_files: []
|