github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/api/v1/handler/graphite/find_parser.go (about) 1 // Copyright (c) 2019 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package graphite 22 23 import ( 24 "fmt" 25 "io" 26 "net/http" 27 "strings" 28 "time" 29 30 "github.com/m3db/m3/src/query/errors" 31 "github.com/m3db/m3/src/query/graphite/graphite" 32 graphitestorage "github.com/m3db/m3/src/query/graphite/storage" 33 "github.com/m3db/m3/src/query/models" 34 "github.com/m3db/m3/src/query/storage" 35 "github.com/m3db/m3/src/query/util/json" 36 xerrors "github.com/m3db/m3/src/x/errors" 37 xtime "github.com/m3db/m3/src/x/time" 38 ) 39 40 // parseFindParamsToQueries parses an incoming request to two find queries, 41 // which are then combined to give the final result. 42 // It returns, in order: 43 // 44 // the given query; this will return all values for exactly that tag which have 45 // 46 // _terminatedQuery, which adds an explicit terminator after the last term in 47 // 48 // no child nodes 49 // 50 // _childQuery, which adds an explicit match all after the last term in the 51 // given query; this will return all values for exactly that tag which have at 52 // least one child node. 53 // _rawQueryString, which is the initial query request (bar final 54 // matcher), which is used to reconstruct the return values. 55 // _err, any error encountered during parsing. 56 // 57 // As an example, given the query `a.b*`, and metrics `a.bar.c` and `a.biz`, 58 // terminatedQuery will return only [biz], and childQuery will return only 59 // [bar]. 60 func parseFindParamsToQueries(r *http.Request) ( 61 _terminatedQuery *storage.CompleteTagsQuery, 62 _childQuery *storage.CompleteTagsQuery, 63 _rawQueryString string, 64 _err error, 65 ) { 66 query := r.FormValue("query") 67 if query == "" { 68 return nil, nil, "", 69 xerrors.NewInvalidParamsError(errors.ErrNoQueryFound) 70 } 71 72 now := time.Now() 73 fromString, untilString := r.FormValue("from"), r.FormValue("until") 74 if len(fromString) == 0 { 75 fromString = "0" 76 } 77 78 if len(untilString) == 0 { 79 untilString = "now" 80 } 81 82 from, err := graphite.ParseTime( 83 fromString, 84 now, 85 tzOffsetForAbsoluteTime, 86 ) 87 if err != nil { 88 return nil, nil, "", 89 xerrors.NewInvalidParamsError(fmt.Errorf("invalid 'from': %s", fromString)) 90 } 91 92 until, err := graphite.ParseTime( 93 untilString, 94 now, 95 tzOffsetForAbsoluteTime, 96 ) 97 if err != nil { 98 return nil, nil, "", 99 xerrors.NewInvalidParamsError(fmt.Errorf("invalid 'until': %s", untilString)) 100 } 101 102 matchers, queryType, err := graphitestorage.TranslateQueryToMatchersWithTerminator(query) 103 if err != nil { 104 return nil, nil, "", 105 xerrors.NewInvalidParamsError(fmt.Errorf("invalid 'query': %s", query)) 106 } 107 108 switch queryType { 109 case graphitestorage.StarStarUnterminatedTranslatedQuery: 110 // Translated query for "**" has unterminated search for children 111 // terms, we are only going to do a single search and assume all 112 // results that come back have also children in the graphite path 113 // tree (since it's very expensive to check if each result that comes 114 // back is a child or leaf node and "**" in a find query is typically 115 // only used for template variables rather than searching for metric 116 // results, which is the only use case isLeaf/hasChildren is useful). 117 // Note: Filter to all graphite tags that appears at the last node 118 // or greater than that (we use 100 as an arbitrary upper bound). 119 maxPathIndexes := 100 120 filter := make([][]byte, 0, maxPathIndexes) 121 parts := 1 + strings.Count(query, ".") 122 firstPathIndex := parts - 1 123 for i := firstPathIndex; i < firstPathIndex+maxPathIndexes; i++ { 124 filter = append(filter, graphite.TagName(i)) 125 } 126 childQuery := &storage.CompleteTagsQuery{ 127 CompleteNameOnly: false, 128 FilterNameTags: filter, 129 TagMatchers: matchers, 130 Start: xtime.ToUnixNano(from), 131 End: xtime.ToUnixNano(until), 132 } 133 return nil, childQuery, query, nil 134 case graphitestorage.TerminatedTranslatedQuery: 135 // Default type of translated query, explicitly craft queries for 136 // a terminated part of the query and a child part of the query. 137 break 138 default: 139 return nil, nil, "", fmt.Errorf("unknown query type: %v", queryType) 140 } 141 142 // NB: Filter will always be the second last term in the matchers, and the 143 // matchers should always have a length of at least 2 (term + terminator) 144 // so this is a sanity check and unexpected in actual execution. 145 if len(matchers) < 2 { 146 return nil, nil, "", 147 xerrors.NewInvalidParamsError(fmt.Errorf("unable to parse 'query': %s", query)) 148 } 149 150 filter := [][]byte{matchers[len(matchers)-2].Name} 151 terminatedQuery := &storage.CompleteTagsQuery{ 152 CompleteNameOnly: false, 153 FilterNameTags: filter, 154 TagMatchers: matchers, 155 Start: xtime.ToUnixNano(from), 156 End: xtime.ToUnixNano(until), 157 } 158 159 clonedMatchers := make([]models.Matcher, len(matchers)) 160 copy(clonedMatchers, matchers) 161 // NB: change terminator from `MatchNotField` to `MatchField` to ensure 162 // segments with children are matched. 163 clonedMatchers[len(clonedMatchers)-1].Type = models.MatchField 164 childQuery := &storage.CompleteTagsQuery{ 165 CompleteNameOnly: false, 166 FilterNameTags: filter, 167 TagMatchers: clonedMatchers, 168 Start: xtime.ToUnixNano(from), 169 End: xtime.ToUnixNano(until), 170 } 171 172 return terminatedQuery, childQuery, query, nil 173 } 174 175 type findResultsOptions struct { 176 includeBothExpandableAndLeaf bool 177 } 178 179 func findResultsJSON( 180 w io.Writer, 181 results []findResult, 182 opts findResultsOptions, 183 ) error { 184 jw := json.NewWriter(w) 185 jw.BeginArray() 186 187 for _, result := range results { 188 writeFindNodeResultJSON(jw, result, opts) 189 } 190 191 jw.EndArray() 192 return jw.Close() 193 } 194 195 type findResult struct { 196 id string 197 name string 198 node nodeDescriptor 199 } 200 201 type nodeDescriptor struct { 202 hasChildren bool 203 isLeaf bool 204 } 205 206 func writeFindNodeResultJSON( 207 jw json.Writer, 208 result findResult, 209 opts findResultsOptions, 210 ) { 211 descriptor := result.node 212 213 // Include the leaf node only if no leaf was specified or 214 // if config optionally sets that both should come back. 215 // The default behavior matches graphite web. 216 includeLeafNode := (descriptor.isLeaf && !descriptor.hasChildren) || 217 (descriptor.isLeaf && 218 descriptor.hasChildren && 219 opts.includeBothExpandableAndLeaf) 220 if includeLeafNode { 221 writeFindResultJSON(jw, result.id, result.name, false) 222 } 223 224 if descriptor.hasChildren { 225 writeFindResultJSON(jw, result.id, result.name, true) 226 } 227 } 228 229 func writeFindResultJSON( 230 jw json.Writer, 231 id string, 232 value string, 233 hasChildren bool, 234 ) { 235 leaf := 1 236 if hasChildren { 237 leaf = 0 238 } 239 240 jw.BeginObject() 241 242 jw.BeginObjectField("id") 243 jw.WriteString(id) 244 245 jw.BeginObjectField("text") 246 jw.WriteString(value) 247 248 jw.BeginObjectField("leaf") 249 jw.WriteInt(leaf) 250 251 jw.BeginObjectField("expandable") 252 jw.WriteInt(1 - leaf) 253 254 jw.BeginObjectField("allowChildren") 255 jw.WriteInt(1 - leaf) 256 257 jw.EndObject() 258 }