|
5 | 5 | #include <iostream> |
6 | 6 | #include <optional> |
7 | 7 | #include <string_view> |
| 8 | +#include <variant> |
8 | 9 |
|
9 | 10 | #include <boost/program_options.hpp> |
10 | 11 | #include <fmt/format.h> |
11 | 12 | #include <spdlog/spdlog.h> |
12 | 13 |
|
| 14 | +#include <clp_s/search/ast/SearchUtils.hpp> |
| 15 | + |
13 | 16 | #include "../clp/type_utils.hpp" |
14 | 17 | #include "../reducer/types.hpp" |
15 | 18 | #include "FileReader.hpp" |
@@ -772,15 +775,25 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { |
772 | 775 | // clang-format on |
773 | 776 | search_options.add(match_options); |
774 | 777 |
|
| 778 | + int64_t count_by_time_bucket_size_millisecs{}; |
| 779 | + std::string aggregation_field; |
775 | 780 | po::options_description aggregation_options("Aggregation Controls"); |
776 | 781 | // clang-format off |
777 | 782 | aggregation_options.add_options()( |
778 | 783 | "count", |
779 | 784 | "Count the number of results" |
780 | 785 | )( |
781 | 786 | "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"), |
783 | 788 | "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" |
784 | 797 | ); |
785 | 798 | // clang-format on |
786 | 799 | search_options.add(aggregation_options); |
@@ -952,6 +965,14 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { |
952 | 965 | << std::endl; |
953 | 966 | std::cerr << " " << m_program_name << R"( s archives-dir "level: INFO")" |
954 | 967 | << " --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; |
955 | 976 |
|
956 | 977 | po::options_description visible_options; |
957 | 978 | visible_options.add(general_options); |
@@ -1026,9 +1047,10 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { |
1026 | 1047 | throw std::invalid_argument("clp-s only supports one output handler at a time"); |
1027 | 1048 | } |
1028 | 1049 |
|
1029 | | - m_aggregation_type = parse_aggregation_options( |
| 1050 | + m_aggregator = parse_aggregation_options( |
1030 | 1051 | parsed_command_line_options, |
1031 | | - m_count_by_time_bucket_size_ms |
| 1052 | + count_by_time_bucket_size_millisecs, |
| 1053 | + aggregation_field |
1032 | 1054 | ); |
1033 | 1055 |
|
1034 | 1056 | for (auto const& [output_handler_name, output_handler_options] : output_options_map) { |
@@ -1122,31 +1144,50 @@ void CommandLineArguments::parse_network_dest_output_handler_options( |
1122 | 1144 |
|
1123 | 1145 | auto CommandLineArguments::parse_aggregation_options( |
1124 | 1146 | 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()) { |
1133 | 1153 | 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." |
1135 | 1155 | ); |
1136 | 1156 | } |
| 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 | + }; |
1137 | 1167 |
|
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) { |
1139 | 1173 | throw std::invalid_argument("Value for count-by-time must be greater than zero."); |
1140 | 1174 | } |
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}); |
1143 | 1180 | } |
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; |
1145 | 1186 | } |
1146 | 1187 |
|
1147 | 1188 | auto CommandLineArguments::reject_aggregation_for_handler(std::string_view handler_name) const |
1148 | 1189 | -> void { |
1149 | | - if (m_aggregation_type.has_value()) { |
| 1190 | + if (m_aggregator.has_value()) { |
1150 | 1191 | throw std::invalid_argument( |
1151 | 1192 | fmt::format("The {} output handler does not support aggregations.", handler_name) |
1152 | 1193 | ); |
@@ -1183,7 +1224,9 @@ void CommandLineArguments::parse_reducer_output_handler_options( |
1183 | 1224 | throw std::invalid_argument("job-id cannot be negative."); |
1184 | 1225 | } |
1185 | 1226 |
|
1186 | | - if (false == m_aggregation_type.has_value()) { |
| 1227 | + if (false == m_aggregator.has_value() |
| 1228 | + || std::holds_alternative<MinMaxAggregator>(m_aggregator.value())) |
| 1229 | + { |
1187 | 1230 | throw std::invalid_argument( |
1188 | 1231 | "The reducer output handler currently only supports count and count-by-time" |
1189 | 1232 | " aggregations." |
|
0 commit comments