github.com/mackerelio/mackerel-agent-plugins@v0.89.3/mackerel-plugin-elasticsearch/lib/elasticsearch.go (about) 1 package mpelasticsearch 2 3 import ( 4 "crypto/tls" 5 "encoding/json" 6 "errors" 7 "flag" 8 "fmt" 9 "net/http" 10 11 mp "github.com/mackerelio/go-mackerel-plugin" 12 "github.com/mackerelio/golib/logging" 13 "golang.org/x/text/cases" 14 "golang.org/x/text/language" 15 ) 16 17 var logger = logging.GetLogger("metrics.plugin.elasticsearch") 18 19 var metricPlace = map[string][]string{ 20 "http_opened": {"http", "total_opened"}, 21 "total_indexing_index": {"indices", "indexing", "index_total"}, 22 "total_indexing_delete": {"indices", "indexing", "delete_total"}, 23 "total_get": {"indices", "get", "total"}, 24 "total_search_query": {"indices", "search", "query_total"}, 25 "total_search_fetch": {"indices", "search", "fetch_total"}, 26 "total_merges": {"indices", "merges", "total"}, 27 "total_refresh": {"indices", "refresh", "total"}, 28 "total_flush": {"indices", "flush", "total"}, 29 "total_warmer": {"indices", "warmer", "total"}, 30 "total_percolate": {"indices", "percolate", "total"}, // MISSINGv7 = no value after v7.0 (at least) 31 "total_suggest": {"indices", "suggest", "total"}, // MISSINGv7 32 "docs_count": {"indices", "docs", "count"}, 33 "docs_deleted": {"indices", "docs", "deleted"}, 34 "fielddata_size": {"indices", "fielddata", "memory_size_in_bytes"}, 35 "filter_cache_size": {"indices", "filter_cache", "memory_size_in_bytes"}, // MISSINGv7 36 "segments_size": {"indices", "segments", "memory_in_bytes"}, 37 "segments_index_writer_size": {"indices", "segments", "index_writer_memory_in_bytes"}, 38 "segments_version_map_size": {"indices", "segments", "version_map_memory_in_bytes"}, 39 "segments_fixed_bit_set_size": {"indices", "segments", "fixed_bit_set_memory_in_bytes"}, 40 "evictions_fielddata": {"indices", "fielddata", "evictions"}, 41 "evictions_filter_cache": {"indices", "filter_cache", "evictions"}, // MISSINGv7 42 "heap_used": {"jvm", "mem", "heap_used_in_bytes"}, 43 "heap_max": {"jvm", "mem", "heap_max_in_bytes"}, 44 "threads_generic": {"thread_pool", "generic", "threads"}, 45 "threads_index": {"thread_pool", "index", "threads"}, // MISSINGv7 46 "threads_snapshot_data": {"thread_pool", "snapshot_data", "threads"}, // MISSINGv7 47 "threads_get": {"thread_pool", "get", "threads"}, 48 "threads_bench": {"thread_pool", "bench", "threads"}, // MISSINGv7 49 "threads_snapshot": {"thread_pool", "snapshot", "threads"}, 50 "threads_merge": {"thread_pool", "merge", "threads"}, // MISSINGv7 51 "threads_suggest": {"thread_pool", "suggest", "threads"}, // MISSINGv7 52 "threads_bulk": {"thread_pool", "bulk", "threads"}, // MISSINGv7 53 "threads_optimize": {"thread_pool", "optimize", "threads"}, // MISSINGv7 54 "threads_warmer": {"thread_pool", "warmer", "threads"}, 55 "threads_flush": {"thread_pool", "flush", "threads"}, 56 "threads_search": {"thread_pool", "search", "threads"}, 57 "threads_percolate": {"thread_pool", "percolate", "threads"}, // MISSINGv7 58 "threads_refresh": {"thread_pool", "refresh", "threads"}, 59 "threads_management": {"thread_pool", "management", "threads"}, 60 "threads_fetch_shard_started": {"thread_pool", "fetch_shard_started", "threads"}, 61 "threads_fetch_shard_store": {"thread_pool", "fetch_shard_store", "threads"}, 62 "threads_listener": {"thread_pool", "listener", "threads"}, // MISSINGv8 63 "count_rx": {"transport", "rx_count"}, 64 "count_tx": {"transport", "tx_count"}, 65 "open_file_descriptors": {"process", "open_file_descriptors"}, 66 "compilations": {"script", "compilations"}, 67 "cache_evictions": {"script", "cache_evictions"}, 68 "compilation_limit_triggered": {"script", "compilation_limit_triggered"}, 69 } 70 71 func getFloatValue(s map[string]interface{}, keys []string) (float64, error) { 72 var val float64 73 sm := s 74 for i, k := range keys { 75 if i+1 < len(keys) { 76 switch sm[k].(type) { 77 case map[string]interface{}: 78 sm = sm[k].(map[string]interface{}) 79 default: 80 return 0, errors.New("Cannot handle as a hash") // nolint 81 } 82 } else { 83 switch sm[k].(type) { 84 case float64: 85 val = sm[k].(float64) 86 default: 87 return 0, errors.New("Not float64") // nolint 88 } 89 } 90 } 91 92 return val, nil 93 } 94 95 // ElasticsearchPlugin mackerel plugin for Elasticsearch 96 type ElasticsearchPlugin struct { 97 URI string 98 Prefix string 99 LabelPrefix string 100 Insecure bool 101 User string 102 Password string 103 SuppressMissingError bool 104 } 105 106 // FetchMetrics interface for mackerelplugin 107 func (p ElasticsearchPlugin) FetchMetrics() (map[string]float64, error) { 108 req, err := http.NewRequest(http.MethodGet, p.URI+"/_nodes/_local/stats", nil) 109 if err != nil { 110 return nil, err 111 } 112 req.Header.Set("User-Agent", "mackerel-plugin-elasticsearch") 113 if p.User != "" && p.Password != "" { 114 req.SetBasicAuth(p.User, p.Password) 115 } 116 client := http.Client{ 117 Transport: &http.Transport{ 118 TLSClientConfig: &tls.Config{InsecureSkipVerify: p.Insecure}, 119 }, 120 } 121 resp, err := client.Do(req) 122 if err != nil { 123 return nil, err 124 } 125 defer resp.Body.Close() 126 127 stat := make(map[string]float64) 128 decoder := json.NewDecoder(resp.Body) 129 130 var s map[string]interface{} 131 err = decoder.Decode(&s) 132 if err != nil { 133 return nil, err 134 } 135 136 nodes := s["nodes"].(map[string]interface{}) 137 n := "" 138 for k := range nodes { 139 if n != "" { 140 return nil, errors.New("Multiple node found") // nolint 141 } 142 n = k 143 } 144 node := nodes[n].(map[string]interface{}) 145 146 for k, v := range metricPlace { 147 val, err := getFloatValue(node, v) 148 if err != nil { 149 if !p.SuppressMissingError { 150 logger.Errorf("Failed to find '%s': %s", k, err) 151 } 152 continue 153 } 154 155 stat[k] = val 156 } 157 158 return stat, nil 159 } 160 161 // GraphDefinition interface for mackerelplugin 162 func (p ElasticsearchPlugin) GraphDefinition() map[string]mp.Graphs { 163 var graphdef = map[string]mp.Graphs{ 164 p.Prefix + ".http": { 165 Label: (p.LabelPrefix + " HTTP"), 166 Unit: "integer", 167 Metrics: []mp.Metrics{ 168 {Name: "http_opened", Label: "Opened", Diff: true}, 169 }, 170 }, 171 p.Prefix + ".indices": { 172 Label: (p.LabelPrefix + " Indices"), 173 Unit: "integer", 174 Metrics: []mp.Metrics{ 175 {Name: "total_indexing_index", Label: "Indexing-Index", Diff: true, Stacked: true}, 176 {Name: "total_indexing_delete", Label: "Indexing-Delete", Diff: true, Stacked: true}, 177 {Name: "total_get", Label: "Get", Diff: true, Stacked: true}, 178 {Name: "total_search_query", Label: "Search-Query", Diff: true, Stacked: true}, 179 {Name: "total_search_fetch", Label: "Search-fetch", Diff: true, Stacked: true}, 180 {Name: "total_merges", Label: "Merges", Diff: true, Stacked: true}, 181 {Name: "total_refresh", Label: "Refresh", Diff: true, Stacked: true}, 182 {Name: "total_flush", Label: "Flush", Diff: true, Stacked: true}, 183 {Name: "total_warmer", Label: "Warmer", Diff: true, Stacked: true}, 184 {Name: "total_percolate", Label: "Percolate", Diff: true, Stacked: true}, 185 {Name: "total_suggest", Label: "Suggest", Diff: true, Stacked: true}, 186 }, 187 }, 188 p.Prefix + ".indices.docs": { 189 Label: (p.LabelPrefix + " Indices Docs"), 190 Unit: "integer", 191 Metrics: []mp.Metrics{ 192 {Name: "docs_count", Label: "Count", Stacked: true}, 193 {Name: "docs_deleted", Label: "Deleted", Stacked: true}, 194 }, 195 }, 196 p.Prefix + ".indices.memory_size": { 197 Label: (p.LabelPrefix + " Indices Memory Size"), 198 Unit: "bytes", 199 Metrics: []mp.Metrics{ 200 {Name: "fielddata_size", Label: "Fielddata", Stacked: true}, 201 {Name: "filter_cache_size", Label: "Filter Cache", Stacked: true}, 202 {Name: "segments_size", Label: "Lucene Segments", Stacked: true}, 203 {Name: "segments_index_writer_size", Label: "Lucene Segments Index Writer", Stacked: true}, 204 {Name: "segments_version_map_size", Label: "Lucene Segments Version Map", Stacked: true}, 205 {Name: "segments_fixed_bit_set_size", Label: "Lucene Segments Fixed Bit Set", Stacked: true}, 206 }, 207 }, 208 p.Prefix + ".indices.evictions": { 209 Label: (p.LabelPrefix + " Indices Evictions"), 210 Unit: "integer", 211 Metrics: []mp.Metrics{ 212 {Name: "evictions_fielddata", Label: "Fielddata", Diff: true}, 213 {Name: "evictions_filter_cache", Label: "Filter Cache", Diff: true}, 214 }, 215 }, 216 p.Prefix + ".jvm.heap": { 217 Label: (p.LabelPrefix + " JVM Heap Mem"), 218 Unit: "bytes", 219 Metrics: []mp.Metrics{ 220 {Name: "heap_used", Label: "Used"}, 221 {Name: "heap_max", Label: "Max"}, 222 }, 223 }, 224 p.Prefix + ".thread_pool.threads": { 225 Label: (p.LabelPrefix + " Thread-Pool Threads"), 226 Unit: "integer", 227 Metrics: []mp.Metrics{ 228 {Name: "threads_generic", Label: "Generic", Stacked: true}, 229 {Name: "threads_index", Label: "Index", Stacked: true}, 230 {Name: "threads_snapshot_data", Label: "Snapshot Data", Stacked: true}, 231 {Name: "threads_get", Label: "Get", Stacked: true}, 232 {Name: "threads_bench", Label: "Bench", Stacked: true}, 233 {Name: "threads_snapshot", Label: "Snapshot", Stacked: true}, 234 {Name: "threads_merge", Label: "Merge", Stacked: true}, 235 {Name: "threads_suggest", Label: "Suggest", Stacked: true}, 236 {Name: "threads_bulk", Label: "Bulk", Stacked: true}, 237 {Name: "threads_optimize", Label: "Optimize", Stacked: true}, 238 {Name: "threads_warmer", Label: "Warmer", Stacked: true}, 239 {Name: "threads_flush", Label: "Flush", Stacked: true}, 240 {Name: "threads_search", Label: "Search", Stacked: true}, 241 {Name: "threads_percolate", Label: "Percolate", Stacked: true}, 242 {Name: "threads_refresh", Label: "Refresh", Stacked: true}, 243 {Name: "threads_management", Label: "Management", Stacked: true}, 244 {Name: "threads_fetch_shard_started", Label: "Fetch Shard Started", Stacked: true}, 245 {Name: "threads_fetch_shard_store", Label: "Fetch Shard Store", Stacked: true}, 246 {Name: "threads_listener", Label: "Listener", Stacked: true}, 247 }, 248 }, 249 p.Prefix + ".transport.count": { 250 Label: (p.LabelPrefix + " Transport Count"), 251 Unit: "integer", 252 Metrics: []mp.Metrics{ 253 {Name: "count_rx", Label: "TX", Diff: true}, 254 {Name: "count_tx", Label: "RX", Diff: true}, 255 }, 256 }, 257 p.Prefix + ".process": { 258 Label: (p.LabelPrefix + " Process"), 259 Unit: "integer", 260 Metrics: []mp.Metrics{ 261 {Name: "open_file_descriptors", Label: "Open File Descriptors"}, 262 }, 263 }, 264 p.Prefix + ".script": { 265 Label: (p.LabelPrefix + " Script"), 266 Unit: "integer", 267 Metrics: []mp.Metrics{ 268 {Name: "compilations", Label: "Compilations", Diff: true}, 269 {Name: "cache_evictions", Label: "Cache Evictions", Diff: true}, 270 {Name: "compilation_limit_triggered", Label: "Compilation Limit Triggered", Diff: true}, 271 }, 272 }, 273 } 274 275 return graphdef 276 } 277 278 // Do the plugin 279 func Do() { 280 optScheme := flag.String("scheme", "http", "Scheme") 281 optHost := flag.String("host", "localhost", "Host") 282 optPort := flag.String("port", "9200", "Port") 283 optPrefix := flag.String("metric-key-prefix", "elasticsearch", "Metric key prefix") 284 optLabelPrefix := flag.String("metric-label-prefix", "", "Metric Label prefix") 285 optTempfile := flag.String("tempfile", "", "Temp file name") 286 optInsecure := flag.Bool("insecure", false, "Skip TLS certificate verification") 287 optUser := flag.String("user", "", "Basic auth user") 288 optPassword := flag.String("password", "", "Basic auth password") 289 optSuppressMissingError := flag.Bool("suppress-missing-error", false, "Suppress ERROR for missing values") 290 flag.Parse() 291 292 var elasticsearch ElasticsearchPlugin 293 elasticsearch.URI = fmt.Sprintf("%s://%s:%s", *optScheme, *optHost, *optPort) 294 elasticsearch.Prefix = *optPrefix 295 if *optLabelPrefix == "" { 296 elasticsearch.LabelPrefix = cases.Title(language.Und, cases.NoLower).String(*optPrefix) 297 } else { 298 elasticsearch.LabelPrefix = *optLabelPrefix 299 } 300 elasticsearch.Insecure = *optInsecure 301 elasticsearch.User = *optUser 302 elasticsearch.Password = *optPassword 303 elasticsearch.SuppressMissingError = *optSuppressMissingError 304 305 helper := mp.NewMackerelPlugin(elasticsearch) 306 if *optTempfile != "" { 307 helper.Tempfile = *optTempfile 308 } else { 309 helper.SetTempfileByBasename(fmt.Sprintf("mackerel-plugin-elasticsearch-%s-%s", *optHost, *optPort)) 310 } 311 312 helper.Run() 313 }