watchman/cmds/debug.cpp (274 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include <folly/chrono/Conv.h> #include <iomanip> #include "watchman/Client.h" #include "watchman/InMemoryView.h" #include "watchman/LRUCache.h" #include "watchman/Logging.h" #include "watchman/Poison.h" #include "watchman/QueryableView.h" #include "watchman/root/Root.h" #include "watchman/watchman_cmd.h" using namespace watchman; static void cmd_debug_recrawl(Client* client, const json_ref& args) { /* resolve the root */ if (json_array_size(args) != 2) { client->sendErrorResponse("wrong number of arguments for 'debug-recrawl'"); return; } auto root = resolveRoot(client, args); auto resp = make_response(); root->scheduleRecrawl("debug-recrawl"); resp.set("recrawl", json_true()); client->enqueueResponse(std::move(resp)); } W_CMD_REG("debug-recrawl", cmd_debug_recrawl, CMD_DAEMON, w_cmd_realpath_root) static void cmd_debug_show_cursors(Client* client, const json_ref& args) { json_ref cursors; /* resolve the root */ if (json_array_size(args) != 2) { client->sendErrorResponse( "wrong number of arguments for 'debug-show-cursors'"); return; } auto root = resolveRoot(client, args); auto resp = make_response(); { auto map = root->inner.cursors.rlock(); cursors = json_object_of_size(map->size()); for (const auto& it : *map) { const auto& name = it.first; const auto& ticks = it.second; cursors.set(name.c_str(), json_integer(ticks)); } } resp.set("cursors", std::move(cursors)); client->enqueueResponse(std::move(resp)); } W_CMD_REG( "debug-show-cursors", cmd_debug_show_cursors, CMD_DAEMON, w_cmd_realpath_root) /* debug-ageout */ static void cmd_debug_ageout(Client* client, const json_ref& args) { /* resolve the root */ if (json_array_size(args) != 3) { client->sendErrorResponse("wrong number of arguments for 'debug-ageout'"); return; } auto root = resolveRoot(client, args); std::chrono::seconds min_age(json_array_get(args, 2).asInt()); auto resp = make_response(); root->performAgeOut(min_age); resp.set("ageout", json_true()); client->enqueueResponse(std::move(resp)); } W_CMD_REG("debug-ageout", cmd_debug_ageout, CMD_DAEMON, w_cmd_realpath_root) static void cmd_debug_poison(Client* client, const json_ref& args) { auto root = resolveRoot(client, args); auto now = std::chrono::system_clock::now(); set_poison_state( root->root_path, now, "debug-poison", std::error_code(ENOMEM, std::generic_category())); auto resp = make_response(); resp.set( "poison", typed_string_to_json(poisoned_reason.rlock()->c_str(), W_STRING_UNICODE)); client->enqueueResponse(std::move(resp)); } W_CMD_REG("debug-poison", cmd_debug_poison, CMD_DAEMON, w_cmd_realpath_root) static void cmd_debug_drop_privs(Client* client, const json_ref&) { client->client_is_owner = false; auto resp = make_response(); resp.set("owner", json_boolean(client->client_is_owner)); client->enqueueResponse(std::move(resp)); } W_CMD_REG("debug-drop-privs", cmd_debug_drop_privs, CMD_DAEMON, NULL) static void cmd_debug_set_subscriptions_paused( Client* clientbase, const json_ref& args) { auto client = (UserClient*)clientbase; const auto& paused = args.at(1); auto& paused_map = paused.object(); for (auto& it : paused_map) { auto sub_iter = client->subscriptions.find(it.first); if (sub_iter == client->subscriptions.end()) { client->sendErrorResponse( "this client does not have a subscription named '{}'", it.first); return; } if (!it.second.isBool()) { client->sendErrorResponse( "new value for subscription '{}' not a boolean", it.first); return; } } auto states = json_object(); for (auto& it : paused_map) { auto sub_iter = client->subscriptions.find(it.first); bool old_paused = sub_iter->second->debug_paused; bool new_paused = it.second.asBool(); sub_iter->second->debug_paused = new_paused; states.set( it.first, json_object({{"old", json_boolean(old_paused)}, {"new", it.second}})); } auto resp = make_response(); resp.set("paused", std::move(states)); clientbase->enqueueResponse(std::move(resp)); } W_CMD_REG( "debug-set-subscriptions-paused", cmd_debug_set_subscriptions_paused, CMD_DAEMON, nullptr) static json_ref getDebugSubscriptionInfo(Root* root) { auto subscriptions = json_array(); for (const auto& user_client : UserClient::getAllClients()) { for (const auto& sub : user_client->subscriptions) { if (root == sub.second->root.get()) { auto last_responses = json_array(); for (auto& response : sub.second->lastResponses) { char timebuf[64]; last_responses.array().push_back(json_object({ {"written_time", typed_string_to_json(Log::timeString( timebuf, std::size(timebuf), folly::to<timeval>(response.written)))}, {"response", response.response}, })); } subscriptions.array().push_back(json_object({ {"name", w_string_to_json(sub.first)}, {"client_id", json_integer(user_client->unique_id)}, {"last_responses", last_responses}, })); } } } return subscriptions; } static void cmd_debug_get_subscriptions( Client* clientbase, const json_ref& args) { auto client = (UserClient*)clientbase; auto root = resolveRoot(client, args); auto resp = make_response(); auto debug_info = root->unilateralResponses->getDebugInfo(); // copy over all the key-value pairs from debug_info resp.object().insert(debug_info.object().begin(), debug_info.object().end()); auto subscriptions = getDebugSubscriptionInfo(root.get()); resp.object().emplace("subscriptions", subscriptions); clientbase->enqueueResponse(std::move(resp)); } W_CMD_REG( "debug-get-subscriptions", cmd_debug_get_subscriptions, CMD_DAEMON, w_cmd_realpath_root) static void cmd_debug_get_asserted_states( Client* clientbase, const json_ref& args) { auto client = (UserClient*)clientbase; auto root = resolveRoot(client, args); auto response = make_response(); // copy over all the key-value pairs to stateSet and release lock auto states = root->assertedStates.rlock()->debugStates(); response.set( {{"root", w_string_to_json(root->root_path)}, {"states", std::move(states)}}); clientbase->enqueueResponse(std::move(response)); } W_CMD_REG( "debug-get-asserted-states", cmd_debug_get_asserted_states, CMD_DAEMON, w_cmd_realpath_root) static void cmd_debug_status(Client* client, const json_ref&) { auto resp = make_response(); auto roots = Root::getStatusForAllRoots(); resp.set("roots", std::move(roots)); client->enqueueResponse(std::move(resp)); } W_CMD_REG( "debug-status", cmd_debug_status, CMD_DAEMON | CMD_ALLOW_ANY_USER, NULL) static void cmd_debug_watcher_info(Client* clientbase, const json_ref& args) { auto* client = static_cast<UserClient*>(clientbase); auto root = resolveRoot(client, args); auto response = make_response(); response.set("watcher-debug-info", root->view()->getWatcherDebugInfo()); client->enqueueResponse(std::move(response)); } W_CMD_REG("debug-watcher-info", cmd_debug_watcher_info, CMD_DAEMON, NULL) static void cmd_debug_watcher_info_clear( Client* clientbase, const json_ref& args) { auto* client = static_cast<UserClient*>(clientbase); auto root = resolveRoot(client, args); auto response = make_response(); root->view()->clearWatcherDebugInfo(); client->enqueueResponse(std::move(response)); } W_CMD_REG( "debug-watcher-info-clear", cmd_debug_watcher_info_clear, CMD_DAEMON, NULL) namespace { void addCacheStats(json_ref& resp, const CacheStats& stats) { resp.set( {{"cacheHit", json_integer(stats.cacheHit)}, {"cacheShare", json_integer(stats.cacheShare)}, {"cacheMiss", json_integer(stats.cacheMiss)}, {"cacheEvict", json_integer(stats.cacheEvict)}, {"cacheStore", json_integer(stats.cacheStore)}, {"cacheLoad", json_integer(stats.cacheLoad)}, {"cacheErase", json_integer(stats.cacheErase)}, {"clearCount", json_integer(stats.clearCount)}, {"size", json_integer(stats.size)}}); } void debugContentHashCache(Client* client, const json_ref& args) { /* resolve the root */ if (json_array_size(args) != 2) { client->sendErrorResponse( "wrong number of arguments for 'debug-contenthash'"); return; } auto root = resolveRoot(client, args); auto view = std::dynamic_pointer_cast<InMemoryView>(root->view()); if (!view) { client->sendErrorResponse("root is not an InMemoryView watcher"); return; } auto stats = view->debugAccessCaches().contentHashCache.stats(); auto resp = make_response(); addCacheStats(resp, stats); client->enqueueResponse(std::move(resp)); } W_CMD_REG( "debug-contenthash", debugContentHashCache, CMD_DAEMON, w_cmd_realpath_root) void debugSymlinkTargetCache(Client* client, const json_ref& args) { /* resolve the root */ if (json_array_size(args) != 2) { client->sendErrorResponse( "wrong number of arguments for 'debug-symlink-target-cache'"); return; } auto root = resolveRoot(client, args); auto view = std::dynamic_pointer_cast<InMemoryView>(root->view()); if (!view) { client->sendErrorResponse("root is not an InMemoryView watcher"); return; } auto stats = view->debugAccessCaches().symlinkTargetCache.stats(); auto resp = make_response(); addCacheStats(resp, stats); client->enqueueResponse(std::move(resp)); } W_CMD_REG( "debug-symlink-target-cache", debugSymlinkTargetCache, CMD_DAEMON, w_cmd_realpath_root) } // namespace /* vim:ts=2:sw=2:et: */