rperf 0.6.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 497392cfda8e82d1c37aadd0953b4c73b6bfb09870e6c612c1fd5fced0e3d24f
4
- data.tar.gz: 6960be209fc3d4aac0f268378c5b7e1399027da0c5b7f498bcb4be0662012d62
3
+ metadata.gz: f19061984f2ea33bbcd569c43e8a7ece03071b8fbb442ebe108468eb07d96a14
4
+ data.tar.gz: 66bee438bd8459db8ce89129cef39bdaaba3ad82e348012c5d220fb5b6f3963f
5
5
  SHA512:
6
- metadata.gz: '09fc32b7577ac9544a846c86c37a7ad11e9de00a27bbb0bbd25cbc2fcabe04e74741c64f9fb3cfe1a9663145e058215272a247766c7b8106218eda80cbcd838f'
7
- data.tar.gz: 9d13e685c5a293c4d9033376509bf4b5c762a5f5155a2d5dd6e838d5a55dc79b9ef7d9521a5bcf65eac88a683f0e20cd7e5dac2680134aa565c709eb48452e40
6
+ metadata.gz: 3a9468eadacbb41afbc751bd767141a3db785a2eaa51e33549503fe160a8adb25f6612c0cc4c61381b8f8442836a970a23d29cda4fe696488ca85d2b048518a2
7
+ data.tar.gz: 5e8e6c6c24fbb264f352c98511481e5d448b6a07e390c72cc31beeab2aa03699cde22f2f04268531ab50572e7cfd54cab7235359915caa6c6b3c9eb40f26d4e9
data/docs/help.md CHANGED
@@ -117,6 +117,7 @@ Rperf.save("profile.txt", data)
117
117
  output: File path to write on stop (String or nil)
118
118
  verbose: Print statistics to stderr (true/false, default: false)
119
119
  format: :pprof, :collapsed, :text, or nil for auto-detect (Symbol or nil)
120
+ defer: Start with timer paused; use Rperf.profile to activate (default: false)
120
121
 
121
122
  ### Rperf.stop return value
122
123
 
@@ -210,6 +211,33 @@ In pprof output, use labels for filtering and grouping:
210
211
  go tool pprof -tagroot=request profile.pb.gz
211
212
  go tool pprof -tagleaf=request profile.pb.gz
212
213
 
214
+ ### Rperf.start with defer: true
215
+
216
+ With `defer: true`, the profiler infrastructure is set up but the sampling
217
+ timer does not start. Use `Rperf.profile` to activate the timer for specific
218
+ sections. Outside `profile` blocks, overhead is zero.
219
+
220
+ ### Rperf.profile(**labels, &block)
221
+
222
+ Activates the sampling timer for the block duration and applies labels.
223
+ Designed for use with `start(defer: true)` to profile only specific
224
+ code paths.
225
+
226
+ ```ruby
227
+ Rperf.start(defer: true, mode: :wall)
228
+
229
+ Rperf.profile(endpoint: "/users") do
230
+ handle_request # sampled with endpoint="/users"
231
+ end
232
+ # timer paused — zero overhead
233
+
234
+ data = Rperf.stop
235
+ ```
236
+
237
+ Nesting is supported: timer stays active until the outermost block exits.
238
+ Also works with `start(defer: false)` — applies labels only (timer already
239
+ running). Raises `RuntimeError` if not started, `ArgumentError` without block.
240
+
213
241
  ### Rperf.labels
214
242
 
215
243
  Returns the current thread's labels as a Hash. Empty hash if none set.
@@ -219,20 +247,20 @@ Returns the current thread's labels as a Hash. Empty hash if none set.
219
247
  Writes data to path. format: :pprof, :collapsed, or :text.
220
248
  nil auto-detects from extension.
221
249
 
222
- ### Rperf::Middleware (Rack)
250
+ ### Rperf::RackMiddleware (Rack)
223
251
 
224
- Labels samples with the request endpoint. Requires `require "rperf/middleware"`.
252
+ Labels samples with the request endpoint. Requires `require "rperf/rack"`.
225
253
 
