bootsnap 1.17.1 → 1.18.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13123a80c1d6f0e73ff602768433dd998a28ab8e63caec7d0e29188990c4afe9
4
- data.tar.gz: cc2e19bea1080f9ce2337095fcb161407e6def9e3412c38fdbb03c0e0ddc2a81
3
+ metadata.gz: 69d7ab3b264f447ac2a6a3ccea6a4cb27e6fa0ce6b50e6fef645c21d41a89a07
4
+ data.tar.gz: 595c8df291862a529b9a679f36bcae270c5d0efeee22b2fa68f1d7fc78cd7a86
5
5
  SHA512:
6
- metadata.gz: 8fbd0f6652387c744f0a345d46e9d7238c1609597c8e26f93ad92cd928178f7c5a3afd2075390c5561c3ace19084c318b27ff3acec9d96bd0153c53ea8a4cb95
7
- data.tar.gz: f35f769e3b39c8f28857a74c874e77cdbe04b4db0df3141fa988ba26ea55bf485e8d560b4cab65d5887508ac2b927eb97d49b6791321b0a68c896db2b617eb6d
6
+ metadata.gz: 38b9b648b181ca689c3ed64b5f01ad2bb1f41c0b51f241eed19b9d5b2fc93247b37fef4e507981b3e606f4d4b1840b2c57a0ef9656d36fbc398f14e725549235
7
+ data.tar.gz: e72104154d6b9bd688a2290e1704546e9dd08f792e2bf34d1a02329578e694c9849e79f3f62f197093ab42a06f8328028426cc0676b6c4c2a3a565ebd586bd44
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.18.1
4
+
5
+ * Handle `EPERM` errors when opening files with `O_NOATIME`.
6
+
7
+ # 1.18.0
8
+
9
+ * `Bootsnap.instrumentation` now receive `:hit` events.
10
+ * Add `Bootsnap.log_stats!` to print hit rate statistics on process exit. Can also be enabled with `BOOTSNAP_STATS=1`.
11
+ * Revalidate stale cache entries by digesting the source content.
12
+ This should significantly improve performance in environments where `mtime` isn't preserved (e.g. CI systems doing a git clone, etc).
13
+ See #468.
14
+ * Open source files and cache entries with `O_NOATIME` when available to reduce disk accesses. See #469.
15
+ * `bootsnap precompile --gemfile` now look for `.rb` files in the whole gem and not just the `lib/` directory. See #466.
16
+
3
17
  # 1.17.1
4
18
 
5
19
  * Fix a compatibility issue with the `prism` library that ships with Ruby 3.3. See #463.
data/README.md CHANGED
@@ -81,6 +81,7 @@ well together.
81
81
  - `DISABLE_BOOTSNAP_COMPILE_CACHE` allows to disable ISeq and YAML caches.
82
82
  - `BOOTSNAP_READONLY` configure bootsnap to not update the cache on miss or stale entries.
83
83
  - `BOOTSNAP_LOG` configure bootsnap to log all caches misses to STDERR.
84
+ - `BOOTSNAP_STATS` log hit rate statistics on exit. Can't be used if `BOOTSNAP_LOG` is enabled.
84
85
  - `BOOTSNAP_IGNORE_DIRECTORIES` a comma separated list of directories that shouldn't be scanned.
85
86
  Useful when you have large directories of non-ruby files inside `$LOAD_PATH`.
86
87
  It defaults to ignore any directory named `node_modules`.
@@ -99,8 +100,8 @@ Bootsnap cache misses can be monitored though a callback:
99
100
  Bootsnap.instrumentation = ->(event, path) { puts "#{event} #{path}" }
