def run_operating_system_checks(tracker: ProblemTracker, instance: EdenInstance, out: ui.Output) -> None: if platform.system() != "Linux": return # get kernel version string; same as "uname -r" current_kernel_release = platform.release() # check if version too low result = _os_is_kernel_version_too_old(instance, current_kernel_release) if result: tracker.add_problem( OSProblem( # TODO: Reword these messages prior to public release description=f"Kernel version {current_kernel_release} too low.", remediation=f"Reboot to upgrade kernel version.", )) # if the kernel version is too low, return here as continuing to # further checks has no benefit return # check against known bad versions result = _os_is_bad_release(instance, current_kernel_release) if result: tracker.add_problem( OSProblem( # TODO: Reword these messages prior to public release description=f"Kernel {current_kernel_release} is a known " + "bad kernel.", remediation="Reboot to upgrade kernel version.", )) return
def run_kerberos_certificate_checks(tracker: ProblemTracker) -> None: # Skip these checks on Windows machines and on Sandcastle if platform.system() == "Windows" or os.environ.get("SANDCASTLE"): return result = subprocess.call(["klist", "-s"]) if result: tracker.add_problem(KerberosProblem())
def check_many_edenfs_are_running( tracker: ProblemTracker, proc_utils: ProcUtils, uid: Optional[int] = None ) -> None: rogue_processes = find_rogue_processes(proc_utils, uid=uid) if len(rogue_processes) > 0: rogue_pids = [p.pid for p in rogue_processes] rogue_pids_problem = ManyEdenFsRunning(rogue_pids) tracker.add_problem(rogue_pids_problem)
def read_shared_path(tracker: ProblemTracker, shared_path: Path) -> str: try: return shared_path.read_text() except (FileNotFoundError, IsADirectoryError): raise except Exception as e: tracker.add_problem(Problem(f"Failed to read .hg/sharedpath: {e}")) raise
def read_shared_path(tracker: ProblemTracker, shared_path: Path) -> str: try: return shared_path.read_text() except (FileNotFoundError, IsADirectoryError): raise except Exception as e: tracker.add_problem(UnreadableSharedpath(e)) raise
def check_shared_path(tracker: ProblemTracker, mount_path: Path) -> None: shared_path = get_shared_path(mount_path) try: dst_shared_path = read_shared_path(tracker, shared_path) except Exception: return if is_nfs_mounted(dst_shared_path): tracker.add_problem(MercurialDataOnNFS(shared_path, dst_shared_path))
def run_kerberos_certificate_checks(self, instance: EdenInstance, tracker: ProblemTracker) -> None: if not instance.get_config_bool("doctor.enable-kerberos-check", default=False): return result = subprocess.call(["klist", "-s"]) if result: tracker.add_problem(KerberosProblem())
def check_in_progress_checkout(tracker: ProblemTracker, checkout: EdenCheckout) -> None: try: checkout.get_snapshot() except InProgressCheckoutError as ex: if proc_utils.new().is_edenfs_process(ex.pid): return tracker.add_problem(PreviousEdenFSCrashedDuringCheckout(checkout, ex))
def check_watchman_subscriptions(tracker: ProblemTracker, path: str, info: WatchmanCheckInfo) -> None: if path not in info.watchman_roots: return watch_details = _call_watchman(["watch-project", path]) watcher = watch_details.get("watcher") if watcher == "eden": return tracker.add_problem(IncorrectWatchmanWatch(path, watcher))
def check_redirections( tracker: ProblemTracker, instance: EdenInstance, checkout: EdenCheckout, mount_table: mtab.MountTable, ) -> None: redirs = get_effective_redirections(checkout, mount_table) for redir in redirs.values(): if redir.state == RedirectionState.MATCHES_CONFIGURATION: continue tracker.add_problem(MisconfiguredRedirection(redir, checkout))
def check_disk_usage( tracker: ProblemTracker, mount_paths: List[str], instance: EdenInstance, fs_util: FsUtil, ) -> None: prob_advice_space_used_ratio_threshold = 0.90 prob_error_absolute_space_used_threshold = 1024 * 1024 * 1024 # 1GB eden_mount_pts_set = get_mount_pts_set(tracker, mount_paths, instance) for eden_mount_pt in eden_mount_pts_set: if eden_mount_pt and os.path.exists(eden_mount_pt): disk_status = fs_util.statvfs(eden_mount_pt) avail = disk_status.f_frsize * disk_status.f_bavail size = disk_status.f_frsize * disk_status.f_blocks if size == 0: continue used = size - avail used_percent = float(used) / size message = ( "Eden lazily loads your files and needs enough disk space to " "store these files when loaded." ) extra_message = instance.get_config_value( "doctor.low-disk-space-message", "" ) if extra_message: message = f"{message} {extra_message}" if avail <= prob_error_absolute_space_used_threshold: tracker.add_problem( Problem( f"{eden_mount_pt} " f"has only {str(avail)} bytes available. " f"{message}", severity=ProblemSeverity.ERROR, ) ) elif used_percent >= prob_advice_space_used_ratio_threshold: tracker.add_problem( Problem( f"{eden_mount_pt} " f"is {used_percent:.2%} full. " f"{message}", severity=ProblemSeverity.ADVICE, ) )
def check_materialized_are_accessible( tracker: ProblemTracker, instance: EdenInstance, checkout: EdenCheckout, ) -> None: mismatched_mode = [] inaccessible_inodes = [] with instance.get_thrift_client_legacy() as client: materialized = client.debugInodeStatus( bytes(checkout.path), b"", flags=DIS_REQUIRE_MATERIALIZED, sync=SyncBehavior(), ) for materialized_dir in materialized: path = Path(os.fsdecode(materialized_dir.path)) try: st = os.lstat(checkout.path / path) except OSError: inaccessible_inodes += [path] continue if not stat.S_ISDIR(st.st_mode): mismatched_mode += [(path, stat.S_IFDIR, st.st_mode)] for dirent in materialized_dir.entries: if dirent.materialized: dirent_path = path / Path(os.fsdecode(dirent.name)) try: dirent_stat = os.lstat(checkout.path / dirent_path) except OSError: inaccessible_inodes += [dirent_path] continue # TODO(xavierd): Symlinks are for now recognized as files. dirent_mode = (stat.S_IFREG if stat.S_ISLNK( dirent_stat.st_mode) else stat.S_IFMT(dirent_stat.st_mode)) if dirent_mode != stat.S_IFMT(dirent.mode): mismatched_mode += [(dirent_path, dirent_stat.st_mode, dirent.mode)] if inaccessible_inodes != []: tracker.add_problem( MaterializedInodesAreInaccessible(inaccessible_inodes)) if mismatched_mode != []: tracker.add_problem( MaterializedInodesHaveDifferentModeOnDisk(mismatched_mode))
def check_shared_path(tracker: ProblemTracker, mount_path: Path) -> None: shared_path = get_shared_path(mount_path) try: dst_shared_path = read_shared_path(tracker, shared_path) except Exception: return if is_nfs_mounted(dst_shared_path): msg = ( f"The Mercurial data directory for {shared_path} is at" f" {dst_shared_path} which is on a NFS filesystem." f" Accessing files and directories in this repository will be slow." ) problem = Problem(msg, severity=ProblemSeverity.ADVICE) tracker.add_problem(problem)
def check_loaded_content( tracker: ProblemTracker, instance: EdenInstance, checkout: EdenCheckout, query_prjfs_file: Callable[[Path], PRJ_FILE_STATE], ) -> None: with instance.get_thrift_client_legacy() as client: loaded = client.debugInodeStatus( bytes(checkout.path), b"", flags=DIS_REQUIRE_LOADED, sync=SyncBehavior(), ) errors = [] for loaded_dir in loaded: path = Path(os.fsdecode(loaded_dir.path)) for dirent in loaded_dir.entries: if not stat.S_ISREG(dirent.mode) or dirent.materialized: continue dirent_path = path / Path(os.fsdecode(dirent.name)) filestate = query_prjfs_file(checkout.path / dirent_path) if (filestate & PRJ_FILE_STATE.HydratedPlaceholder != PRJ_FILE_STATE.HydratedPlaceholder): # We should only compute the sha1 of files that have been read. continue def compute_file_sha1(file: Path) -> bytes: hasher = hashlib.sha1() with open(checkout.path / dirent_path, "rb") as f: while True: buf = f.read(1024 * 1024) if buf == b"": break hasher.update(buf) return hasher.digest() sha1 = client.getSHA1(bytes(checkout.path), [bytes(dirent_path)], sync=SyncBehavior())[0].get_sha1() on_disk_sha1 = compute_file_sha1(checkout.path / dirent_path) if sha1 != on_disk_sha1: errors += [(dirent_path, sha1, on_disk_sha1)] if errors != []: tracker.add_problem(LoadedFileHasDifferentContentOnDisk(errors))
def check_eden_directory(tracker: ProblemTracker, instance: EdenInstance) -> None: if not is_nfs_mounted(str(instance.state_dir)): return msg = ( f"Eden's state directory is on an NFS file system: {instance.state_dir}\n" f" This will likely cause performance problems and/or other errors.") # On FB devservers the default Eden state directory path is ~/local/.eden # Normally ~/local is expected to be a symlink to local disk (for users who are # still using NFS home directories in the first place). The most common cause of # the Eden state directory being on NFS is for users that somehow have a regular # directory at ~/local rather than a symlink. Suggest checking this as a # remediation. remediation = ( "The most common cause for this is if your ~/local symlink does not point " "to local disk. Make sure that ~/local is a symlink pointing to local disk " "and then restart Eden.") tracker.add_problem(Problem(msg, remediation))
def check_hg(tracker: ProblemTracker, checkout: EdenCheckout) -> None: file_checker_classes: List[Type[HgChecker]] = [ DirstateChecker, HgrcChecker, RequiresChecker, SharedPathChecker, SharedChecker, BookmarksChecker, BranchChecker, ] # `AbandonedTransactionChecker` is looking for the existence of the journal # file as indicator of a potential problem. The rest is check if files are # missing. other_checker_classes: List[Type[HgChecker]] = [ AbandonedTransactionChecker ] # pyre-fixme[45]: Cannot instantiate abstract class `HgChecker`. file_checkers = [ checker_class(checkout) for checker_class in file_checker_classes ] checkers = file_checkers + [ # pyre-fixme[45]: Cannot instantiate abstract class `HgChecker`. checker_class(checkout) for checker_class in other_checker_classes ] hg_path = checkout.path / ".hg" if not os.path.exists(hg_path): description = f"Missing hg directory: {checkout.path}/.hg" tracker.add_problem(HgDirectoryError(checkout, checkers, description)) return check_in_progress_checkout(tracker, checkout) bad_checkers: List[HgChecker] = [] for checker in checkers: try: if checker.check(): continue bad_checkers.append(checker) except Exception: tracker.add_problem(UnexpectedCheckError()) if bad_checkers: # if all the file checkers fail, it indicates we are seeing an empty # `.hg` directory msg = (f"No contents present in hg directory: {checkout.path}/.hg" if len(bad_checkers) == len(file_checkers) else None) tracker.add_problem(HgDirectoryError(checkout, bad_checkers, msg))
def check_nuclide_subscriptions(tracker: ProblemTracker, path: str, info: WatchmanCheckInfo) -> None: if info.nuclide_roots is None: return # Note that nuclide_roots is a set, but each entry in the set # could appear as a root folder multiple times if the user uses multiple # Atom windows. path_prefix = path + "/" connected_nuclide_roots = [ nuclide_root for nuclide_root in info.nuclide_roots if path == nuclide_root or nuclide_root.startswith(path_prefix) ] if not connected_nuclide_roots: # There do not appear to be any Nuclide connections for path. return subscriptions = _call_watchman(["debug-get-subscriptions", path]) subscribers = subscriptions.get("subscribers", []) subscription_counts: Dict[str, int] = {} for subscriber in subscribers: subscriber_info = subscriber.get("info", {}) name = subscriber_info.get("name") if name is None: continue elif name in subscription_counts: subscription_counts[name] += 1 else: subscription_counts[name] = 1 missing_or_duplicate_subscriptions = [] for nuclide_root in connected_nuclide_roots: filewatcher_subscription = f"filewatcher-{nuclide_root}" # Note that even if the user has `nuclide_root` opened in multiple # Nuclide windows, the Nuclide server should not create the # "filewatcher-" subscription multiple times. if subscription_counts.get(filewatcher_subscription) != 1: missing_or_duplicate_subscriptions.append(filewatcher_subscription) # Today, Nuclide creates a number of Watchman subscriptions per root # folder that is under an Hg working copy. (It should probably # consolidate these subscriptions, though it will take some work to # refactor things to do that.) Because each of connected_nuclide_roots # is a root folder in at least one Atom window, there must be at least # as many instances of each subscription as there are # connected_nuclide_roots. # # TODO(mbolin): Come up with a more stable contract than including a # hardcoded list of Nuclide subscription names in here because Eden and # Nuclide releases are not synced. This is admittedly a stopgap measure: # the primary objective is to figure out how Eden/Nuclide gets into # this state to begin with and prevent it. # # Further, Nuclide should probably rename these subscriptions so that: # (1) It is clear that Nuclide is the one who created the subscription. # (2) The subscription can be ascribed to an individual Nuclide client # if we are going to continue to create the same subscription # multiple times. num_roots = len(connected_nuclide_roots) for hg_subscription in NUCLIDE_HG_SUBSCRIPTIONS: if subscription_counts.get(hg_subscription, 0) < num_roots: missing_or_duplicate_subscriptions.append(hg_subscription) if missing_or_duplicate_subscriptions: def format_paths(paths: List[str]) -> str: return "\n ".join(paths) missing_subscriptions = [ sub for sub in missing_or_duplicate_subscriptions if 0 == subscription_counts.get(sub, 0) ] duplicate_subscriptions = [ sub for sub in missing_or_duplicate_subscriptions if 1 < subscription_counts.get(sub, 0) ] output = io.StringIO() output.write( "Nuclide appears to be used to edit the following directories\n" f"under {path}:\n\n" f" {format_paths(connected_nuclide_roots)}\n\n") if missing_subscriptions: output.write( "but the following Watchman subscriptions appear to be missing:\n\n" f" {format_paths(missing_subscriptions)}\n\n") if duplicate_subscriptions: conj = "and" if missing_subscriptions else "but" output.write( f"{conj} the following Watchman subscriptions have duplicates:\n\n" f" {format_paths(duplicate_subscriptions)}\n\n") output.write( "This can cause file changes to fail to show up in Nuclide.\n" "Currently, the only workaround for this is to run\n" '"Nuclide Remote Projects: Kill And Restart" from the\n' "command palette in Atom.\n") tracker.add_problem(Problem(output.getvalue()))
def check_for_stale_mounts(tracker: ProblemTracker, mount_table: mtab.MountTable) -> None: stale_mounts = get_all_stale_eden_mount_points(mount_table) if stale_mounts: tracker.add_problem(StaleMountsFound(stale_mounts, mount_table))
def check_eden_directory(tracker: ProblemTracker, instance: EdenInstance) -> None: if not is_nfs_mounted(str(instance.state_dir)): return tracker.add_problem(StateDirOnNFS(instance))