1 /* Copyright 2012-present Facebook, Inc.
2 * Licensed under the Apache License, Version 2.0 */
3 #include "InMemoryView.h"
4 #include <algorithm>
5 #include <thread>
6 #include "ThreadPool.h"
7 #include "make_unique.h"
8 #include "watchman.h"
9 #include "watchman_scopeguard.h"
10
11 // Each root gets a number that uniquely identifies it within the process. This
12 // helps avoid confusion if a root is removed and then added again.
13 static std::atomic<long> next_root_number{1};
14
15 namespace watchman {
16
InMemoryFileResult(const watchman_file * file,ContentHashCache & contentHashCache)17 InMemoryFileResult::InMemoryFileResult(
18 const watchman_file* file,
19 ContentHashCache& contentHashCache)
20 : file_(file), contentHashCache_(contentHashCache) {}
21
stat() const22 const FileInformation& InMemoryFileResult::stat() const {
23 return file_->stat;
24 }
25
baseName() const26 w_string_piece InMemoryFileResult::baseName() const {
27 return file_->getName();
28 }
29
dirName()30 w_string_piece InMemoryFileResult::dirName() {
31 if (!dirName_) {
32 dirName_ = file_->parent->getFullPath();
33 }
34 return dirName_;
35 }
36
exists() const37 bool InMemoryFileResult::exists() const {
38 return file_->exists;
39 }
40
ctime() const41 const w_clock_t& InMemoryFileResult::ctime() const {
42 return file_->ctime;
43 }
44
otime() const45 const w_clock_t& InMemoryFileResult::otime() const {
46 return file_->otime;
47 }
48
readLink() const49 Future<w_string> InMemoryFileResult::readLink() const {
50 return makeFuture(file_->symlink_target);
51 }
52
getContentSha1()53 Future<FileResult::ContentHash> InMemoryFileResult::getContentSha1() {
54 auto dir = dirName();
55 dir.advance(contentHashCache_.rootPath().size());
56
57 // If dirName is the root, dir.size() will now be zero
58 if (dir.size() > 0) {
59 // if not at the root, skip the slash character at the
60 // front of dir
61 dir.advance(1);
62 }
63
64 ContentHashCacheKey key{w_string::pathCat({dir, baseName()}),
65 size_t(file_->stat.size),
66 file_->stat.mtime};
67
68 return contentHashCache_.get(key).then(
69 [](Result<std::shared_ptr<const ContentHashCache::Node>>&& result)
70 -> FileResult::ContentHash { return result.value()->value(); });
71 }
72
view(const w_string & root_path)73 InMemoryView::view::view(const w_string& root_path)
74 : root_dir(watchman::make_unique<watchman_dir>(root_path, nullptr)),
75 rootNumber(next_root_number++) {}
76
InMemoryView(w_root_t * root,std::shared_ptr<Watcher> watcher)77 InMemoryView::InMemoryView(w_root_t* root, std::shared_ptr<Watcher> watcher)
78 : cookies_(root->cookies),
79 config_(root->config),
80 view_(view(root->root_path)),
81 root_path(root->root_path),
82 watcher_(watcher),
83 contentHashCache_(
84 root->root_path,
85 config_.getInt("content_hash_max_items", 128 * 1024),
86 std::chrono::milliseconds(
87 config_.getInt("content_hash_negative_cache_ttl_ms", 2000))),
88 enableContentCacheWarming_(
89 config_.getBool("content_hash_warming", false)),
90 maxFilesToWarmInContentCache_(
91 size_t(config_.getInt("content_hash_max_warm_per_settle", 1024))),
92 syncContentCacheWarming_(
93 config_.getBool("content_hash_warm_wait_before_settle", false)),
94 scm_(SCM::scmForPath(root->root_path)) {}
95
insertAtHeadOfFileList(struct watchman_file * file)96 void InMemoryView::view::insertAtHeadOfFileList(struct watchman_file* file) {
97 file->next = latest_file;
98 if (file->next) {
99 file->next->prev = &file->next;
100 }
101 latest_file = file;
102 file->prev = &latest_file;
103 }
104
markFileChanged(SyncView::LockedPtr & view,watchman_file * file,const struct timeval & now)105 void InMemoryView::markFileChanged(
106 SyncView::LockedPtr& view,
107 watchman_file* file,
108 const struct timeval& now) {
109 if (file->exists) {
110 watcher_->startWatchFile(file);
111 }
112
113 file->otime.timestamp = now.tv_sec;
114 file->otime.ticks = view->mostRecentTick;
115
116 if (view->latest_file != file) {
117 // unlink from list
118 file->removeFromFileList();
119
120 // and move to the head
121 view->insertAtHeadOfFileList(file);
122 }
123 }
124
resolveDir(SyncView::ConstLockedPtr & view,const w_string & dir_name) const125 const watchman_dir* InMemoryView::resolveDir(
126 SyncView::ConstLockedPtr& view,
127 const w_string& dir_name) const {
128 watchman_dir* dir;
129 const char* dir_component;
130 const char* dir_end;
131
132 if (dir_name == root_path) {
133 return view->root_dir.get();
134 }
135
136 dir_component = dir_name.data();
137 dir_end = dir_component + dir_name.size();
138
139 dir = view->root_dir.get();
140 dir_component += root_path.size() + 1; // Skip root path prefix
141
142 w_assert(dir_component <= dir_end, "impossible file name");
143
144 while (true) {
145 w_string_t component;
146 auto sep = (const char*)memchr(dir_component, '/', dir_end - dir_component);
147 // Note: if sep is NULL it means that we're looking at the basename
148 // component of the input directory name, which is the terminal
149 // iteration of this search.
150
151 w_string_new_len_typed_stack(
152 &component,
153 dir_component,
154 sep ? (uint32_t)(sep - dir_component)
155 : (uint32_t)(dir_end - dir_component),
156 W_STRING_BYTE);
157
158 auto child = dir->getChildDir(&component);
159 if (!child) {
160 return nullptr;
161 }
162
163 dir = child;
164
165 if (!sep) {
166 // We reached the end of the string
167 if (dir) {
168 // We found the dir
169 return dir;
170 }
171 // Does not exist
172 return nullptr;
173 }
174
175 // Skip to the next component for the next iteration
176 dir_component = sep + 1;
177 }
178
179 return nullptr;
180 }
181
resolveDir(SyncView::LockedPtr & view,const w_string & dir_name,bool create)182 watchman_dir* InMemoryView::resolveDir(
183 SyncView::LockedPtr& view,
184 const w_string& dir_name,
185 bool create) {
186 watchman_dir *dir, *parent;
187 const char* dir_component;
188 const char* dir_end;
189
190 if (dir_name == root_path) {
191 return view->root_dir.get();
192 }
193
194 dir_component = dir_name.data();
195 dir_end = dir_component + dir_name.size();
196
197 dir = view->root_dir.get();
198 dir_component += root_path.size() + 1; // Skip root path prefix
199
200 w_assert(dir_component <= dir_end, "impossible file name");
201
202 while (true) {
203 w_string_t component;
204 auto sep = (const char*)memchr(dir_component, '/', dir_end - dir_component);
205 // Note: if sep is NULL it means that we're looking at the basename
206 // component of the input directory name, which is the terminal
207 // iteration of this search.
208
209 w_string_new_len_typed_stack(
210 &component,
211 dir_component,
212 sep ? (uint32_t)(sep - dir_component)
213 : (uint32_t)(dir_end - dir_component),
214 W_STRING_BYTE);
215
216 auto child = dir->getChildDir(&component);
217
218 if (!child && !create) {
219 return nullptr;
220 }
221 if (!child && sep && create) {
222 // A component in the middle wasn't present. Since we're in create
223 // mode, we know that the leaf must exist. The assumption is that
224 // we have another pending item for the parent. We'll create the
225 // parent dir now and our other machinery will populate its contents
226 // later.
227 w_string child_name(dir_component, (uint32_t)(sep - dir_component));
228
229 auto& new_child = dir->dirs[child_name];
230 new_child.reset(new watchman_dir(child_name, dir));
231
232 child = new_child.get();
233 }
234
235 parent = dir;
236 dir = child;
237
238 if (!sep) {
239 // We reached the end of the string
240 if (dir) {
241 // We found the dir
242 return dir;
243 }
244 // We need to create the dir
245 break;
246 }
247
248 // Skip to the next component for the next iteration
249 dir_component = sep + 1;
250 }
251
252 w_string child_name(dir_component, (uint32_t)(dir_end - dir_component));
253 auto& new_child = parent->dirs[child_name];
254 new_child.reset(new watchman_dir(child_name, parent));
255
256 dir = new_child.get();
257
258 return dir;
259 }
260
markDirDeleted(SyncView::LockedPtr & view,struct watchman_dir * dir,const struct timeval & now,bool recursive)261 void InMemoryView::markDirDeleted(
262 SyncView::LockedPtr& view,
263 struct watchman_dir* dir,
264 const struct timeval& now,
265 bool recursive) {
266 if (!dir->last_check_existed) {
267 // If we know that it doesn't exist, return early
268 return;
269 }
270 dir->last_check_existed = false;
271
272 for (auto& it : dir->files) {
273 auto file = it.second.get();
274
275 if (file->exists) {
276 auto full_name = w_dir_path_cat_str(dir, file->getName());
277 w_log(W_LOG_DBG, "mark_deleted: %s\n", full_name.c_str());
278 file->exists = false;
279 markFileChanged(view, file, now);
280 }
281 }
282
283 if (recursive) {
284 for (auto& it : dir->dirs) {
285 auto child = it.second.get();
286
287 markDirDeleted(view, child, now, true);
288 }
289 }
290 }
291
getOrCreateChildFile(SyncView::LockedPtr & view,watchman_dir * dir,const w_string & file_name,const struct timeval & now)292 watchman_file* InMemoryView::getOrCreateChildFile(
293 SyncView::LockedPtr& view,
294 watchman_dir* dir,
295 const w_string& file_name,
296 const struct timeval& now) {
297 // file_name is typically a baseName slice; let's use it as-is
298 // to look up a child...
299 auto it = dir->files.find(file_name);
300 if (it != dir->files.end()) {
301 return it->second.get();
302 }
303
304 // ... but take the shorter string from inside the file that
305 // we create as the key.
306 auto file = watchman_file::make(file_name, dir);
307 auto& file_ptr = dir->files[file->getName()];
308 file_ptr = std::move(file);
309
310 file_ptr->ctime.ticks = view->mostRecentTick;
311 file_ptr->ctime.timestamp = now.tv_sec;
312
313 auto suffix = file_name.suffix();
314 if (suffix) {
315 auto& sufhead = view->suffixes[suffix];
316 if (!sufhead) {
317 // Create the list head if we don't already have one for this suffix.
318 sufhead.reset(new watchman::InMemoryView::file_list_head);
319 }
320
321 file_ptr->suffix_next = sufhead->head;
322 if (file_ptr->suffix_next) {
323 sufhead->head->suffix_prev = &file_ptr->suffix_next;
324 }
325 sufhead->head = file_ptr.get();
326 file_ptr->suffix_prev = &sufhead->head;
327 }
328
329 watcher_->startWatchFile(file_ptr.get());
330
331 return file_ptr.get();
332 }
333
ageOutFile(std::unordered_set<w_string> & dirs_to_erase,watchman_file * file)334 void InMemoryView::ageOutFile(
335 std::unordered_set<w_string>& dirs_to_erase,
336 watchman_file* file) {
337 auto parent = file->parent;
338
339 auto full_name = w_dir_path_cat_str(parent, file->getName());
340 w_log(W_LOG_DBG, "age_out file=%s\n", full_name.c_str());
341
342 // Revise tick for fresh instance reporting
343 last_age_out_tick = std::max(last_age_out_tick, file->otime.ticks);
344
345 // If we have a corresponding dir, we want to arrange to remove it, but only
346 // after we have unlinked all of the associated file nodes.
347 dirs_to_erase.insert(full_name);
348
349 // Remove the entry from the containing file hash; this will free it.
350 // We don't need to stop watching it, because we already stopped watching it
351 // when we marked it as !exists.
352 parent->files.erase(file->getName());
353 }
354
ageOut(w_perf_t & sample,std::chrono::seconds minAge)355 void InMemoryView::ageOut(w_perf_t& sample, std::chrono::seconds minAge) {
356 struct watchman_file *file, *prior;
357 time_t now;
358 uint32_t num_aged_files = 0;
359 uint32_t num_walked = 0;
360 std::unordered_set<w_string> dirs_to_erase;
361
362 time(&now);
363 last_age_out_timestamp = now;
364 auto view = view_.wlock();
365
366 file = view->latest_file;
367 prior = nullptr;
368 while (file) {
369 ++num_walked;
370 if (file->exists || file->otime.timestamp + minAge.count() > now) {
371 prior = file;
372 file = file->next;
373 continue;
374 }
375
376 ageOutFile(dirs_to_erase, file);
377 num_aged_files++;
378
379 // Go back to last good file node; we can't trust that the
380 // value of file->next saved before age_out_file is a valid
381 // file node as anything past that point may have also been
382 // aged out along with it.
383 file = prior;
384 }
385
386 for (auto& name : dirs_to_erase) {
387 auto parent = resolveDir(view, name.dirName(), false);
388 if (parent) {
389 parent->dirs.erase(name.baseName());
390 }
391 }
392
393 if (num_aged_files + dirs_to_erase.size()) {
394 w_log(
395 W_LOG_ERR,
396 "aged %" PRIu32 " files, %" PRIu32 " dirs\n",
397 num_aged_files,
398 uint32_t(dirs_to_erase.size()));
399 }
400 sample.add_meta(
401 "age_out",
402 json_object({{"walked", json_integer(num_walked)},
403 {"files", json_integer(num_aged_files)},
404 {"dirs", json_integer(dirs_to_erase.size())}}));
405 }
406
timeGenerator(w_query * query,struct w_query_ctx * ctx) const407 void InMemoryView::timeGenerator(w_query* query, struct w_query_ctx* ctx)
408 const {
409 struct watchman_file* f;
410
411 // Walk back in time until we hit the boundary
412 auto view = view_.rlock();
413 for (f = view->latest_file; f; f = f->next) {
414 ctx->bumpNumWalked();
415 // Note that we use <= for the time comparisons in here so that we
416 // report the things that changed inclusive of the boundary presented.
417 // This is especially important for clients using the coarse unix
418 // timestamp as the since basis, as they would be much more
419 // likely to miss out on changes if we didn't.
420 if (ctx->since.is_timestamp && f->otime.timestamp <= ctx->since.timestamp) {
421 break;
422 }
423 if (!ctx->since.is_timestamp && f->otime.ticks <= ctx->since.clock.ticks) {
424 break;
425 }
426
427 if (!w_query_file_matches_relative_root(ctx, f)) {
428 continue;
429 }
430
431 w_query_process_file(
432 query,
433 ctx,
434 watchman::make_unique<InMemoryFileResult>(f, contentHashCache_));
435 }
436 }
437
suffixGenerator(w_query * query,struct w_query_ctx * ctx) const438 void InMemoryView::suffixGenerator(w_query* query, struct w_query_ctx* ctx)
439 const {
440 struct watchman_file* f;
441
442 auto view = view_.rlock();
443 for (const auto& suff : query->suffixes) {
444 // Head of suffix index for this suffix
445 auto it = view->suffixes.find(suff);
446 if (it == view->suffixes.end()) {
447 continue;
448 }
449
450 // Walk and process
451 for (f = it->second->head; f; f = f->suffix_next) {
452 ctx->bumpNumWalked();
453 if (!w_query_file_matches_relative_root(ctx, f)) {
454 continue;
455 }
456
457 w_query_process_file(
458 query,
459 ctx,
460 watchman::make_unique<InMemoryFileResult>(f, contentHashCache_));
461 }
462 }
463 }
464
pathGenerator(w_query * query,struct w_query_ctx * ctx) const465 void InMemoryView::pathGenerator(w_query* query, struct w_query_ctx* ctx)
466 const {
467 w_string_t* relative_root;
468 struct watchman_file* f;
469
470 if (query->relative_root) {
471 relative_root = query->relative_root;
472 } else {
473 relative_root = root_path;
474 }
475
476 auto view = view_.rlock();
477
478 for (const auto& path : query->paths) {
479 const watchman_dir* dir;
480 w_string_t* file_name;
481 w_string dir_name;
482
483 // Compose path with root
484 auto full_name = w_string::pathCat({relative_root, path.name});
485
486 // special case of root dir itself
487 if (w_string_equal(root_path, full_name)) {
488 // dirname on the root is outside the root, which is useless
489 dir = resolveDir(view, full_name);
490 goto is_dir;
491 }
492
493 // Ideally, we'd just resolve it directly as a dir and be done.
494 // It's not quite so simple though, because we may resolve a dir
495 // that had been deleted and replaced by a file.
496 // We prefer to resolve the parent and walk down.
497 dir_name = full_name.dirName();
498 if (!dir_name) {
499 continue;
500 }
501
502 dir = resolveDir(view, dir_name);
503
504 if (!dir) {
505 // Doesn't exist, and never has
506 continue;
507 }
508
509 if (!dir->files.empty()) {
510 file_name = w_string_basename(path.name);
511 f = dir->getChildFile(file_name);
512 w_string_delref(file_name);
513
514 // If it's a file (but not an existent dir)
515 if (f && (!f->exists || !f->stat.isDir())) {
516 ctx->bumpNumWalked();
517 w_query_process_file(
518 query,
519 ctx,
520 watchman::make_unique<InMemoryFileResult>(f, contentHashCache_));
521 continue;
522 }
523 }
524
525 // Is it a dir?
526 if (dir->dirs.empty()) {
527 continue;
528 }
529
530 dir = dir->getChildDir(full_name.baseName());
531 is_dir:
532 // We got a dir; process recursively to specified depth
533 if (dir) {
534 dirGenerator(query, ctx, dir, path.depth);
535 }
536 }
537 }
538
dirGenerator(w_query * query,struct w_query_ctx * ctx,const watchman_dir * dir,uint32_t depth) const539 void InMemoryView::dirGenerator(
540 w_query* query,
541 struct w_query_ctx* ctx,
542 const watchman_dir* dir,
543 uint32_t depth) const {
544 for (auto& it : dir->files) {
545 auto file = it.second.get();
546 ctx->bumpNumWalked();
547
548 w_query_process_file(
549 query,
550 ctx,
551 watchman::make_unique<InMemoryFileResult>(file, contentHashCache_));
552 }
553
554 if (depth > 0) {
555 for (auto& it : dir->dirs) {
556 const auto child = it.second.get();
557
558 dirGenerator(query, ctx, child, depth - 1);
559 }
560 }
561 }
562
allFilesGenerator(w_query * query,struct w_query_ctx * ctx) const563 void InMemoryView::allFilesGenerator(w_query* query, struct w_query_ctx* ctx)
564 const {
565 struct watchman_file* f;
566 auto view = view_.rlock();
567
568 for (f = view->latest_file; f; f = f->next) {
569 ctx->bumpNumWalked();
570 if (!w_query_file_matches_relative_root(ctx, f)) {
571 continue;
572 }
573
574 w_query_process_file(
575 query,
576 ctx,
577 watchman::make_unique<InMemoryFileResult>(f, contentHashCache_));
578 }
579 }
580
getMostRecentRootNumberAndTickValue() const581 ClockPosition InMemoryView::getMostRecentRootNumberAndTickValue() const {
582 auto view = view_.rlock();
583 return ClockPosition(view->rootNumber, view->mostRecentTick);
584 }
585
getCurrentClockString() const586 w_string InMemoryView::getCurrentClockString() const {
587 auto view = view_.rlock();
588 char clockbuf[128];
589 if (!clock_id_string(
590 view->rootNumber, view->mostRecentTick, clockbuf, sizeof(clockbuf))) {
591 throw std::runtime_error("clock string exceeded clockbuf size");
592 }
593 return w_string(clockbuf, W_STRING_UNICODE);
594 }
595
getLastAgeOutTickValue() const596 uint32_t InMemoryView::getLastAgeOutTickValue() const {
597 return last_age_out_tick;
598 }
599
getLastAgeOutTimeStamp() const600 time_t InMemoryView::getLastAgeOutTimeStamp() const {
601 return last_age_out_timestamp;
602 }
603
startThreads(const std::shared_ptr<w_root_t> & root)604 void InMemoryView::startThreads(const std::shared_ptr<w_root_t>& root) {
605 // Start a thread to call into the watcher API for filesystem notifications
606 auto self = std::static_pointer_cast<InMemoryView>(shared_from_this());
607 w_log(W_LOG_DBG, "starting threads for %p %s\n", this, root_path.c_str());
608 std::thread notifyThreadInstance([self, root]() {
609 w_set_thread_name("notify %p %s", self.get(), self->root_path.c_str());
610 try {
611 self->notifyThread(root);
612 } catch (const std::exception& e) {
613 watchman::log(watchman::ERR, "Exception: ", e.what(), " cancel root\n");
614 root->cancel();
615 }
616 watchman::log(watchman::DBG, "out of loop\n");
617 });
618 notifyThreadInstance.detach();
619
620 // Wait for it to signal that the watcher has been initialized
621 bool pinged = false;
622 pending_.lockAndWait(std::chrono::milliseconds(-1) /* infinite */, pinged);
623
624 // And now start the IO thread
625 std::thread ioThreadInstance([self, root]() {
626 w_set_thread_name("io %p %s", self.get(), self->root_path.c_str());
627 try {
628 self->ioThread(root);
629 } catch (const std::exception& e) {
630 watchman::log(watchman::ERR, "Exception: ", e.what(), " cancel root\n");
631 root->cancel();
632 }
633 watchman::log(watchman::DBG, "out of loop\n");
634 });
635 ioThreadInstance.detach();
636 }
637
signalThreads()638 void InMemoryView::signalThreads() {
639 w_log(W_LOG_DBG, "signalThreads! %p %s\n", this, root_path.c_str());
640 stopThreads_ = true;
641 watcher_->signalThreads();
642 pending_.ping();
643 }
644
wakeThreads()645 void InMemoryView::wakeThreads() {
646 pending_.ping();
647 }
648
doAnyOfTheseFilesExist(const std::vector<w_string> & fileNames) const649 bool InMemoryView::doAnyOfTheseFilesExist(
650 const std::vector<w_string>& fileNames) const {
651 auto view = view_.rlock();
652 for (auto& name : fileNames) {
653 auto fullName = w_string::pathCat({root_path, name});
654 const auto dir = resolveDir(view, fullName.dirName());
655 if (!dir) {
656 continue;
657 }
658
659 auto file = dir->getChildFile(fullName.baseName());
660 if (!file) {
661 continue;
662 }
663 if (file->exists) {
664 return true;
665 }
666 }
667 return false;
668 }
669
getWatcher() const670 const std::shared_ptr<Watcher>& InMemoryView::getWatcher() const {
671 return watcher_;
672 }
673
getName() const674 const w_string& InMemoryView::getName() const {
675 return watcher_->name;
676 }
677
getSCM() const678 SCM* InMemoryView::getSCM() const {
679 return scm_.get();
680 }
681
warmContentCache()682 void InMemoryView::warmContentCache() {
683 if (!enableContentCacheWarming_) {
684 return;
685 }
686
687 watchman::log(
688 watchman::DBG, "considering files for content hash cache warming\n");
689
690 size_t n = 0;
691 std::deque<Future<std::shared_ptr<const ContentHashCache::Node>>> futures;
692
693 {
694 // Walk back in time until we hit the boundary, or hit the limit
695 // on the number of files we should warm up.
696 auto view = view_.rlock();
697 struct watchman_file* f;
698 for (f = view->latest_file; f && n < maxFilesToWarmInContentCache_;
699 f = f->next) {
700 if (f->otime.ticks <= lastWarmedTick_) {
701 watchman::log(
702 watchman::DBG,
703 "warmContentCache: stop because file ticks ",
704 f->otime.ticks,
705 " is <= lastWarmedTick_ ",
706 lastWarmedTick_,
707 "\n");
708 break;
709 }
710
711 if (f->exists && f->stat.isFile()) {
712 // Note: we could also add an expression to further constrain
713 // the things we warm up here. Let's see if we need it before
714 // going ahead and adding.
715
716 auto dirStr = f->parent->getFullPath();
717 w_string_piece dir(dirStr);
718 dir.advance(contentHashCache_.rootPath().size());
719
720 // If dirName is the root, dir.size() will now be zero
721 if (dir.size() > 0) {
722 // if not at the root, skip the slash character at the
723 // front of dir
724 dir.advance(1);
725 }
726 ContentHashCacheKey key{w_string::pathCat({dir, f->getName()}),
727 size_t(f->stat.size),
728 f->stat.mtime};
729
730 watchman::log(
731 watchman::DBG, "warmContentCache: lookup ", key.relativePath, "\n");
732 auto f = contentHashCache_.get(key);
733 if (syncContentCacheWarming_) {
734 futures.emplace_back(std::move(f));
735 }
736 ++n;
737 }
738 }
739
740 lastWarmedTick_ = view->mostRecentTick;
741 }
742
743 watchman::log(
744 watchman::DBG,
745 "warmContentCache, lastWarmedTick_ now ",
746 lastWarmedTick_,
747 " scheduled ",
748 n,
749 " files for hashing, will wait for ",
750 futures.size(),
751 " lookups to finish\n");
752
753 if (syncContentCacheWarming_) {
754 // Wait for them to finish, but don't use get() because we don't
755 // care about any errors that may have occurred.
756 collectAll(futures.begin(), futures.end()).wait();
757 watchman::log(watchman::DBG, "warmContentCache: hashing complete\n");
758 }
759 }
760
debugContentHashCache(struct watchman_client * client,const json_ref & args)761 void InMemoryView::debugContentHashCache(
762 struct watchman_client* client,
763 const json_ref& args) {
764 /* resolve the root */
765 if (json_array_size(args) != 2) {
766 send_error_response(
767 client, "wrong number of arguments for 'debug-contenthash'");
768 return;
769 }
770
771 auto root = resolve_root_or_err(client, args, 1, false);
772 if (!root) {
773 return;
774 }
775 auto view = std::dynamic_pointer_cast<watchman::InMemoryView>(root->view());
776 if (!view) {
777 send_error_response(client, "root is not an InMemoryView watcher");
778 return;
779 }
780
781 auto stats = view->contentHashCache_.stats();
782 auto resp = make_response();
783 resp.set({{"cacheHit", json_integer(stats.cacheHit)},
784 {"cacheShare", json_integer(stats.cacheShare)},
785 {"cacheMiss", json_integer(stats.cacheMiss)},
786 {"cacheEvict", json_integer(stats.cacheEvict)},
787 {"cacheStore", json_integer(stats.cacheStore)},
788 {"cacheLoad", json_integer(stats.cacheLoad)},
789 {"cacheErase", json_integer(stats.cacheErase)},
790 {"clearCount", json_integer(stats.clearCount)},
791 {"size", json_integer(stats.size)}});
792 send_and_dispose_response(client, std::move(resp));
793 }
794 W_CMD_REG(
795 "debug-contenthash",
796 InMemoryView::debugContentHashCache,
797 CMD_DAEMON,
798 w_cmd_realpath_root);
799 }
800