226
254
  ```ruby
227
255
  # Rails
228
- Rails.application.config.middleware.use Rperf::Middleware
256
+ Rails.application.config.middleware.use Rperf::RackMiddleware
229
257
 
230
258
  # Sinatra
231
- use Rperf::Middleware
259
+ use Rperf::RackMiddleware
232
260
  ```
233
261
 
234
- The middleware only sets labels start profiling separately.
235
- Option: `label_key:` (default: `:endpoint`).
262
+ The middleware uses `Rperf.profile` to activate timer and set labels.
263
+ Start profiling separately. Option: `label_key:` (default: `:endpoint`).
236
264
 
237
265
  ### Rperf::ActiveJobMiddleware
238
266
 
data/ext/rperf/rperf.c CHANGED
@@ -36,6 +36,7 @@
36
36
  #define RPERF_FRAME_TABLE_OLD_KEYS_INITIAL 16
37
37
  #define RPERF_AGG_TABLE_INITIAL 1024
38
38
  #define RPERF_STACK_POOL_INITIAL 4096
39
+ #define RPERF_PAUSED(prof) ((prof)->profile_refcount == 0)
39
40
 
40
41
  /* Synthetic frame IDs (reserved in frame_table, 0-based) */
41
42
  #define RPERF_SYNTHETIC_GVL_BLOCKED 0
@@ -182,6 +183,11 @@ typedef struct rperf_profiler {
182
183
  /* Label sets: Ruby Array of Hash objects, managed from Ruby side.
183
184
  * Index 0 is reserved (no labels). GC-marked via profiler_mark. */
184
185
  VALUE label_sets; /* Ruby Array or Qnil */
186
+ /* Profile refcount: controls timer active/paused state.
187
+ * start(defer:false) sets to 1, start(defer:true) sets to 0.
188
+ * profile_inc/dec transitions 0↔1 arm/disarm the timer.
189
+ * Modified only under GVL, so plain int is safe. */
190
+ int profile_refcount;
185
191
  } rperf_profiler_t;
186
192
 
187
193
  static rperf_profiler_t g_profiler;
