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