live debug journal command

Summary: Running `eden debug journal -f` will print and follow the eden journal in a similar style to the unix `tail -f` command.

Reviewed By: chadaustin

Differential Revision: D16112458

fbshipit-source-id: 5304cd0f857bdbeca41c2591e98920f4f1fc8f42
This commit is contained in:
Brian Strauch 2019-07-09 09:10:19 -07:00 committed by Facebook Github Bot
parent 969abcdcb4
commit e2d4362896
6 changed files with 67 additions and 29 deletions

View File

@ -15,6 +15,7 @@ import shlex
import shutil
import stat
import sys
import time
from pathlib import Path
from typing import (
IO,
@ -884,7 +885,7 @@ class DebugJournalGetMemoryLimitCmd(Subcmd):
return 0
@debug_cmd("journal", "Prints the most recent N entries from the journal")
@debug_cmd("journal", "Prints the most recent entries from the journal")
class DebugJournalCmd(Subcmd):
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
@ -901,6 +902,13 @@ class DebugJournalCmd(Subcmd):
help="Show only deltas for paths matching this pattern. "
"Specify '^((?!^\\.hg/).)*$' to exclude the .hg/ directory.",
)
parser.add_argument(
"-f",
"--follow",
action="store_true",
default=False,
help="Output appended data as the journal grows.",
)
parser.add_argument(
"-i",
"--ignore-case",
@ -915,27 +923,48 @@ class DebugJournalCmd(Subcmd):
)
def run(self, args: argparse.Namespace) -> int:
instance, checkout, _rel_path = cmd_util.require_checkout(args, args.path)
pattern: Optional[Pattern[bytes]] = None
if args.pattern:
pattern_bytes = args.pattern.encode("utf-8")
flags = re.IGNORECASE if args.ignore_case else 0
pattern = re.compile(pattern_bytes, flags)
instance, checkout, _ = cmd_util.require_checkout(args, args.path)
mount = bytes(checkout.path)
def refresh(params):
with instance.get_thrift_client() as client:
params = DebugGetRawJournalParams(
mountPoint=bytes(checkout.path), limit=args.limit
)
journal = client.debugGetRawJournal(params)
deltas = journal.allDeltas
if len(deltas) == 0:
seq_num = params.fromSequenceNumber
else:
seq_num = deltas[0].fromPosition.sequenceNumber + 1
_print_raw_journal_deltas(reversed(deltas), pattern)
return seq_num
try:
raw_journal = client.debugGetRawJournal(params)
params = DebugGetRawJournalParams(
mountPoint=mount, fromSequenceNumber=1, limit=args.limit
)
seq_num = refresh(params)
while args.follow:
REFRESH_SEC = 2
time.sleep(REFRESH_SEC)
params = DebugGetRawJournalParams(
mountPoint=mount, fromSequenceNumber=seq_num
)
seq_num = refresh(params)
except EdenError as err:
print(err, file=sys.stderr)
return 1
if args.pattern:
flags = re.IGNORECASE if args.ignore_case else 0
bytes_pattern = args.pattern.encode("utf-8")
pattern: Optional[Pattern[bytes]] = re.compile(bytes_pattern, flags)
except KeyboardInterrupt:
if args.follow:
pass
else:
pattern = None
# debugGetRawJournal() returns the most recent entries first, but
# we want to display the oldest entries first, so we pass a reversed
# iterator along.
_print_raw_journal_deltas(reversed(raw_journal.allDeltas), pattern)
raise
return 0

View File

@ -224,12 +224,13 @@ std::unique_ptr<JournalDeltaRange> Journal::accumulateRange(
}
std::vector<DebugJournalDelta> Journal::getDebugRawJournalInfo(
size_t limit,
SequenceNumber from,
std::optional<size_t> limit,
long mountGeneration) const {
auto result = std::vector<DebugJournalDelta>();
forEachDelta(
0,
std::optional<size_t>(limit),
from,
limit,
[mountGeneration, &result](const JournalDelta& current) -> void {
DebugJournalDelta delta;
JournalPosition fromPosition;

View File

@ -144,7 +144,8 @@ class Journal {
* reached then it will just return what had been currently found.
* */
std::vector<DebugJournalDelta> getDebugRawJournalInfo(
size_t limit,
SequenceNumber from,
std::optional<size_t> limit,
long mountGeneration) const;
void setMemoryLimit(size_t limit);

View File

@ -138,7 +138,7 @@ TEST(Journal, debugRawJournalInfoRemoveCreateUpdate) {
long mountGen = 333;
auto debugDeltas = journal.getDebugRawJournalInfo(3, mountGen);
auto debugDeltas = journal.getDebugRawJournalInfo(0, 3, mountGen);
ASSERT_EQ(3, debugDeltas.size());
// Debug Raw Journal Info returns info from newest->latest
@ -155,14 +155,14 @@ TEST(Journal, debugRawJournalInfoRemoveCreateUpdate) {
EXPECT_EQ(debugDeltas[2].fromPosition.mountGeneration, mountGen);
EXPECT_EQ(debugDeltas[2].fromPosition.sequenceNumber, 1);
debugDeltas = journal.getDebugRawJournalInfo(1, mountGen);
debugDeltas = journal.getDebugRawJournalInfo(0, 1, mountGen);
ASSERT_EQ(1, debugDeltas.size());
EXPECT_TRUE(debugDeltas[0].changedPaths["test.txt"].existedBefore);
EXPECT_TRUE(debugDeltas[0].changedPaths["test.txt"].existedAfter);
EXPECT_EQ(debugDeltas[0].fromPosition.mountGeneration, mountGen);
EXPECT_EQ(debugDeltas[0].fromPosition.sequenceNumber, 3);
debugDeltas = journal.getDebugRawJournalInfo(0, mountGen);
debugDeltas = journal.getDebugRawJournalInfo(0, 0, mountGen);
ASSERT_EQ(0, debugDeltas.size());
}

View File

@ -675,8 +675,14 @@ void EdenServiceHandler::debugGetRawJournal(
auto helper = INSTRUMENT_THRIFT_CALL(DBG3, params->mountPoint);
auto edenMount = server_->getMount(params->mountPoint);
auto mountGeneration = static_cast<ssize_t>(edenMount->getMountGeneration());
std::optional<size_t> limitopt = std::nullopt;
if (auto limit = params->limit_ref()) {
limitopt = static_cast<size_t>(*limit);
}
out.allDeltas = edenMount->getJournal().getDebugRawJournalInfo(
static_cast<size_t>(params->limit), mountGeneration);
params->fromSequenceNumber, limitopt, mountGeneration);
#else
NOT_IMPLEMENTED();
#endif // !_WIN32

View File

@ -200,7 +200,8 @@ struct FileDelta {
struct DebugGetRawJournalParams {
1: PathString mountPoint
2: i32 limit
2: optional i32 limit
3: i32 fromSequenceNumber
}
struct DebugPathChangeInfo {