100
101
  ```
101
102
 
102
- `event` is either `:miss` or `:stale`. You can also call `Bootsnap.log!` as a shortcut to
103
- log all events to STDERR.
103
+ `event` is either `:hit`, `:miss`, `:stale` or `:revalidated`.
104
+ You can also call `Bootsnap.log!` as a shortcut to log all events to STDERR.
104
105
 
105
106
  To turn instrumentation back off you can set it to nil:
106
107
 
@@ -20,6 +20,10 @@
20
20
  #include <fcntl.h>
21
21
  #include <sys/stat.h>
22
22
 
23
+ #ifndef O_NOATIME
24
+ #define O_NOATIME 0
25
+ #endif
26
+
23
27
  /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
24
28
  * 981 for the cache dir */
25
29
  #define MAX_CACHEPATH_SIZE 1000
@@ -30,7 +34,7 @@
30
34
  #define MAX_CREATE_TEMPFILE_ATTEMPT 3
31
35
 
32
36
  #ifndef RB_UNLIKELY
33
- #define RB_UNLIKELY(x) (x)
37
+ #define RB_UNLIKELY(x) (x)
34
38
  #endif
35
39
 
36
40
  /*
@@ -54,8 +58,10 @@ struct bs_cache_key {
54
58
  uint32_t ruby_revision;
55
59
  uint64_t size;
56
60
  uint64_t mtime;
57
- uint64_t data_size; /* not used for equality */
58
- uint8_t pad[24];
61
+ uint64_t data_size; //
62
+ uint64_t digest;
63
+ uint8_t digest_set;
64
+ uint8_t pad[15];
59
65
  } __attribute__((packed));
60
66
 
61
67
  /*
@@ -69,7 +75,7 @@ struct bs_cache_key {
69
75
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
70
76
 
71
77
  /* Effectively a schema version. Bumping invalidates all previous caches */
72
- static const uint32_t current_version = 4;
78
+ static const uint32_t current_version = 5;
73
79
 
74
80
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
75
81
  * new OS ABI, etc. */
@@ -87,10 +93,10 @@ static VALUE rb_mBootsnap_CompileCache;
87
93
  static VALUE rb_mBootsnap_CompileCache_Native;
88
94
  static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
89
95
  static ID instrumentation_method;
90
- static VALUE sym_miss;
91
- static VALUE sym_stale;
96
+ static VALUE sym_hit, sym_miss, sym_stale, sym_revalidated;
92
97
  static bool instrumentation_enabled = false;
93
98
  static bool readonly = false;
99
+ static bool perm_issue = false;
94
100
 
95
101
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
96
102
  static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
@@ -100,12 +106,21 @@ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handl
100
106
  static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
101
107
 
102
108
  /* Helpers */
109
+ enum cache_status {
110
+ miss,
111
+ hit,
112
+ stale,
113
+ };
103
114
  static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
104
115
  static int bs_read_key(int fd, struct bs_cache_key * key);
105
- static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
116
+ static enum cache_status cache_key_equal_fast_path(struct bs_cache_key * k1, struct bs_cache_key * k2);
117
+ static int cache_key_equal_slow_path(struct bs_cache_key * current_key, struct bs_cache_key * cached_key, const VALUE input_data);
118
+ static int update_cache_key(struct bs_cache_key *current_key, int cache_fd, const char ** errno_provenance);
119
+
120
+ static void bs_cache_key_digest(struct bs_cache_key * key, const VALUE input_data);
106
121
  static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
107
122
  static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
108
- static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
123
+ static int open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance);
109
124
  static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
110
125
  static uint32_t get_ruby_revision(void);
111
126
  static uint32_t get_ruby_platform(void);
@@ -161,11 +176,10 @@ Init_bootsnap(void)
161
176
 
162
177
  instrumentation_method = rb_intern("_instrument");
163
178
 
179
+ sym_hit = ID2SYM(rb_intern("hit"));
164
180
  sym_miss = ID2SYM(rb_intern("miss"));
165
- rb_global_variable(&sym_miss);
166
-
167
181
  sym_stale = ID2SYM(rb_intern("stale"));
168
- rb_global_variable(&sym_stale);
182
+ sym_revalidated = ID2SYM(rb_intern("revalidated"));
169
183
 
170
184
  rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
171
185
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
@@ -185,6 +199,14 @@ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
185
199
  return enabled;
186
200
  }
187
201
 
202
+ static inline void
203
+ bs_instrumentation(VALUE event, VALUE path)
204
+ {
205
+ if (RB_UNLIKELY(instrumentation_enabled)) {
206
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path);
207
+ }
208
+ }
209
+
188
210
  static VALUE
189
211
  bs_readonly_set(VALUE self, VALUE enabled)
190
212
  {
@@ -290,17 +312,53 @@ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_C
290
312
  * The data_size member is not compared, as it serves more of a "header"
291
313
  * function.
292
314
  */
293
- static int
294
- cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
315
+ static enum cache_status cache_key_equal_fast_path(struct bs_cache_key *k1,
316
+ struct bs_cache_key *k2) {
317
+ if (k1->version == k2->version &&
318
+ k1->ruby_platform == k2->ruby_platform &&
319
+ k1->compile_option == k2->compile_option &&
320
+ k1->ruby_revision == k2->ruby_revision && k1->size == k2->size) {
321
+ return (k1->mtime == k2->mtime) ? hit : stale;
322
+ }
323
+ return miss;
324
+ }
325
+
326
+ static int cache_key_equal_slow_path(struct bs_cache_key *current_key,
327
+ struct bs_cache_key *cached_key,
328
+ const VALUE input_data)
295
329
  {
296
- return (
297
- k1->version == k2->version &&
298
- k1->ruby_platform == k2->ruby_platform &&
299
- k1->compile_option == k2->compile_option &&
300
- k1->ruby_revision == k2->ruby_revision &&
301
- k1->size == k2->size &&
302
- k1->mtime == k2->mtime
303
- );
330
+ bs_cache_key_digest(current_key, input_data);
331
+ return current_key->digest == cached_key->digest;
332
+ }
333
+
334
+ static int update_cache_key(struct bs_cache_key *current_key, int cache_fd, const char ** errno_provenance)
335
+ {
336
+ lseek(cache_fd, 0, SEEK_SET);
337
+ ssize_t nwrite = write(cache_fd, current_key, KEY_SIZE);
338
+ if (nwrite < 0) {
339
+ *errno_provenance = "update_cache_key:write";
340
+ return -1;
341
+ }
342
+
343
+ #ifdef HAVE_FDATASYNC
344
+ if (fdatasync(cache_fd) < 0) {
345
+ *errno_provenance = "update_cache_key:fdatasync";
346
+ return -1;
347
+ }
348
+ #endif
349
+
350
+ return 0;
351
+ }
352
+
353
+ /*
354
+ * Fills the cache key digest.
355
+ */
356
+ static void bs_cache_key_digest(struct bs_cache_key *key,
357
+ const VALUE input_data) {
358
+ if (key->digest_set)
359
+ return;
360
+ key->digest = fnv1a_64(input_data);
361
+ key->digest_set = 1;
304
362
  }
305
363
 
306
364
  /*
@@ -356,17 +414,34 @@ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
356
414
 
357
415
  return bs_precompile(path, path_v, cache_path, handler);
358
416
  }
417
+
418
+ static int bs_open_noatime(const char *path, int flags) {
419
+ int fd = 1;
420
+ if (!perm_issue) {
421
+ fd = open(path, flags | O_NOATIME);
422
+ if (fd < 0 && errno == EPERM) {
423
+ errno = 0;
424
+ perm_issue = true;
425
+ }
426
+ }
427
+
428
+ if (perm_issue) {
429
+ fd = open(path, flags);
430
+ }
431
+ return fd;
432
+ }
433
+
359
434
  /*
360
435
  * Open the file we want to load/cache and generate a cache key for it if it
361
436
  * was loaded.
362
437
  */
363
438
  static int
364
- open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
439
+ open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
365
440
  {
366
441
  struct stat statbuf;
367
442
  int fd;
368
443
 
369
- fd = open(path, O_RDONLY);
444
+ fd = bs_open_noatime(path, O_RDONLY);
370
445
  if (fd < 0) {
371
446
  *errno_provenance = "bs_fetch:open_current_file:open";
372
447
  return fd;
@@ -389,6 +464,7 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
389
464
  key->ruby_revision = current_ruby_revision;
390
465
  key->size = (uint64_t)statbuf.st_size;
391
466
  key->mtime = (uint64_t)statbuf.st_mtime;
467
+ key->digest_set = false;
392
468
 
393
469
  return fd;
394
470
  }
@@ -432,7 +508,12 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
432
508
  {
433
509
  int fd, res;
434
510
 
435
- fd = open(path, O_RDONLY);
511
+ if (readonly) {
512
+ fd = bs_open_noatime(path, O_RDONLY);
513
+ } else {
514
+ fd = bs_open_noatime(path, O_RDWR);
515
+ }
516
+
436
517
  if (fd < 0) {
437
518
  *errno_provenance = "bs_fetch:open_cache_file:open";
438
519
  return CACHE_MISS;
@@ -677,7 +758,8 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
677
758
  int res, valid_cache = 0, exception_tag = 0;
678
759
  const char * errno_provenance = NULL;
679
760
 
680
- VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
761
+ VALUE status = Qfalse;
762
+ VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
681
763
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
682
764
  VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
683
765
 
@@ -695,20 +777,44 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
695
777
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
696
778
  if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
697
779
  /* This is ok: valid_cache remains false, we re-populate it. */
698
- if (RB_UNLIKELY(instrumentation_enabled)) {
699
- rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
700
- }
780
+ bs_instrumentation(cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
701
781
  } else if (cache_fd < 0) {
702
782
  exception_message = rb_str_new_cstr(cache_path);
703
783
  goto fail_errno;
704
784
  } else {
705
785
  /* True if the cache existed and no invalidating changes have occurred since
706
786
  * it was generated. */
707
- valid_cache = cache_key_equal(&current_key, &cached_key);
708
- if (RB_UNLIKELY(instrumentation_enabled)) {
709
- if (!valid_cache) {
710
- rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
787
+
788
+ switch(cache_key_equal_fast_path(&current_key, &cached_key)) {
789
+ case hit:
790
+ status = sym_hit;
791
+ valid_cache = true;
792
+ break;
793
+ case miss:
794
+ valid_cache = false;
795
+ break;
796
+ case stale:
797
+ valid_cache = false;
798
+ if ((input_data = bs_read_contents(current_fd, current_key.size,
799
+ &errno_provenance)) == Qfalse) {
800
+ exception_message = path_v;
801
+ goto fail_errno;
711
802
  }
803
+ valid_cache = cache_key_equal_slow_path(&current_key, &cached_key, input_data);
804
+ if (valid_cache) {
805
+ if (!readonly) {
806
+ if (update_cache_key(&current_key, cache_fd, &errno_provenance)) {
807
+ exception_message = path_v;
808
+ goto fail_errno;
809
+ }
810
+ }
811
+ status = sym_revalidated;
812
+ }
813
+ break;
814
+ };
815
+
816
+ if (!valid_cache) {
817
+ status = sym_stale;
712
818
  }
713
819
  }
714
820
 
@@ -722,7 +828,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
722
828
  else if (res == CACHE_UNCOMPILABLE) {
723
829
  /* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
724
830
  This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
725
- if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse){
831
+ if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
726
832
  exception_message = path_v;
727
833
  goto fail_errno;
728
834
  }
@@ -741,7 +847,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
741
847
  /* Cache is stale, invalid, or missing. Regenerate and write it out. */
742
848
 
743
849
  /* Read the contents of the source file into a buffer */
744
- if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse){
850
+ if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
745
851
  exception_message = path_v;
746
852
  goto fail_errno;
747
853
  }
@@ -763,6 +869,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
763
869
  * We do however ignore any failures to persist the cache, as it's better
764
870
  * to move along, than to interrupt the process.
765
871
  */
872
+ bs_cache_key_digest(&current_key, input_data);
766
873
  atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
767
874
 
768
875
  /* Having written the cache, now convert storage_data to output_data */
@@ -792,6 +899,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
792
899
  goto succeed; /* output_data is now the correct return. */
793
900
 
794
901
  #define CLEANUP \
902
+ if (status != Qfalse) bs_instrumentation(status, path_v); \
795
903
  if (current_fd >= 0) close(current_fd); \
796
904
  if (cache_fd >= 0) close(cache_fd);
797
905
 
@@ -818,13 +926,16 @@ invalid_type_storage_data:
818
926
  static VALUE
819
927
  bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
820
928
  {
929
+ if (readonly) {
930
+ return Qfalse;
931
+ }
932
+
821
933
  struct bs_cache_key cached_key, current_key;
822
- char * contents = NULL;
823
934
  int cache_fd = -1, current_fd = -1;
824
935
  int res, valid_cache = 0, exception_tag = 0;
825
936
  const char * errno_provenance = NULL;
826
937
 
827
- VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
938
+ VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
828
939
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
829
940
 
830
941
  /* Open the source file and generate a cache key for it */
@@ -840,7 +951,26 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
840
951
  } else {
841
952
  /* True if the cache existed and no invalidating changes have occurred since
842
953
  * it was generated. */
843
- valid_cache = cache_key_equal(&current_key, &cached_key);
954
+ switch(cache_key_equal_fast_path(&current_key, &cached_key)) {
955
+ case hit:
956
+ valid_cache = true;
957
+ break;
958
+ case miss:
959
+ valid_cache = false;
960
+ break;
961
+ case stale:
962
+ valid_cache = false;
963
+ if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
964
+ goto fail;
965
+ }
966
+ valid_cache = cache_key_equal_slow_path(&current_key, &cached_key, input_data);
967
+ if (valid_cache) {
968
+ if (update_cache_key(&current_key, cache_fd, &errno_provenance)) {
969
+ goto fail;
970
+ }
971
+ }
972
+ break;
973
+ };
844
974
  }
845
975
 
846
976
  if (valid_cache) {
@@ -867,6 +997,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
867
997
  if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
868
998
 
869
999
  /* Write the cache key and storage_data to the cache directory */
1000
+ bs_cache_key_digest(&current_key, input_data);
870
1001
  res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
871
1002
  if (res < 0) goto fail;
872
1003
 
@@ -3,21 +3,28 @@
3
3
  require "mkmf"
4
4
 
5
5
  if %w[ruby truffleruby].include?(RUBY_ENGINE)
6
- $CFLAGS << " -O3 "
7
- $CFLAGS << " -std=c99"
6
+ have_func "fdatasync", "fcntl.h"
7
+
8
+ unless RUBY_PLATFORM.match?(/mswin|mingw|cygwin/)
9
+ append_cppflags ["_GNU_SOURCE"] # Needed of O_NOATIME
10
+ end
11
+
12
+ append_cflags ["-O3", "-std=c99"]
8
13
 
9
14
  # ruby.h has some -Wpedantic fails in some cases
10
15
  # (e.g. https://github.com/Shopify/bootsnap/issues/15)
11
16
  unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
12
- $CFLAGS << " -Wall"
13
- $CFLAGS << " -Werror"
14
- $CFLAGS << " -Wextra"
15
- $CFLAGS << " -Wpedantic"
17
+ append_cflags([
18
+ "-Wall",
19
+ "-Werror",
20
+ "-Wextra",
21
+ "-Wpedantic",
16
22
 
17
- $CFLAGS << " -Wno-unused-parameter" # VALUE self has to be there but we don't care what it is.
18
- $CFLAGS << " -Wno-keyword-macro" # hiding return
19
- $CFLAGS << " -Wno-gcc-compat" # ruby.h 2.6.0 on macos 10.14, dunno
20
- $CFLAGS << " -Wno-compound-token-split-by-macro"
23
+ "-Wno-unused-parameter", # VALUE self has to be there but we don't care what it is.
24
+ "-Wno-keyword-macro", # hiding return
25
+ "-Wno-gcc-compat", # ruby.h 2.6.0 on macos 10.14, dunno
26
+ "-Wno-compound-token-split-by-macro",
27
+ ])
21
28
  end
22
29
 
23
30
  create_makefile("bootsnap/bootsnap")
data/lib/bootsnap/cli.rb CHANGED
@@ -60,14 +60,16 @@ module Bootsnap
60
60
  precompile_json_files(main_sources)
61
61
 
62
62
  if compile_gemfile
63
- # Some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
64
- gem_exclude = Regexp.union([exclude, "/spec/", "/test/"].compact)
65
- precompile_ruby_files($LOAD_PATH.map { |d| File.expand_path(d) }, exclude: gem_exclude)
66
-
67
63
  # Gems that include JSON or YAML files usually don't put them in `lib/`.
68
64
  # So we look at the gem root.
65
+ # Similarly, gems that include Rails engines generally file Ruby files in `app/`.
66
+ # However some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
67
+ gem_exclude = Regexp.union([exclude, "/spec/", "/test/", "/features/"].compact)
68
+
69
69
  gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems/[^/]+}
70
- gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] }.compact.uniq
70
+ gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] || p }.uniq
71
+
72
+ precompile_ruby_files(gem_paths, exclude: gem_exclude)
71
73
  precompile_yaml_files(gem_paths, exclude: gem_exclude)
72
74
  precompile_json_files(gem_paths, exclude: gem_exclude)
73
75
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bootsnap
4
- VERSION = "1.17.1"
4
+ VERSION = "1.18.1"
5
5
  end
data/lib/bootsnap.rb CHANGED
@@ -11,6 +11,16 @@ module Bootsnap
11
11
  class << self
12
12
  attr_reader :logger
13
13
 
14
+ def log_stats!
15
+ stats = {hit: 0, revalidated: 0, miss: 0, stale: 0}
16
+ self.instrumentation = ->(event, _path) { stats[event] += 1 }
17
+ Kernel.at_exit do
18
+ stats.each do |event, count|
19
+ $stderr.puts "bootsnap #{event}: #{count}"
20
+ end
21
+ end
22
+ end
23
+
14
24
  def log!
15
25
  self.logger = $stderr.method(:puts)
16
26
  end
@@ -18,9 +28,9 @@ module Bootsnap
18
28
  def logger=(logger)
19
29
  @logger = logger
20
30
  self.instrumentation = if logger.respond_to?(:debug)
21
- ->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") }
31
+ ->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") unless event == :hit }
22
32
  else
23
- ->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") }
33
+ ->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") unless event == :hit }
24
34
  end
25
35
  end
26
36
 
@@ -110,6 +120,8 @@ module Bootsnap
110
120
 
111
121
  if ENV["BOOTSNAP_LOG"]
112
122
  log!
123
+ elsif ENV["BOOTSNAP_STATS"]
124
+ log_stats!
113
125
  end
114
126
  end
115
127
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.17.1
4
+ version: 1.18.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-12 00:00:00.000000000 Z
11
+ date: 2024-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
83
  - !ruby/object:Gem::Version
84
84
  version: '0'
85
85
  requirements: []
86
- rubygems_version: 3.5.4
86
+ rubygems_version: 3.5.5
87
87
  signing_key:
88
88
  specification_version: 4
89
89
  summary: Boot large ruby/rails apps faster