github.com/netdata/go.d.plugin@v0.58.1/modules/solr/solr.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package solr 4 5 import ( 6 _ "embed" 7 "encoding/json" 8 "fmt" 9 "io" 10 "net/http" 11 "net/url" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/netdata/go.d.plugin/pkg/web" 17 18 "github.com/netdata/go.d.plugin/agent/module" 19 ) 20 21 //go:embed "config_schema.json" 22 var configSchema string 23 24 func init() { 25 module.Register("solr", module.Creator{ 26 JobConfigSchema: configSchema, 27 Create: func() module.Module { return New() }, 28 }) 29 } 30 31 const ( 32 defaultURL = "http://127.0.0.1:8983" 33 defaultHTTPTimeout = time.Second 34 ) 35 36 const ( 37 minSupportedVersion = 6.4 38 coresHandlersURLPath = "/solr/admin/metrics" 39 coresHandlersURLQuery = "group=core&prefix=UPDATE,QUERY&wt=json" 40 infoSystemURLPath = "/solr/admin/info/system" 41 infoSystemURLQuery = "wt=json" 42 ) 43 44 type infoSystem struct { 45 Lucene struct { 46 Version string `json:"solr-spec-version"` 47 } 48 } 49 50 // New creates Solr with default values 51 func New() *Solr { 52 config := Config{ 53 HTTP: web.HTTP{ 54 Request: web.Request{ 55 URL: defaultURL, 56 }, 57 Client: web.Client{ 58 Timeout: web.Duration{Duration: defaultHTTPTimeout}, 59 }, 60 }, 61 } 62 return &Solr{ 63 Config: config, 64 cores: make(map[string]bool), 65 } 66 } 67 68 // Config is the Solr module configuration. 69 type Config struct { 70 web.HTTP `yaml:",inline"` 71 } 72 73 // Solr solr module 74 type Solr struct { 75 module.Base 76 Config `yaml:",inline"` 77 78 cores map[string]bool 79 client *http.Client 80 version float64 81 charts *Charts 82 } 83 84 func (s *Solr) doRequest(req *http.Request) (*http.Response, error) { 85 return s.client.Do(req) 86 } 87 88 // Cleanup makes cleanup 89 func (Solr) Cleanup() {} 90 91 // Init makes initialization 92 func (s *Solr) Init() bool { 93 if s.URL == "" { 94 s.Error("URL not set") 95 return false 96 } 97 98 client, err := web.NewHTTPClient(s.Client) 99 if err != nil { 100 s.Error(err) 101 return false 102 } 103 104 s.client = client 105 return true 106 } 107 108 // Check makes check 109 func (s *Solr) Check() bool { 110 if err := s.getVersion(); err != nil { 111 s.Error(err) 112 return false 113 } 114 115 if s.version < minSupportedVersion { 116 s.Errorf("unsupported Solr version : %.1f", s.version) 117 return false 118 } 119 120 return true 121 } 122 123 // Charts creates Charts 124 func (s *Solr) Charts() *Charts { 125 s.charts = &Charts{} 126 127 return s.charts 128 } 129 130 // Collect collects metrics 131 func (s *Solr) Collect() map[string]int64 { 132 req, err := createRequest(s.Request, coresHandlersURLPath, coresHandlersURLQuery) 133 if err != nil { 134 s.Errorf("error on creating http request : %v", err) 135 return nil 136 } 137 138 resp, err := s.doRequest(req) 139 if err != nil { 140 s.Errorf("error on request to %s : %s", req.URL, err) 141 return nil 142 } 143 defer closeBody(resp) 144 145 if resp.StatusCode != http.StatusOK { 146 s.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode) 147 return nil 148 } 149 150 metrics, err := s.parse(resp) 151 if err != nil { 152 s.Errorf("error on parse response from %s : %s", req.URL, err) 153 return nil 154 } 155 156 return metrics 157 } 158 159 func (s *Solr) getVersion() error { 160 req, err := createRequest(s.Request, infoSystemURLPath, infoSystemURLQuery) 161 if err != nil { 162 return fmt.Errorf("error on creating http request : %v", err) 163 } 164 165 resp, err := s.doRequest(req) 166 if err != nil { 167 return fmt.Errorf("error on request to %s : %s", req.URL, err) 168 } 169 defer closeBody(resp) 170 171 if resp.StatusCode != http.StatusOK { 172 return fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode) 173 } 174 175 var info infoSystem 176 177 if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { 178 return fmt.Errorf("error on decode response from %s : %s", req.URL, err) 179 } 180 181 var idx int 182 183 if idx = strings.LastIndex(info.Lucene.Version, "."); idx == -1 { 184 return fmt.Errorf("error on parsing version '%s': bad format", info.Lucene.Version) 185 } 186 187 if s.version, err = strconv.ParseFloat(info.Lucene.Version[:idx], 64); err != nil { 188 return fmt.Errorf("error on parsing version '%s' : %s", info.Lucene.Version, err) 189 } 190 191 return nil 192 } 193 194 func createRequest(req web.Request, urlPath, urlQuery string) (*http.Request, error) { 195 r := req.Copy() 196 u, err := url.Parse(r.URL) 197 if err != nil { 198 return nil, err 199 } 200 201 u.Path = urlPath 202 u.RawQuery = urlQuery 203 r.URL = u.String() 204 return web.NewHTTPRequest(r) 205 } 206 207 func closeBody(resp *http.Response) { 208 if resp != nil && resp.Body != nil { 209 _, _ = io.Copy(io.Discard, resp.Body) 210 _ = resp.Body.Close() 211 } 212 }