github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/api/v1/handler/graphite/find.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 "errors" 25 "net/http" 26 "sort" 27 "sync" 28 29 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions" 30 "github.com/m3db/m3/src/query/api/v1/options" 31 "github.com/m3db/m3/src/query/api/v1/route" 32 "github.com/m3db/m3/src/query/graphite/graphite" 33 graphitestorage "github.com/m3db/m3/src/query/graphite/storage" 34 "github.com/m3db/m3/src/query/storage/m3/consolidators" 35 "github.com/m3db/m3/src/query/util/logging" 36 xerrors "github.com/m3db/m3/src/x/errors" 37 "github.com/m3db/m3/src/x/instrument" 38 xhttp "github.com/m3db/m3/src/x/net/http" 39 40 "go.uber.org/zap" 41 ) 42 43 const ( 44 // FindURL is the url for finding graphite metrics. 45 FindURL = route.Prefix + "/graphite/metrics/find" 46 ) 47 48 // FindHTTPMethods are the HTTP methods for this handler. 49 var FindHTTPMethods = []string{http.MethodGet, http.MethodPost} 50 51 type grahiteFindHandler struct { 52 storage graphitestorage.Storage 53 graphiteStorageOpts graphitestorage.M3WrappedStorageOptions 54 fetchOptionsBuilder handleroptions.FetchOptionsBuilder 55 instrumentOpts instrument.Options 56 } 57 58 // NewFindHandler returns a new instance of handler. 59 func NewFindHandler(opts options.HandlerOptions) http.Handler { 60 wrappedStore := graphitestorage.NewM3WrappedStorage(opts.Storage(), 61 opts.M3DBOptions(), opts.InstrumentOpts(), opts.GraphiteStorageOptions()) 62 return &grahiteFindHandler{ 63 storage: wrappedStore, 64 graphiteStorageOpts: opts.GraphiteStorageOptions(), 65 fetchOptionsBuilder: opts.GraphiteFindFetchOptionsBuilder(), 66 instrumentOpts: opts.InstrumentOpts(), 67 } 68 } 69 70 func mergeTags( 71 terminatedResult *consolidators.CompleteTagsResult, 72 childResult *consolidators.CompleteTagsResult, 73 ) (map[string]nodeDescriptor, error) { 74 var ( 75 terminatedResultTags = 0 76 childResultTags = 0 77 ) 78 79 // Sanity check the queries aren't complete name only queries. 80 if terminatedResult != nil { 81 terminatedResultTags = len(terminatedResult.CompletedTags) 82 if terminatedResult.CompleteNameOnly { 83 return nil, errors.New("terminated result is completing name only") 84 } 85 } 86 87 if childResult != nil { 88 childResultTags = len(childResult.CompletedTags) 89 if childResult.CompleteNameOnly { 90 return nil, errors.New("child result is completing name only") 91 } 92 } 93 94 size := terminatedResultTags + childResultTags 95 tagMap := make(map[string]nodeDescriptor, size) 96 if terminatedResult != nil { 97 for _, tag := range terminatedResult.CompletedTags { 98 for _, value := range tag.Values { 99 descriptor := tagMap[string(value)] 100 descriptor.isLeaf = true 101 tagMap[string(value)] = descriptor 102 } 103 } 104 } 105 106 if childResult != nil { 107 for _, tag := range childResult.CompletedTags { 108 for _, value := range tag.Values { 109 descriptor := tagMap[string(value)] 110 descriptor.hasChildren = true 111 tagMap[string(value)] = descriptor 112 } 113 } 114 } 115 116 return tagMap, nil 117 } 118 119 func findResultsSorted(prefix string, tagMap map[string]nodeDescriptor) []findResult { 120 results := make([]findResult, 0, len(tagMap)) 121 for name, node := range tagMap { 122 results = append(results, findResult{ 123 id: prefix + name, 124 name: name, 125 node: node, 126 }) 127 } 128 sort.Slice(results, func(i, j int) bool { 129 if results[i].id != results[j].id { 130 return results[i].id < results[j].id 131 } 132 if results[i].node.hasChildren && !results[j].node.hasChildren { 133 return true 134 } 135 if results[i].node.isLeaf && !results[j].node.isLeaf { 136 return true 137 } 138 return false 139 }) 140 return results 141 } 142 143 func (h *grahiteFindHandler) ServeHTTP( 144 w http.ResponseWriter, 145 r *http.Request, 146 ) { 147 ctx, opts, err := h.fetchOptionsBuilder.NewFetchOptions(r.Context(), r) 148 if err != nil { 149 xhttp.WriteError(w, err) 150 return 151 } 152 153 logger := logging.WithContext(ctx, h.instrumentOpts) 154 w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) 155 156 // NB: need to run two separate queries, one of which will match only the 157 // provided matchers, and one which will match the provided matchers with at 158 // least one more child node. For further information, refer to the comment 159 // for parseFindParamsToQueries. 160 terminatedQuery, childQuery, raw, err := parseFindParamsToQueries(r) 161 if err != nil { 162 xhttp.WriteError(w, err) 163 return 164 } 165 166 var ( 167 terminatedResult *consolidators.CompleteTagsResult 168 tErr error 169 childResult *consolidators.CompleteTagsResult 170 cErr error 171 wg sync.WaitGroup 172 ) 173 if terminatedQuery != nil { 174 // Sometimes we only perform the child query, so only perform 175 // terminated query if not nil. 176 wg.Add(1) 177 go func() { 178 terminatedResult, tErr = h.storage.CompleteTags(ctx, terminatedQuery, opts) 179 wg.Done() 180 }() 181 } 182 // Always perform child query. 183 wg.Add(1) 184 go func() { 185 childResult, cErr = h.storage.CompleteTags(ctx, childQuery, opts) 186 wg.Done() 187 }() 188 189 wg.Wait() 190 191 if err := xerrors.FirstError(tErr, cErr); err != nil { 192 logger.Error("unable to find search", zap.Error(err)) 193 xhttp.WriteError(w, err) 194 return 195 } 196 197 meta := childResult.Metadata 198 if terminatedResult != nil { 199 meta = terminatedResult.Metadata.CombineMetadata(childResult.Metadata) 200 } 201 202 // NB: merge results from both queries to specify which series have children 203 seenMap, err := mergeTags(terminatedResult, childResult) 204 if err != nil { 205 logger.Error("unable to find merge", zap.Error(err)) 206 xhttp.WriteError(w, err) 207 return 208 } 209 210 prefix := graphite.DropLastMetricPart(raw) 211 if len(prefix) > 0 { 212 prefix += "." 213 } 214 215 results := findResultsSorted(prefix, seenMap) 216 217 err = handleroptions.AddDBResultResponseHeaders(w, meta, opts) 218 if err != nil { 219 logger.Error("unable to render find header", zap.Error(err)) 220 xhttp.WriteError(w, err) 221 return 222 } 223 224 // TODO: Support multiple result types 225 resultOpts := findResultsOptions{ 226 includeBothExpandableAndLeaf: h.graphiteStorageOpts.FindResultsIncludeBothExpandableAndLeaf, 227 } 228 if err := findResultsJSON(w, results, resultOpts); err != nil { 229 logger.Error("unable to render find results", zap.Error(err)) 230 } 231 }