blob: cba281df31ccea521f3c83139cf98e367fb10986 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/forensics/feedback_data/system_log_recorder/system_log_recorder.h"
#include <memory>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/developer/forensics/feedback_data/constants.h"
#include "src/developer/forensics/feedback_data/system_log_recorder/encoding/identity_decoder.h"
#include "src/developer/forensics/feedback_data/system_log_recorder/encoding/identity_encoder.h"
#include "src/developer/forensics/feedback_data/system_log_recorder/encoding/production_encoding.h"
#include "src/developer/forensics/feedback_data/system_log_recorder/reader.h"
#include "src/developer/forensics/testing/stubs/diagnostics_archive.h"
#include "src/developer/forensics/testing/stubs/diagnostics_batch_iterator.h"
#include "src/developer/forensics/testing/unit_test_fixture.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "src/lib/files/scoped_temp_dir.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace forensics {
namespace feedback_data {
namespace system_log_recorder {
namespace {
constexpr zx::duration kTimeWaitForLimitedLogs = zx::sec(60);
// Only change "X" for one character. i.e. X -> 12 is not allowed.
const auto kMaxLogLineSize =
StorageSize::Bytes(std::string("[15604.000][07559][07687][] INFO: line X\n").size());
const auto kDroppedFormatStrSize =
StorageSize::Bytes(std::string("!!! DROPPED X MESSAGES !!!\n").size());
const auto kMaxDecompressedSize = StorageSize::Kilobytes(256);
TEST(Encoding, VerifyProductionEncoderDecoderVersion) {
// Verify that the production decoder and encoder always have the same version.
ProductionEncoder encoder;
ProductionDecoder decoder;
EXPECT_EQ(encoder.GetEncodingVersion(), decoder.GetEncodingVersion());
}
std::string BuildLogMessage(const std::string& message) {
constexpr char fmt[] = R"JSON(
[
{
"metadata": {
"timestamp": 15604000000000,
"severity": "INFO",
"pid": 7559,
"tid": 7687
},
"payload": {
"root": {
"message": {
"value": "%s"
}
}
}
}
]
)JSON";
return fxl::StringPrintf(fmt, message.c_str());
}
using SystemLogRecorderTest = UnitTestFixture;
TEST_F(SystemLogRecorderTest, SingleThreaded_SmokeTest) {
// To simulate a real load, we set up the test with the following conditions:
// * The listener will get messages every 750 milliseconds.
// * The writer writes messages every 1 second. Each write will contain at most 2 log
// lines.
// * Each file will contain at most 2 log lines.
//
// Using the above, we'll see log lines arrive with the at the following times:
// 0.00: line0, line1, line2, line3 -> write 1 -> file 1
// 0.75: line4, line5, line6, line7 -> write 1 -> file 1
// 1.50: line8 -> write 2 -> file 2
// 2.25: line9 -> write 3 -> file 2
// 3.00: line10 -> write 4 -> file 2
// 3.75: line11 -> write 4 -> file 2
// 4.50: line12 -> write 5 -> file 3
// 5.25: line13 -> write 6 -> file 3
//
// Note: we use the IdentityEncoder to easily control which messages are dropped.
// Note 2: we offset time by kTimeWaitForLimitedLog to wait for the buffer rate limiter.
const zx::duration kArchivePeriod = zx::msec(750);
const zx::duration kWriterPeriod = zx::sec(1);
const std::vector<std::vector<std::string>> json_batches({
{
BuildLogMessage("line 0"),
BuildLogMessage("line 1"),
BuildLogMessage("line 2"),
BuildLogMessage("line 3"),
},
{
BuildLogMessage("line 4"),
BuildLogMessage("line 5"),
BuildLogMessage("line 6"),
BuildLogMessage("line 7"),
},
{BuildLogMessage("line 8")},
{BuildLogMessage("line 9")},
{BuildLogMessage("line A")},
{BuildLogMessage("line B")},
{BuildLogMessage("line C")},
{BuildLogMessage("line D")},
{},
});
stubs::DiagnosticsArchive archive(std::make_unique<stubs::DiagnosticsBatchIteratorDelayedBatches>(
dispatcher(), json_batches, kTimeWaitForLimitedLogs, kArchivePeriod));
InjectServiceProvider(&archive, kArchiveAccessorName);
files::ScopedTempDir temp_dir;
const StorageSize kWriteSize = kMaxLogLineSize * 2 + kDroppedFormatStrSize;
SystemLogRecorder recorder(
dispatcher(), dispatcher(), services(),
SystemLogRecorder::WriteParameters{
.period = kWriterPeriod,
.max_write_size = kWriteSize,
.logs_dir = temp_dir.path(),
.max_num_files = 2u,
.total_log_size = 2u * kWriteSize,
},
std::unique_ptr<RedactorBase>(new IdentityRedactor(inspect::BoolProperty())),
std::unique_ptr<Encoder>(new IdentityEncoder()));
recorder.Start();
RunLoopFor(kTimeWaitForLimitedLogs);
std::string contents;
files::ScopedTempDir output_dir;
const std::string output_path = files::JoinPath(output_dir.path(), "output.txt");
IdentityDecoder decoder;
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_TRUE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
}
ASSERT_TRUE(files::ReadFileToString(output_path, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 0
[15604.000][07559][07687][] INFO: line 1
!!! DROPPED 6 MESSAGES !!!
)");
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_TRUE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
}
ASSERT_TRUE(files::ReadFileToString(output_path, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 0
[15604.000][07559][07687][] INFO: line 1
!!! DROPPED 6 MESSAGES !!!
[15604.000][07559][07687][] INFO: line 8
)");
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_TRUE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
}
ASSERT_TRUE(files::ReadFileToString(output_path, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 0
[15604.000][07559][07687][] INFO: line 1
!!! DROPPED 6 MESSAGES !!!
[15604.000][07559][07687][] INFO: line 8
[15604.000][07559][07687][] INFO: line 9
)");
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_TRUE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
}
ASSERT_TRUE(files::ReadFileToString(output_path, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 8
[15604.000][07559][07687][] INFO: line 9
[15604.000][07559][07687][] INFO: line A
[15604.000][07559][07687][] INFO: line B
)");
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_TRUE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
}
ASSERT_TRUE(files::ReadFileToString(output_path, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 8
[15604.000][07559][07687][] INFO: line 9
[15604.000][07559][07687][] INFO: line A
[15604.000][07559][07687][] INFO: line B
[15604.000][07559][07687][] INFO: line C
)");
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_TRUE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
}
ASSERT_TRUE(files::ReadFileToString(output_path, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 8
[15604.000][07559][07687][] INFO: line 9
[15604.000][07559][07687][] INFO: line A
[15604.000][07559][07687][] INFO: line B
[15604.000][07559][07687][] INFO: line C
[15604.000][07559][07687][] INFO: line D
)");
}
TEST_F(SystemLogRecorderTest, SingleThreaded_StopAndDeleteLogs) {
// To simulate a real load, we set up the test with the following conditions:
// * The listener will get messages every 750 milliseconds.
// * The writer writes messages every 1 second. Each write will contain at most 2 log
// lines.
// * Each file will contain at most 2 log lines.
//
// Using the above, we'll see log lines arrive with the at the following times:
// 0.00: line0, line1, line2, line3 -> write 1 -> file 1
// 0.75: line4, line5, line6, line7 -> write 1 -> file 1
// 1.50: line8 -> write 2 -> file 2
// 2.25: line9 -> write 3 -> file 2
// 3.00: line10 -> write 4 -> file 2
// 3.75: line11 -> write 4 -> file 2
// 4.50: line12 -> write 5 -> file 3
// 5.25: line13 -> write 6 -> file 3
//
// Note: we use the IdentityEncoder to easily control which messages are dropped.
// Note 2: we offset time by kTimeWaitForLimitedLog to wait for the buffer rate limiter.
const zx::duration kArchivePeriod = zx::msec(750);
const zx::duration kWriterPeriod = zx::sec(1);
const std::vector<std::vector<std::string>> json_batches({
{
BuildLogMessage("line 0"),
BuildLogMessage("line 1"),
BuildLogMessage("line 2"),
BuildLogMessage("line 3"),
},
{
BuildLogMessage("line 4"),
BuildLogMessage("line 5"),
BuildLogMessage("line 6"),
BuildLogMessage("line 7"),
},
{BuildLogMessage("line 8")},
{BuildLogMessage("line 9")},
{BuildLogMessage("line A")},
{BuildLogMessage("line B")},
{BuildLogMessage("line C")},
{BuildLogMessage("line D")},
{},
});
stubs::DiagnosticsArchive archive(std::make_unique<stubs::DiagnosticsBatchIteratorDelayedBatches>(
dispatcher(), json_batches, kTimeWaitForLimitedLogs, kArchivePeriod, /*strict=*/false));
InjectServiceProvider(&archive, kArchiveAccessorName);
files::ScopedTempDir temp_dir;
const StorageSize kWriteSize = kMaxLogLineSize * 2 + kDroppedFormatStrSize;
SystemLogRecorder recorder(
dispatcher(), dispatcher(), services(),
SystemLogRecorder::WriteParameters{
.period = kWriterPeriod,
.max_write_size = kWriteSize,
.logs_dir = temp_dir.path(),
.max_num_files = 2u,
.total_log_size = 2u * kWriteSize,
},
std::unique_ptr<RedactorBase>(new IdentityRedactor(inspect::BoolProperty())),
std::unique_ptr<Encoder>(new IdentityEncoder()));
recorder.Start();
RunLoopFor(kTimeWaitForLimitedLogs);
std::string contents;
RunLoopFor(kWriterPeriod);
files::ScopedTempDir output_dir;
const std::string output_path = files::JoinPath(output_dir.path(), "output.txt");
IdentityDecoder decoder;
{
float compression_ratio;
ASSERT_TRUE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
}
ASSERT_TRUE(files::ReadFileToString(output_path, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 0
[15604.000][07559][07687][] INFO: line 1
!!! DROPPED 6 MESSAGES !!!
)");
recorder.StopAndDeleteLogs();
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_FALSE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
}
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_FALSE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
}
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_FALSE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
}
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_FALSE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
}
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_FALSE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
}
}
TEST_F(SystemLogRecorderTest, SingleThreaded_Flush) {
// To simulate a real load, we set up the test with the following conditions:
// * The listener will get messages every 750 milliseconds.
// * The writer writes messages every 1 second. Each write will contain at most 2 log
// lines.
// * Each file will contain at most 2 log lines.
//
// Using the above, we'll see log lines arrive with the at the following times:
// 0.00: line0, line1, line2, line3 -> write 1 -> file 1
// 0.75: line4, line5, line6, line7 -> write 1 -> file 1
// 0.75: FLUSH
// 1.50: line8 -> write 2 -> file 2
//
// Note: we use the IdentityEncoder to easily control which messages are dropped.
// Note 2: we offset time by kTimeWaitForLimitedLog to wait for the buffer rate limiter.
const zx::duration kArchivePeriod = zx::msec(750);
const zx::duration kWriterPeriod = zx::sec(1);
const std::vector<std::vector<std::string>> json_batches({
{
BuildLogMessage("line 0"),
BuildLogMessage("line 1"),
BuildLogMessage("line 2"),
BuildLogMessage("line 3"),
},
{
BuildLogMessage("line 4"),
BuildLogMessage("line 5"),
BuildLogMessage("line 6"),
BuildLogMessage("line 7"),
},
{BuildLogMessage("line 8")},
{},
});
stubs::DiagnosticsArchive archive(std::make_unique<stubs::DiagnosticsBatchIteratorDelayedBatches>(
dispatcher(), json_batches, kTimeWaitForLimitedLogs, kArchivePeriod, /*strict=*/true));
InjectServiceProvider(&archive, kArchiveAccessorName);
files::ScopedTempDir temp_dir;
const std::string kFlushStr = "FLUSH\n";
const StorageSize kWriteSize =
kMaxLogLineSize * 2 + kDroppedFormatStrSize + StorageSize::Bytes(kFlushStr.size());
SystemLogRecorder recorder(
dispatcher(), dispatcher(), services(),
SystemLogRecorder::WriteParameters{
.period = kWriterPeriod,
.max_write_size = kWriteSize,
.logs_dir = temp_dir.path(),
.max_num_files = 2u,
.total_log_size = 2u * kWriteSize,
},
std::unique_ptr<RedactorBase>(new IdentityRedactor(inspect::BoolProperty())),
std::unique_ptr<Encoder>(new IdentityEncoder()));
recorder.Start();
RunLoopFor(kTimeWaitForLimitedLogs);
RunLoopFor(kArchivePeriod);
recorder.Flush(kFlushStr);
std::string contents;
files::ScopedTempDir output_dir;
const std::string output_path = files::JoinPath(output_dir.path(), "output.txt");
IdentityDecoder decoder;
{
float compression_ratio;
ASSERT_TRUE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
}
ASSERT_TRUE(files::ReadFileToString(output_path, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 0
[15604.000][07559][07687][] INFO: line 1
!!! DROPPED 6 MESSAGES !!!
FLUSH
)");
RunLoopFor(kWriterPeriod);
RunLoopFor(kWriterPeriod);
{
float compression_ratio;
ASSERT_TRUE(Concatenate(temp_dir.path(), kMaxDecompressedSize, &decoder, output_path,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
}
ASSERT_TRUE(files::ReadFileToString(output_path, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 0
[15604.000][07559][07687][] INFO: line 1
!!! DROPPED 6 MESSAGES !!!
FLUSH
[15604.000][07559][07687][] INFO: line 8
)");
}
} // namespace
} // namespace system_log_recorder
} // namespace feedback_data
} // namespace forensics