github.com/m3db/m3@v1.5.0/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 // the given query; this will return all values for exactly that tag which have 44 // _terminatedQuery, which adds an explicit terminator after the last term in 45 // no child nodes 46 // _childQuery, which adds an explicit match all after the last term in the 47 // given query; this will return all values for exactly that tag which have at 48 // least one child node. 49 // _rawQueryString, which is the initial query request (bar final 50 // matcher), which is used to reconstruct the return values. 51 // _err, any error encountered during parsing. 52 // 53 // As an example, given the query `a.b*`, and metrics `a.bar.c` and `a.biz`, 54 // terminatedQuery will return only [biz], and childQuery will return only 55 // [bar]. 56 func parseFindParamsToQueries(r *http.Request) ( 57 _terminatedQuery *storage.CompleteTagsQuery, 58 _childQuery *storage.CompleteTagsQuery, 59 _rawQueryString string, 60 _err error, 61 ) { 62 query := r.FormValue("query") 63 if query == "" { 64 return nil, nil, "", 65 xerrors.NewInvalidParamsError(errors.ErrNoQueryFound) 66 } 67 68 now := time.Now() 69 fromString, untilString := r.FormValue("from"), r.FormValue("until") 70 if len(fromString) == 0 { 71 fromString = "0" 72 } 73 74 if len(untilString) == 0 { 75 untilString = "now" 76 } 77 78 from, err := graphite.ParseTime( 79 fromString, 80 now, 81 tzOffsetForAbsoluteTime, 82 ) 83 84 if err != nil { 85 return nil, nil, "", 86 xerrors.NewInvalidParamsError(fmt.Errorf("invalid 'from': %s", fromString)) 87 } 88 89 until, err := graphite.ParseTime( 90 untilString, 91 now, 92 tzOffsetForAbsoluteTime, 93 ) 94 95 if err != nil { 96 return nil, nil, "", 97 xerrors.NewInvalidParamsError(fmt.Errorf("invalid 'until': %s", untilString)) 98 } 99 100 matchers, queryType, err := graphitestorage.TranslateQueryToMatchersWithTerminator(query) 101 if err != nil { 102 return nil, nil, "", 103 xerrors.NewInvalidParamsError(fmt.Errorf("invalid 'query': %s", query)) 104 } 105 106 switch queryType { 107 case graphitestorage.StarStarUnterminatedTranslatedQuery: 108 // Translated query for "**" has unterminated search for children 109 // terms, we are only going to do a single search and assume all 110 // results that come back have also children in the graphite path 111 // tree (since it's very expensive to check if each result that comes 112 // back is a child or leaf node and "**" in a find query is typically 113 // only used for template variables rather than searching for metric 114 // results, which is the only use case isLeaf/hasChildren is useful). 115 // Note: Filter to all graphite tags that appears at the last node 116 // or greater than that (we use 100 as an arbitrary upper bound). 117 maxPathIndexes := 100 118 filter := make([][]byte, 0, maxPathIndexes) 119 parts := 1 + strings.Count(query, ".") 120 firstPathIndex := parts - 1 121 for i := firstPathIndex; i < firstPathIndex+maxPathIndexes; i++ { 122 filter = append(filter, graphite.TagName(i)) 123 } 124 childQuery := &storage.CompleteTagsQuery{ 125 CompleteNameOnly: false, 126 FilterNameTags: filter, 127 TagMatchers: matchers, 128 Start: xtime.ToUnixNano(from), 129 End: xtime.ToUnixNano(until), 130 } 131 return nil, childQuery, query, nil 132 case graphitestorage.TerminatedTranslatedQuery: 133 // Default type of translated query, explicitly craft queries for 134 // a terminated part of the query and a child part of the query. 135 break 136 default: 137 return nil, nil, "", fmt.Errorf("unknown query type: %v", queryType) 138 } 139 140 // NB: Filter will always be the second last term in the matchers, and the 141 // matchers should always have a length of at least 2 (term + terminator) 142 // so this is a sanity check and unexpected in actual execution. 143 if len(matchers) < 2 { 144 return nil, nil, "", 145 xerrors.NewInvalidParamsError(fmt.Errorf("unable to parse 'query': %s", query)) 146 } 147 148 filter := [][]byte{matchers[len(matchers)-2].Name} 149 terminatedQuery := &storage.CompleteTagsQuery{ 150 CompleteNameOnly: false, 151 FilterNameTags: filter, 152 TagMatchers: matchers, 153 Start: xtime.ToUnixNano(from), 154 End: xtime.ToUnixNano(until), 155 } 156 157 clonedMatchers := make([]models.Matcher, len(matchers)) 158 copy(clonedMatchers, matchers) 159 // NB: change terminator from `MatchNotField` to `MatchField` to ensure 160 // segments with children are matched. 161 clonedMatchers[len(clonedMatchers)-1].Type = models.MatchField 162 childQuery := &storage.CompleteTagsQuery{ 163 CompleteNameOnly: false, 164 FilterNameTags: filter, 165 TagMatchers: clonedMatchers, 166 Start: xtime.ToUnixNano(from), 167 End: xtime.ToUnixNano(until), 168 } 169 170 return terminatedQuery, childQuery, query, nil 171 } 172 173 type findResultsOptions struct { 174 includeBothExpandableAndLeaf bool 175 } 176 177 func findResultsJSON( 178 w io.Writer, 179 prefix string, 180 tags map[string]nodeDescriptor, 181 opts findResultsOptions, 182 ) error { 183 jw := json.NewWriter(w) 184 jw.BeginArray() 185 186 for value, descriptor := range tags { 187 writeFindNodeResultJSON(jw, prefix, value, descriptor, opts) 188 } 189 190 jw.EndArray() 191 return jw.Close() 192 } 193 194 func writeFindNodeResultJSON( 195 jw json.Writer, 196 prefix string, 197 value string, 198 descriptor nodeDescriptor, 199 opts findResultsOptions, 200 ) { 201 id := fmt.Sprintf("%s%s", prefix, value) 202 203 // Include the leaf node only if no leaf was specified or 204 // if config optionally sets that both should come back. 205 // The default behavior matches graphite web. 206 includeLeafNode := (descriptor.isLeaf && !descriptor.hasChildren) || 207 (descriptor.isLeaf && 208 descriptor.hasChildren && 209 opts.includeBothExpandableAndLeaf) 210 if includeLeafNode { 211 writeFindResultJSON(jw, id, value, false) 212 } 213 214 if descriptor.hasChildren { 215 writeFindResultJSON(jw, id, value, true) 216 } 217 } 218 219 func writeFindResultJSON( 220 jw json.Writer, 221 id string, 222 value string, 223 hasChildren bool, 224 ) { 225 leaf := 1 226 if hasChildren { 227 leaf = 0 228 } 229 230 jw.BeginObject() 231 232 jw.BeginObjectField("id") 233 jw.WriteString(id) 234 235 jw.BeginObjectField("text") 236 jw.WriteString(value) 237 238 jw.BeginObjectField("leaf") 239 jw.WriteInt(leaf) 240 241 jw.BeginObjectField("expandable") 242 jw.WriteInt(1 - leaf) 243 244 jw.BeginObjectField("allowChildren") 245 jw.WriteInt(1 - leaf) 246 247 jw.EndObject() 248 }