Skip to content

Commit 2c1355f

Browse files
authored
Merge branch 'main' into pr-query-log-correlation
2 parents 551d36b + 46f0c0a commit 2c1355f

19 files changed

Lines changed: 1141 additions & 386 deletions
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#include "AggregationSink.hpp"
2+
3+
#include <iostream>
4+
#include <string_view>
5+
#include <variant>
6+
7+
#include <bsoncxx/builder/basic/document.hpp>
8+
#include <bsoncxx/builder/basic/kvp.hpp>
9+
#include <mongocxx/client.hpp>
10+
#include <mongocxx/collection.hpp>
11+
#include <mongocxx/exception/exception.hpp>
12+
#include <nlohmann/json.hpp>
13+
14+
#include <clp_s/archive_constants.hpp>
15+
#include <clp_s/ErrorCode.hpp>
16+
#include <clp_s/ResultsCacheUtils.hpp>
17+
18+
using std::string_view;
19+
20+
namespace clp_s {
21+
auto StdoutSink::write(AggregationResult const& result) -> void {
22+
nlohmann::json document;
23+
document[constants::results_cache::search::cArchiveId] = m_archive_id;
24+
for (auto const& [key, value] : result) {
25+
std::visit([&](auto const& field_value) { document[key] = field_value; }, value);
26+
}
27+
std::cout << document.dump() << '\n';
28+
}
29+
30+
ResultsCacheSink::ResultsCacheSink(string_view uri, string_view collection, string_view archive_id)
31+
: m_archive_id{archive_id} {
32+
m_collection = connect_to_results_cache(uri, collection, m_client);
33+
}
34+
35+
auto ResultsCacheSink::write(AggregationResult const& result) -> void {
36+
bsoncxx::builder::basic::document document;
37+
document.append(
38+
bsoncxx::builder::basic::kvp(constants::results_cache::search::cArchiveId, m_archive_id)
39+
);
40+
for (auto const& [key, value] : result) {
41+
std::visit(
42+
[&](auto const& field_value) {
43+
document.append(bsoncxx::builder::basic::kvp(key, field_value));
44+
},
45+
value
46+
);
47+
}
48+
m_results.push_back(document.extract());
49+
}
50+
51+
auto ResultsCacheSink::finish() -> ErrorCode {
52+
if (m_results.empty()) {
53+
return ErrorCode::ErrorCodeSuccess;
54+
}
55+
56+
try {
57+
m_collection.insert_many(m_results);
58+
m_results.clear();
59+
} catch (mongocxx::exception const& e) {
60+
return ErrorCode::ErrorCodeFailureDbBulkWrite;
61+
}
62+
return ErrorCode::ErrorCodeSuccess;
63+
}
64+
} // namespace clp_s
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#ifndef CLP_S_AGGREGATIONSINK_HPP
2+
#define CLP_S_AGGREGATIONSINK_HPP
3+
4+
#include <string>
5+
#include <string_view>
6+
#include <vector>
7+
8+
#include <bsoncxx/document/value.hpp>
9+
#include <mongocxx/client.hpp>
10+
#include <mongocxx/collection.hpp>
11+
12+
#include <clp_s/aggregators.hpp>
13+
#include <clp_s/ErrorCode.hpp>
14+
15+
namespace clp_s {
16+
/**
17+
* Consumes an aggregation's result documents and writes them to a destination.
18+
*/
19+
class AggregationSink {
20+
public:
21+
// Constructors
22+
AggregationSink() = default;
23+
24+
// Delete copy constructor and assignment operator
25+
AggregationSink(AggregationSink const&) = delete;
26+
auto operator=(AggregationSink const&) -> AggregationSink& = delete;
27+
28+
// Delete move constructor and assignment operator
29+
AggregationSink(AggregationSink&&) = delete;
30+
auto operator=(AggregationSink&&) -> AggregationSink& = delete;
31+
32+
// Destructor
33+
virtual ~AggregationSink() = default;
34+
35+
// Methods
36+
/**
37+
* Writes one result document.
38+
* @param result The result document to write.
39+
*/
40+
virtual auto write(AggregationResult const& result) -> void = 0;
41+
42+
/**
43+
* Flushes any buffered results.
44+
* @return ErrorCodeSuccess on success or relevant error code on error
45+
*/
46+
[[nodiscard]] virtual auto finish() -> ErrorCode = 0;
47+
};
48+
49+
/**
50+
* Sink that writes aggregation results to standard output as newline-delimited JSON.
51+
*/
52+
class StdoutSink : public AggregationSink {
53+
public:
54+
// Constructors
55+
explicit StdoutSink(std::string_view archive_id) : m_archive_id{archive_id} {}
56+
57+
// Methods implementing AggregationSink
58+
auto write(AggregationResult const& result) -> void override;
59+
60+
[[nodiscard]] auto finish() -> ErrorCode override { return ErrorCode::ErrorCodeSuccess; }
61+
62+
private:
63+
// Data members
64+
std::string m_archive_id;
65+
};
66+
67+
/**
68+
* Sink that writes aggregation results to a MongoDB results-cache collection.
69+
*/
70+
class ResultsCacheSink : public AggregationSink {
71+
public:
72+
// Constructors
73+
ResultsCacheSink(
74+
std::string_view uri,
75+
std::string_view collection,
76+
std::string_view archive_id
77+
);
78+
79+
// Methods implementing AggregationSink
80+
auto write(AggregationResult const& result) -> void override;
81+
82+
/**
83+
* Flushes the buffered result documents.
84+
* @return ErrorCodeSuccess on success
85+
* @return ErrorCodeFailureDbBulkWrite on database error
86+
*/
87+
[[nodiscard]] auto finish() -> ErrorCode override;
88+
89+
private:
90+
// Data members
91+
mongocxx::client m_client;
92+
mongocxx::collection m_collection;
93+
std::string m_archive_id;
94+
std::vector<bsoncxx::document::value> m_results;
95+
};
96+
} // namespace clp_s
97+
98+
#endif // CLP_S_AGGREGATIONSINK_HPP

