2023-10-20 14:09:41 -05:00
package cloudwatch
import (
"regexp"
2024-02-07 06:53:05 -06:00
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
2023-10-20 14:09:41 -05:00
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
)
// nonWordRegex is for spliting the expressions to just functions and ids
var nonWordRegex = regexp . MustCompile ( ` \W+ ` )
// getMetricQueryBatches separates queries into batches if necessary. Metric Insight queries cannot run together, and math expressions must be run
// with all the queries they reference.
func getMetricQueryBatches ( queries [ ] * models . CloudWatchQuery , logger log . Logger ) [ ] [ ] * models . CloudWatchQuery {
2023-11-29 15:43:34 -06:00
if ! hasMultipleMetricInsights ( queries ) {
2023-10-20 14:09:41 -05:00
return [ ] [ ] * models . CloudWatchQuery { queries }
}
logger . Debug ( "Separating queries into batches" )
2023-11-29 15:43:34 -06:00
// set up list of math expression queries since below we loop over them to get what query IDs they reference
var mathQueries [ ] * models . CloudWatchQuery
for _ , query := range queries {
if query . GetGetMetricDataAPIMode ( ) == models . GMDApiModeMathExpression {
mathQueries = append ( mathQueries , query )
2023-10-20 14:09:41 -05:00
}
}
2023-11-29 15:43:34 -06:00
// put queries into a set in order to facilitate lookup below
idToQuery := make ( map [ string ] * models . CloudWatchQuery , len ( queries ) )
for _ , q := range queries {
idToQuery [ q . Id ] = q
}
// gets query IDs which are referenced in math expressions
mathQueryIdToReferences := make ( map [ string ] [ ] * models . CloudWatchQuery )
// we will use this set of referenced queries to determine the root queries below
referencedQueries := make ( map [ string ] bool )
for _ , mathQuery := range mathQueries {
substrings := nonWordRegex . Split ( mathQuery . Expression , - 1 )
for _ , id := range substrings {
query , found := idToQuery [ id ]
2023-10-20 14:09:41 -05:00
if found {
2023-11-29 15:43:34 -06:00
mathQueryIdToReferences [ mathQuery . Id ] = append ( mathQueryIdToReferences [ mathQuery . Id ] , query )
referencedQueries [ query . Id ] = true
2023-10-20 14:09:41 -05:00
}
}
}
batches := [ ] [ ] * models . CloudWatchQuery { }
2023-11-29 15:43:34 -06:00
for _ , query := range queries {
// if a query is not referenced, then it is a "root" query
if _ , ok := referencedQueries [ query . Id ] ; ! ok {
batches = append ( batches , getConnectedQueries ( query , mathQueryIdToReferences ) )
2023-10-20 14:09:41 -05:00
}
}
return batches
}
2023-11-29 15:43:34 -06:00
// getConnectedQueries does a breadth-first search to find all the query ids connected to the root id by references. The root id is also returned in the response.
func getConnectedQueries ( root * models . CloudWatchQuery , queryReferences map [ string ] [ ] * models . CloudWatchQuery ) [ ] * models . CloudWatchQuery {
visited := map [ string ] bool { root . Id : true }
queriesToReturn := [ ] * models . CloudWatchQuery { }
2023-10-20 14:09:41 -05:00
2023-11-29 15:43:34 -06:00
queriesToVisit := [ ] * models . CloudWatchQuery { root }
for i := 0 ; i < len ( queriesToVisit ) ; i ++ {
currentQuery := queriesToVisit [ i ]
queriesToReturn = append ( queriesToReturn , currentQuery )
for _ , queryRef := range queryReferences [ currentQuery . Id ] {
if ! visited [ queryRef . Id ] {
visited [ queryRef . Id ] = true
queriesToVisit = append ( queriesToVisit , queryRef )
2023-10-20 14:09:41 -05:00
}
}
}
2023-11-29 15:43:34 -06:00
return queriesToReturn
}
func hasMultipleMetricInsights ( queries [ ] * models . CloudWatchQuery ) bool {
count := 0
for _ , query := range queries {
if query . GetGetMetricDataAPIMode ( ) == models . GMDApiModeSQLExpression {
count ++
}
if count > 1 {
return true
}
}
return false
2023-10-20 14:09:41 -05:00
}