@@ -718,8 +724,8 @@ rperf_handle_suspended(rperf_profiler_t *prof, VALUE thread, rperf_thread_data_t
718
724
  if (depth <= 0) return;
719
725
  buf->frame_pool_count += depth;
720
726
 
721
- /* Record normal sample (skip if first time — no prev_time) */
722
- if (!is_first) {
727
+ /* Record normal sample (skip if first time — no prev_time, or if paused) */
728
+ if (!is_first && !RPERF_PAUSED(prof)) {
723
729
  int64_t weight = time_now - td->prev_time_ns;
724
730
  rperf_record_sample(prof, frame_start, depth, weight, RPERF_SAMPLE_NORMAL, td->thread_seq, td->label_set_id);
725
731
  }
@@ -758,7 +764,7 @@ rperf_handle_resumed(rperf_profiler_t *prof, VALUE thread, rperf_thread_data_t *
758
764
  * Both samples are written directly into the same buffer before calling
759
765
  * rperf_try_swap, so that a swap triggered by the first sample cannot
760
766
  * move the second into a different buffer with a stale frame_start. */
761
- if (prof->mode == 1 && td->suspended_at_ns > 0) {
767
+ if (prof->mode == 1 && td->suspended_at_ns > 0 && !RPERF_PAUSED(prof)) {
762
768
  rperf_sample_buffer_t *buf = &prof->buffers[atomic_load_explicit(&prof->active_idx, memory_order_relaxed)];
763
769
  if (rperf_ensure_frame_pool_capacity(buf, RPERF_MAX_STACK_DEPTH) < 0) goto skip_gvl;
764
770
  size_t frame_start = buf->frame_pool_count;
@@ -851,6 +857,7 @@ rperf_gc_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE
851
857
  }
852
858
  else if (event & RUBY_INTERNAL_EVENT_GC_EXIT) {
853
859
  if (prof->gc.enter_ns <= 0) return;
860
+ if (RPERF_PAUSED(prof)) { prof->gc.enter_ns = 0; return; }
854
861
 
855
862
  int64_t wall_now = rperf_wall_time_ns();
856
863
  int64_t weight = wall_now - prof->gc.enter_ns;
@@ -888,6 +895,7 @@ rperf_sample_job(void *arg)
888
895
  rperf_profiler_t *prof = (rperf_profiler_t *)arg;
889
896
 
890
897
  if (!prof->running) return;
898
+ if (RPERF_PAUSED(prof)) return;
891
899
 
892
900
  /* Measure sampling overhead */
893
901
  struct timespec ts_start, ts_end;
@@ -985,20 +993,32 @@ rperf_worker_nanosleep_func(void *arg)
985
993
 
986
994
  CHECKED(pthread_mutex_lock(&prof->worker_mutex));
987
995
  while (prof->running) {
988
- int ret = pthread_cond_timedwait(&prof->worker_cond, &prof->worker_mutex, &deadline);
989
- if (ret != 0 && ret != ETIMEDOUT) {
990
- fprintf(stderr, "rperf: pthread_cond_timedwait failed: %s\n", strerror(ret));
991
- abort();
992
- }
993
- if (ret == ETIMEDOUT) {
994
- prof->stats.trigger_count++;
995
- rb_postponed_job_trigger(prof->pj_handle);
996
- /* Advance deadline by interval */
996
+ if (RPERF_PAUSED(prof)) {
997
+ /* Paused: wait indefinitely until signaled (resume or stop) */
998
+ CHECKED(pthread_cond_wait(&prof->worker_cond, &prof->worker_mutex));
999
+ /* Reset deadline on wake to avoid burst of catch-up triggers */
1000
+ clock_gettime(CLOCK_REALTIME, &deadline);
997
1001
  deadline.tv_nsec += interval_ns;
998
1002
  if (deadline.tv_nsec >= 1000000000L) {
999
1003
  deadline.tv_sec++;
1000
1004
  deadline.tv_nsec -= 1000000000L;
1001
1005
  }
1006
+ } else {
1007
+ int ret = pthread_cond_timedwait(&prof->worker_cond, &prof->worker_mutex, &deadline);
1008
+ if (ret != 0 && ret != ETIMEDOUT) {
1009
+ fprintf(stderr, "rperf: pthread_cond_timedwait failed: %s\n", strerror(ret));
1010
+ abort();
1011
+ }
1012
+ if (ret == ETIMEDOUT) {
1013
+ prof->stats.trigger_count++;
1014
+ rb_postponed_job_trigger(prof->pj_handle);
1015
+ /* Advance deadline by interval */
1016
+ deadline.tv_nsec += interval_ns;
1017
+ if (deadline.tv_nsec >= 1000000000L) {
1018
+ deadline.tv_sec++;
1019
+ deadline.tv_nsec -= 1000000000L;
1020
+ }
1021
+ }
1002
1022
  }
1003
1023
  rperf_try_aggregate(prof);
1004
1024
  }
@@ -1110,14 +1130,15 @@ rperf_build_aggregated_result(rperf_profiler_t *prof)
1110
1130
 
1111
1131
  /* ---- Ruby API ---- */
1112
1132
 
1113
- /* _c_start(frequency, mode, aggregate, signal)
1133
+ /* _c_start(frequency, mode, aggregate, signal, defer)
1114
1134
  * frequency: Integer (Hz)
1115
1135
  * mode: 0 = cpu, 1 = wall
1116
1136
  * aggregate: 0 or 1
1117
1137
  * signal: Integer (RT signal number, 0 = nanosleep, -1 = default)
1138
+ * defer: if truthy, start with timer paused (profile_refcount = 0)
1118
1139
  */
1119
1140
  static VALUE
1120
- rb_rperf_start(VALUE self, VALUE vfreq, VALUE vmode, VALUE vagg, VALUE vsig)
1141
+ rb_rperf_start(VALUE self, VALUE vfreq, VALUE vmode, VALUE vagg, VALUE vsig, VALUE vdefer)
1121
1142
  {
1122
1143
  int frequency = NUM2INT(vfreq);
1123
1144
  int mode = NUM2INT(vmode);
@@ -1222,6 +1243,7 @@ rb_rperf_start(VALUE self, VALUE vfreq, VALUE vmode, VALUE vagg, VALUE vsig)
1222
1243
  clock_gettime(CLOCK_MONOTONIC, &g_profiler.start_monotonic);
1223
1244
 
1224
1245
  g_profiler.running = 1;
1246
+ g_profiler.profile_refcount = RTEST(vdefer) ? 0 : 1;
1225
1247
 
1226
1248
  #if RPERF_USE_TIMER_SIGNAL
1227
1249
  g_profiler.timer_signal = timer_signal;
@@ -1269,7 +1291,12 @@ rb_rperf_start(VALUE self, VALUE vfreq, VALUE vmode, VALUE vagg, VALUE vsig)
1269
1291
  }
1270
1292
 
1271
1293
  its.it_value.tv_sec = 0;
1272
- its.it_value.tv_nsec = 1000000000L / g_profiler.frequency;
1294
+ if (RPERF_PAUSED(&g_profiler)) {
1295
+ /* defer mode: create timer but don't arm it */
1296
+ its.it_value.tv_nsec = 0;
1297
+ } else {
1298
+ its.it_value.tv_nsec = 1000000000L / g_profiler.frequency;
1299
+ }
1273
1300
  its.it_interval = its.it_value;
1274
1301
  if (timer_settime(g_profiler.timer_id, 0, &its, NULL) != 0) {
1275
1302
  timer_delete(g_profiler.timer_id);
@@ -1558,6 +1585,94 @@ rb_rperf_get_label_sets(VALUE self)
1558
1585
  return g_profiler.label_sets;
1559
1586
  }
1560
1587
 
1588
+ /* ---- Profile refcount API (timer pause/resume) ---- */
1589
+
1590
+ /* Helper: arm the timer with the configured interval */
1591
+ static void
1592
+ rperf_arm_timer(rperf_profiler_t *prof)
1593
+ {
1594
+ #if RPERF_USE_TIMER_SIGNAL
1595
+ if (prof->timer_signal > 0) {
1596
+ struct itimerspec its;
1597
+ its.it_value.tv_sec = 0;
1598
+ its.it_value.tv_nsec = 1000000000L / prof->frequency;
1599
+ its.it_interval = its.it_value;
1600
+ timer_settime(prof->timer_id, 0, &its, NULL);
1601
+ return;
1602
+ }
1603
+ #endif
1604
+ /* nanosleep mode: signal the worker to wake from cond_wait */
1605
+ CHECKED(pthread_mutex_lock(&prof->worker_mutex));
1606
+ CHECKED(pthread_cond_signal(&prof->worker_cond));
1607
+ CHECKED(pthread_mutex_unlock(&prof->worker_mutex));
1608
+ }
1609
+
1610
+ /* Helper: disarm the timer (stop firing) */
1611
+ static void
1612
+ rperf_disarm_timer(rperf_profiler_t *prof)
1613
+ {
1614
+ #if RPERF_USE_TIMER_SIGNAL
1615
+ if (prof->timer_signal > 0) {
1616
+ struct itimerspec its;
1617
+ memset(&its, 0, sizeof(its));
1618
+ timer_settime(prof->timer_id, 0, &its, NULL);
1619
+ return;
1620
+ }
1621
+ #endif
1622
+ /* nanosleep mode: worker will see RPERF_PAUSED on next iteration */
1623
+ }
1624
+
1625
+ /* Helper: reset prev_time_ns for all threads (called on resume to avoid
1626
+ * inflated weight from pause duration). Must be called with GVL held. */
1627
+ static void
1628
+ rperf_reset_thread_times(rperf_profiler_t *prof)
1629
+ {
1630
+ VALUE threads = rb_funcall(rb_cThread, rb_intern("list"), 0);
1631
+ long tc = RARRAY_LEN(threads);
1632
+ for (long i = 0; i < tc; i++) {
1633
+ VALUE thread = RARRAY_AREF(threads, i);
1634
+ rperf_thread_data_t *td = (rperf_thread_data_t *)rb_internal_thread_specific_get(thread, prof->ts_key);
1635
+ if (td) {
1636
+ td->prev_time_ns = rperf_current_time_ns(prof, td);
1637
+ td->prev_wall_ns = rperf_wall_time_ns();
1638
+ }
1639
+ }
1640
+ }
1641
+
1642
+ /* _c_profile_inc() — increment profile refcount; resume timer on 0→1.
1643
+ * Called with GVL held. */
1644
+ static VALUE
1645
+ rb_rperf_profile_inc(VALUE self)
1646
+ {
1647
+ if (!g_profiler.running) return Qfalse;
1648
+ g_profiler.profile_refcount++;
1649
+ if (g_profiler.profile_refcount == 1) {
1650
+ rperf_reset_thread_times(&g_profiler);
1651
+ rperf_arm_timer(&g_profiler);
1652
+ }
1653
+ return Qtrue;
1654
+ }
1655
+
1656
+ /* _c_profile_dec() — decrement profile refcount; pause timer on 1→0.
1657
+ * Called with GVL held. */
1658
+ static VALUE
1659
+ rb_rperf_profile_dec(VALUE self)
1660
+ {
1661
+ if (!g_profiler.running) return Qfalse;
1662
+ g_profiler.profile_refcount--;
1663
+ if (g_profiler.profile_refcount == 0) {
1664
+ rperf_disarm_timer(&g_profiler);
1665
+ }
1666
+ return Qtrue;
1667
+ }
1668
+
1669
+ /* _c_running?() — check if profiler is running. */
1670
+ static VALUE
1671
+ rb_rperf_running_p(VALUE self)
1672
+ {
1673
+ return g_profiler.running ? Qtrue : Qfalse;
1674
+ }
1675
+
1561
1676
  /* ---- Fork safety ---- */
1562
1677
 
1563
1678
  static void
@@ -1608,6 +1723,7 @@ rperf_after_fork_child(void)
1608
1723
  /* Reset stats */
1609
1724
  g_profiler.stats.sampling_count = 0;
1610
1725
  g_profiler.stats.sampling_total_ns = 0;
1726
+ g_profiler.profile_refcount = 0;
1611
1727
  atomic_store_explicit(&g_profiler.swap_ready, 0, memory_order_relaxed);
1612
1728
  }
1613
1729
 
@@ -1617,13 +1733,16 @@ void
1617
1733
  Init_rperf(void)
1618
1734
  {
1619
1735
  VALUE mRperf = rb_define_module("Rperf");
1620
- rb_define_module_function(mRperf, "_c_start", rb_rperf_start, 4);
1736
+ rb_define_module_function(mRperf, "_c_start", rb_rperf_start, 5);
1621
1737
  rb_define_module_function(mRperf, "_c_stop", rb_rperf_stop, 0);
1622
1738
  rb_define_module_function(mRperf, "_c_snapshot", rb_rperf_snapshot, 1);
1623
1739
  rb_define_module_function(mRperf, "_c_set_label", rb_rperf_set_label, 1);
1624
1740
  rb_define_module_function(mRperf, "_c_get_label", rb_rperf_get_label, 0);
1625
1741
  rb_define_module_function(mRperf, "_c_set_label_sets", rb_rperf_set_label_sets, 1);
1626
1742
  rb_define_module_function(mRperf, "_c_get_label_sets", rb_rperf_get_label_sets, 0);
1743
+ rb_define_module_function(mRperf, "_c_profile_inc", rb_rperf_profile_inc, 0);
1744
+ rb_define_module_function(mRperf, "_c_profile_dec", rb_rperf_profile_dec, 0);
1745
+ rb_define_module_function(mRperf, "_c_running?", rb_rperf_running_p, 0);
1627
1746
 
1628
1747
  memset(&g_profiler, 0, sizeof(g_profiler));
1629
1748
  g_profiler.label_sets = Qnil;
@@ -5,7 +5,7 @@ module Rperf::ActiveJobMiddleware
5
5
 
6
6
  included do
7
7
  around_perform do |job, block|
8
- Rperf.label(job: job.class.name) do
8
+ Rperf.profile(job: job.class.name) do
9
9
  block.call
10
10
  end
11
11
  end
@@ -1,6 +1,6 @@
1
1
  require "rperf"
2
2
 
3
- class Rperf::Middleware
3
+ class Rperf::RackMiddleware
4
4
  def initialize(app, label_key: :endpoint)
5
5
  @app = app
6
6
  @label_key = label_key
@@ -8,7 +8,7 @@ class Rperf::Middleware
8
8
 
9
9
  def call(env)
10
10
  endpoint = "#{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"
11
- Rperf.label(@label_key => endpoint) do
11
+ Rperf.profile(@label_key => endpoint) do
12
12
  @app.call(env)
13
13
  end
14
14
  end
data/lib/rperf/sidekiq.rb CHANGED
@@ -2,7 +2,7 @@ require "rperf"
2
2
 
3
3
  class Rperf::SidekiqMiddleware
4
4
  def call(_worker, job, _queue)
5
- Rperf.label(job: job["class"]) do
5
+ Rperf.profile(job: job["class"]) do
6
6
  yield
7
7
  end
8
8
  end
data/lib/rperf/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rperf
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
data/lib/rperf.rb CHANGED
@@ -23,7 +23,7 @@ module Rperf
23
23
  # .collapsed → collapsed stacks (FlameGraph / speedscope compatible)
24
24
  # .txt → text report (human/AI readable flat + cumulative table)
25
25
  # otherwise (.pb.gz etc) → pprof protobuf (gzip compressed)
26
- def self.start(frequency: 1000, mode: :cpu, output: nil, verbose: false, format: nil, stat: false, signal: nil, aggregate: true)
26
+ def self.start(frequency: 1000, mode: :cpu, output: nil, verbose: false, format: nil, stat: false, signal: nil, aggregate: true, defer: false)
27
27
  raise ArgumentError, "frequency must be a positive integer (got #{frequency.inspect})" unless frequency.is_a?(Integer) && frequency > 0
28
28
  raise ArgumentError, "frequency must be <= 10000 (10KHz), got #{frequency}" if frequency > 10_000
29
29
  raise ArgumentError, "mode must be :cpu or :wall, got #{mode.inspect}" unless %i[cpu wall].include?(mode)
@@ -44,7 +44,7 @@ module Rperf
44
44
  @stat_start_mono = Process.clock_gettime(Process::CLOCK_MONOTONIC) if @stat
45
45
  @label_set_table = nil
46
46
  @label_set_index = nil
47
- _c_start(frequency, c_mode, aggregate, c_signal)
47
+ _c_start(frequency, c_mode, aggregate, c_signal, defer)
48
48
 
49
49
  if block_given?
50
50
  begin
@@ -148,6 +148,38 @@ module Rperf
148
148
  end
149
149
  end
150
150
 
151
+ # Profiles the given block: activates timer sampling for the duration
152
+ # and optionally applies labels. Use with start(defer: true) to profile
153
+ # only specific sections of code.
154
+ #
155
+ # Rperf.start(defer: true, mode: :wall)
156
+ # Rperf.profile(endpoint: "/users") { handle_request }
157
+ # data = Rperf.stop
158
+ #
159
+ # Nesting is supported: timer stays active until the outermost profile exits.
160
+ # Requires a block. Raises if profiling is not started.
161
+ def self.profile(**kw, &block)
162
+ raise ArgumentError, "Rperf.profile requires a block" unless block
163
+ raise RuntimeError, "Rperf is not started" unless _c_running?
164
+
165
+ _init_label_sets unless @label_set_table
166
+
167
+ cur_id = _c_get_label
168
+ cur_labels = @label_set_table[cur_id] || {}
169
+ new_labels = cur_labels.merge(kw).reject { |_, v| v.nil? }
170
+ new_id = _intern_label_set(new_labels)
171
+ _c_set_label(new_id)
172
+
173
+ _c_profile_inc
174
+
175
+ begin
176
+ yield
177
+ ensure
178
+ _c_profile_dec
179
+ _c_set_label(cur_id)
180
+ end
181
+ end
182
+
151
183
  # Returns the current thread's labels as a Hash.
152
184
  # Returns an empty Hash if no labels are set or profiling is not running.
153
185
  def self.labels
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rperf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Sasada
@@ -53,7 +53,7 @@ files:
53
53
  - ext/rperf/rperf.c
54
54
  - lib/rperf.rb
55
55
  - lib/rperf/active_job.rb
56
- - lib/rperf/middleware.rb
56
+ - lib/rperf/rack.rb
57
57
  - lib/rperf/sidekiq.rb
58
58
  - lib/rperf/version.rb
59
59
  homepage: https://github.com/ko1/rperf