components/core/src/clp_s/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,13 +480,19 @@ endif()
480480

481481
set(
482482
CLP_S_EXE_SOURCES
483+
AggregationSink.cpp
484+
AggregationSink.hpp
485+
aggregators.cpp
486+
aggregators.hpp
483487
CommandLineArguments.cpp
484488
CommandLineArguments.hpp
485489
ErrorCode.hpp
486490
kv_ir_search.cpp
487491
kv_ir_search.hpp
488492
OutputHandlerImpl.cpp
489493
OutputHandlerImpl.hpp
494+
ResultsCacheUtils.cpp
495+
ResultsCacheUtils.hpp
490496
TraceableException.hpp
491497
)
492498

@@ -544,6 +550,7 @@ if(CLP_BUILD_TESTING)
544550
target_sources(
545551
clp_s_unit_test_sources
546552
INTERFACE
553+
filter/tests/test-clp_s-bitmap_view.cpp
547554
filter/tests/test-clp_s-bloom_filter.cpp
548555
filter/tests/test-clp_s-xxhash.cpp
549556
tests/clp_s_test_utils.cpp

components/core/src/clp_s/CommandLineArguments.cpp

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
#include <iostream>
66
#include <optional>
77
#include <string_view>
8+
#include <variant>
89

910
#include <boost/program_options.hpp>
1011
#include <fmt/format.h>
1112
#include <spdlog/spdlog.h>
1213

14+
#include <clp_s/search/ast/SearchUtils.hpp>
15+
1316
#include "../clp/type_utils.hpp"
1417
#include "../reducer/types.hpp"
1518
#include "FileReader.hpp"
@@ -772,15 +775,25 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) {
772775
// clang-format on
773776
search_options.add(match_options);
774777

778+
int64_t count_by_time_bucket_size_millisecs{};
779+
std::string aggregation_field;
775780
po::options_description aggregation_options("Aggregation Controls");
776781
// clang-format off
777782
aggregation_options.add_options()(
778783
"count",
779784
"Count the number of results"
780785
)(
781786
"count-by-time",
782-
po::value<int64_t>(&m_count_by_time_bucket_size_ms)->value_name("SIZE"),
787+
po::value<int64_t>(&count_by_time_bucket_size_millisecs)->value_name("SIZE"),
783788
"Count the number of results in each time span of the given size (ms)"
789+
)(
790+
"min",
791+
po::value<std::string>(&aggregation_field)->value_name("FIELD"),
792+
"Find the minimum value of the given field"
793+
)(
794+
"max",
795+
po::value<std::string>(&aggregation_field)->value_name("FIELD"),
796+
"Find the maximum value of the given field"
784797
);
785798
// clang-format on
786799
search_options.add(aggregation_options);
@@ -952,6 +965,14 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) {
952965
<< std::endl;
953966
std::cerr << " " << m_program_name << R"( s archives-dir "level: INFO")"
954967
<< " --count" << std::endl;
968+
std::cerr << std::endl;
969+
970+
std::cerr << " # Search archives in archives-dir for logs matching a KQL query"
971+
R"( "level: INFO" and output the maximum value of field "latency" to)"
972+
" stdout"
973+
<< std::endl;
974+
std::cerr << " " << m_program_name << R"( s archives-dir "level: INFO")"
975+
<< " --max latency" << std::endl;
955976

956977
po::options_description visible_options;
957978
visible_options.add(general_options);
@@ -1026,9 +1047,10 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) {
10261047
throw std::invalid_argument("clp-s only supports one output handler at a time");
10271048
}
10281049

