88
99package org .opensearch .plugin .insights .core .reader ;
1010
11- import static org .opensearch .plugin .insights .core .utils .ExporterReaderUtils .generateLocalIndexDateHash ;
12- import static org .opensearch .plugin .insights .rules .model .SearchQueryRecord .VERBOSE_ONLY_FIELDS ;
13-
1411import java .time .ZoneOffset ;
1512import java .time .ZonedDateTime ;
1613import java .time .format .DateTimeFormatter ;
1714import java .util .ArrayList ;
18- import java .util .Arrays ;
15+ import java .util .Collections ;
1916import java .util .List ;
2017import org .apache .logging .log4j .LogManager ;
2118import org .apache .logging .log4j .Logger ;
2219import org .opensearch .action .search .SearchRequest ;
2320import org .opensearch .action .search .SearchResponse ;
2421import org .opensearch .client .Client ;
25- import org .opensearch .common .xcontent .LoggingDeprecationHandler ;
26- import org .opensearch .common .xcontent .XContentType ;
27- import org .opensearch .core .common .Strings ;
22+ import org .opensearch .core .action .ActionListener ;
2823import org .opensearch .core .xcontent .NamedXContentRegistry ;
29- import org .opensearch .core .xcontent .XContentParser ;
30- import org .opensearch .index .IndexNotFoundException ;
31- import org .opensearch .index .query .BoolQueryBuilder ;
32- import org .opensearch .index .query .MatchQueryBuilder ;
33- import org .opensearch .index .query .QueryBuilders ;
34- import org .opensearch .index .query .RangeQueryBuilder ;
3524import org .opensearch .plugin .insights .core .metrics .OperationalMetric ;
3625import org .opensearch .plugin .insights .core .metrics .OperationalMetricsCounter ;
37- import org .opensearch .plugin .insights .rules .model .Attribute ;
26+ import org .opensearch .plugin .insights .core .utils .IndexDiscoveryHelper ;
27+ import org .opensearch .plugin .insights .core .utils .QueryInsightsQueryBuilder ;
28+ import org .opensearch .plugin .insights .core .utils .SearchResponseParser ;
29+ import org .opensearch .plugin .insights .rules .model .MetricType ;
3830import org .opensearch .plugin .insights .rules .model .SearchQueryRecord ;
39- import org .opensearch .search .SearchHit ;
40- import org .opensearch .search .builder .SearchSourceBuilder ;
41- import org .opensearch .search .sort .SortBuilders ;
42- import org .opensearch .search .sort .SortOrder ;
31+ import reactor .util .annotation .NonNull ;
4332
4433/**
4534 * Local index reader for reading query insights data from local OpenSearch indices.
@@ -102,64 +91,83 @@ public LocalIndexReader setIndexPattern(DateTimeFormatter indexPattern) {
10291 /**
10392 * Export a list of SearchQueryRecord from local index
10493 *
105- * @param from start timestamp
106- * @param to end timestamp
107- * @param id query/group id
108- * @param verbose whether to return full output
109- * @return list of SearchQueryRecords whose timestamps fall between from and to
94+ * @param from start timestamp
95+ * @param to end timestamp
96+ * @param id query/group id
97+ * @param verbose whether to return full output
98+ * @param metricType metric type to read
11099 */
111100 @ Override
112- public List <SearchQueryRecord > read (final String from , final String to , final String id , final Boolean verbose ) {
113- List <SearchQueryRecord > records = new ArrayList <>();
101+ public void read (
102+ final String from ,
103+ final String to ,
104+ final String id ,
105+ final Boolean verbose ,
106+ @ NonNull final MetricType metricType ,
107+ final ActionListener <List <SearchQueryRecord >> listener
108+ ) {
109+ // Validate input parameters
114110 if (from == null || to == null ) {
115- return records ;
111+ listener .onResponse (new ArrayList <>());
112+ return ;
116113 }
114+
115+ // Parse and validate date range
117116 final ZonedDateTime start = ZonedDateTime .parse (from );
118117 ZonedDateTime end = ZonedDateTime .parse (to );
119118 ZonedDateTime now = ZonedDateTime .now (ZoneOffset .UTC );
120119 if (end .isAfter (now )) {
121120 end = now ;
122121 }
123- ZonedDateTime curr = start ;
124- // TODO: send single search request instead of one per index
125- while (curr .isBefore (end .plusDays (1 ).toLocalDate ().atStartOfDay (end .getZone ()))) {
126- String indexName = buildLocalIndexName (curr );
127- SearchRequest searchRequest = new SearchRequest (indexName );
128- SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder ().size (MAX_TOP_N_INDEX_READ_SIZE );
129- MatchQueryBuilder excludeQuery = QueryBuilders .matchQuery ("indices" , "top_queries*" );
130- RangeQueryBuilder rangeQuery = QueryBuilders .rangeQuery ("timestamp" )
131- .from (start .toInstant ().toEpochMilli ())
132- .to (end .toInstant ().toEpochMilli ());
133- BoolQueryBuilder query = QueryBuilders .boolQuery ().must (rangeQuery ).mustNot (excludeQuery );
134-
135- if (id != null ) {
136- query .must (QueryBuilders .matchQuery ("id" , id ));
122+ final ZonedDateTime finalEnd = end ;
123+
124+ // Discover indices in the date range
125+ IndexDiscoveryHelper .discoverIndicesInDateRange (client , indexPattern , start , finalEnd , new ActionListener <List <String >>() {
126+ @ Override
127+ public void onResponse (List <String > indexNames ) {
128+ if (indexNames .isEmpty ()) {
129+ listener .onResponse (Collections .emptyList ());
130+ return ;
131+ }
132+
133+ // Build and execute search request
134+ executeSearchRequest (indexNames , start , finalEnd , id , verbose , metricType , listener );
137135 }
138- searchSourceBuilder .query (query );
139- if (Boolean .FALSE .equals (verbose )) {
140- // Exclude these fields
141- searchSourceBuilder .fetchSource (
142- Strings .EMPTY_ARRAY ,
143- Arrays .stream (VERBOSE_ONLY_FIELDS ).map (Attribute ::toString ).toArray (String []::new )
144- );
136+
137+ @ Override
138+ public void onFailure (Exception e ) {
139+ listener .onFailure (e );
145140 }
146- searchSourceBuilder .sort (SortBuilders .fieldSort ("measurements.latency.number" ).order (SortOrder .DESC ));
147- searchRequest .source (searchSourceBuilder );
148- try {
149- SearchResponse searchResponse = client .search (searchRequest ).actionGet ();
150- for (SearchHit hit : searchResponse .getHits ()) {
151- XContentParser parser = XContentType .JSON .xContent ()
152- .createParser (namedXContentRegistry , LoggingDeprecationHandler .INSTANCE , hit .getSourceAsString ());
153- SearchQueryRecord record = SearchQueryRecord .fromXContent (parser );
154- records .add (record );
155- }
156- } catch (IndexNotFoundException ignored ) {} catch (Exception e ) {
157- OperationalMetricsCounter .getInstance ().incrementCounter (OperationalMetric .LOCAL_INDEX_READER_PARSING_EXCEPTIONS );
158- logger .error ("Unable to parse search hit: " , e );
141+ });
142+ }
143+
144+ /**
145+ * Executes the search request against the discovered indices.
146+ */
147+ private void executeSearchRequest (
148+ final List <String > indexNames ,
149+ final ZonedDateTime start ,
150+ final ZonedDateTime end ,
151+ final String id ,
152+ final Boolean verbose ,
153+ final MetricType metricType ,
154+ final ActionListener <List <SearchQueryRecord >> listener
155+ ) {
156+ SearchRequest searchRequest = QueryInsightsQueryBuilder .buildTopNSearchRequest (indexNames , start , end , id , verbose , metricType );
157+
158+ client .search (searchRequest , new ActionListener <SearchResponse >() {
159+ @ Override
160+ public void onResponse (SearchResponse searchResponse ) {
161+ SearchResponseParser .parseSearchResponse (searchResponse , namedXContentRegistry , listener );
159162 }
160- curr = curr .plusDays (1 );
161- }
162- return records ;
163+
164+ @ Override
165+ public void onFailure (Exception e ) {
166+ OperationalMetricsCounter .getInstance ().incrementCounter (OperationalMetric .LOCAL_INDEX_READER_SEARCH_EXCEPTIONS );
167+ logger .error ("Failed to search indices {}: " , indexNames , e );
168+ listener .onFailure (e );
169+ }
170+ });
163171 }
164172
165173 /**
@@ -169,8 +177,4 @@ public List<SearchQueryRecord> read(final String from, final String to, final St
169177 public void close () {
170178 logger .debug ("Closing the LocalIndexReader.." );
171179 }
172-
173- private String buildLocalIndexName (ZonedDateTime current ) {
174- return current .format (indexPattern ) + "-" + generateLocalIndexDateHash (current .toLocalDate ());
175- }
176180}
0 commit comments