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 +4 -4
- data/docs/help.md +34 -6
- data/ext/rperf/rperf.c +135 -16
- data/lib/rperf/active_job.rb +1 -1
- data/lib/rperf/{middleware.rb → rack.rb} +2 -2
- data/lib/rperf/sidekiq.rb +1 -1
- data/lib/rperf/version.rb +1 -1
- data/lib/rperf.rb +34 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f19061984f2ea33bbcd569c43e8a7ece03071b8fbb442ebe108468eb07d96a14
|
|
4
|
+
data.tar.gz: 66bee438bd8459db8ce89129cef39bdaaba3ad82e348012c5d220fb5b6f3963f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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::
|
|
250
|
+
### Rperf::RackMiddleware (Rack)
|
|
223
251
|
|
|
224
|
-
Labels samples with the request endpoint. Requires `require "rperf/
|
|
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::
|
|
256
|
+
Rails.application.config.middleware.use Rperf::RackMiddleware
|
|
229
257
|
|
|
230
258
|
# Sinatra
|
|
231
|
-
use Rperf::
|
|
259
|
+
use Rperf::RackMiddleware
|
|
232
260
|
```
|
|
233
261
|
|
|
234
|
-
The middleware
|
|
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
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
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
|
-
|
|
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,
|
|
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;
|
data/lib/rperf/active_job.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require "rperf"
|
|
2
2
|
|
|
3
|
-
class Rperf::
|
|
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.
|
|
11
|
+
Rperf.profile(@label_key => endpoint) do
|
|
12
12
|
@app.call(env)
|
|
13
13
|
end
|
|
14
14
|
end
|
data/lib/rperf/sidekiq.rb
CHANGED
data/lib/rperf/version.rb
CHANGED
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.
|
|
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/
|
|
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
|