1029-
m_aggregation_type = parse_aggregation_options(
1050+
m_aggregator = parse_aggregation_options(
10301051
parsed_command_line_options,
1031-
m_count_by_time_bucket_size_ms
1052+
count_by_time_bucket_size_millisecs,
1053+
aggregation_field
10321054
);
10331055

10341056
for (auto const& [output_handler_name, output_handler_options] : output_options_map) {
@@ -1122,31 +1144,50 @@ void CommandLineArguments::parse_network_dest_output_handler_options(
11221144

11231145
auto CommandLineArguments::parse_aggregation_options(
11241146
po::variables_map const& parsed_options,
1125-
int64_t count_by_time_bucket_size_ms
1126-
) -> std::optional<AggregationType> {
1127-
std::optional<AggregationType> aggregation_type;
1128-
if (parsed_options.count("count")) {
1129-
aggregation_type = AggregationType::Count;
1130-
}
1131-
if (parsed_options.count("count-by-time")) {
1132-
if (aggregation_type.has_value()) {
1147+
int64_t count_by_time_bucket_size_millisecs,
1148+
std::string_view aggregation_field
1149+
) -> std::optional<Aggregator> {
1150+
std::optional<Aggregator> aggregator;
1151+
auto const set_aggregator = [&](Aggregator value) {
1152+
if (aggregator.has_value()) {
11331153
throw std::invalid_argument(
1134-
"The --count-by-time and --count options are mutually exclusive."
1154+
"The --count, --count-by-time, --min, and --max options are mutually exclusive."
11351155
);
11361156
}
1157+
aggregator = std::move(value);
1158+
};
1159+
auto const validate_aggregation_field = [&]() {
1160+
if (aggregation_field.empty()) {
1161+
throw std::invalid_argument("The --min and --max options require a field.");
1162+
}
1163+
if (search::ast::has_unescaped_wildcards(aggregation_field)) {
1164+
throw std::invalid_argument("The --min and --max field must not contain wildcards.");
1165+
}
1166+
};
11371167

1138-
if (count_by_time_bucket_size_ms <= 0) {
1168+
if (parsed_options.count("count")) {
1169+
set_aggregator(CountAggregator{});
1170+
}
1171+
if (parsed_options.count("count-by-time")) {
1172+
if (count_by_time_bucket_size_millisecs <= 0) {
11391173
throw std::invalid_argument("Value for count-by-time must be greater than zero.");
11401174
}
1141-
1142-
aggregation_type = AggregationType::CountByTime;
1175+
set_aggregator(CountByTimeAggregator{count_by_time_bucket_size_millisecs});
1176+
}
1177+
if (parsed_options.count("min")) {
1178+
validate_aggregation_field();
1179+
set_aggregator(MinMaxAggregator{false, aggregation_field});
11431180
}
1144-
return aggregation_type;
1181+
if (parsed_options.count("max")) {
1182+
validate_aggregation_field();
1183+
set_aggregator(MinMaxAggregator{true, aggregation_field});
1184+
}
1185+
return aggregator;
11451186
}
11461187

11471188
auto CommandLineArguments::reject_aggregation_for_handler(std::string_view handler_name) const
11481189
-> void {
1149-
if (m_aggregation_type.has_value()) {
1190+
if (m_aggregator.has_value()) {
11501191
throw std::invalid_argument(
11511192
fmt::format("The {} output handler does not support aggregations.", handler_name)
11521193
);
@@ -1183,7 +1224,9 @@ void CommandLineArguments::parse_reducer_output_handler_options(
11831224
throw std::invalid_argument("job-id cannot be negative.");
11841225
}
11851226

1186-
if (false == m_aggregation_type.has_value()) {
1227+
if (false == m_aggregator.has_value()
1228+
|| std::holds_alternative<MinMaxAggregator>(m_aggregator.value()))
1229+
{
11871230
throw std::invalid_argument(
11881231
"The reducer output handler currently only supports count and count-by-time"
11891232
" aggregations."

0 commit comments

Comments
 (0)