bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/scollector/collectors/elasticsearch.go (about)

     1  package collectors
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/url"
    10  	"reflect"
    11  	"regexp"
    12  	"strings"
    13  	"time"
    14  
    15  	"bosun.org/cmd/scollector/conf"
    16  	"bosun.org/metadata"
    17  	"bosun.org/opentsdb"
    18  	"bosun.org/slog"
    19  )
    20  
    21  func init() {
    22  	registerInit(func(c *conf.Conf) {
    23  		for _, filter := range c.ElasticIndexFilters {
    24  			err := AddElasticIndexFilter(filter, true)
    25  			if err != nil {
    26  				slog.Errorf("Error processing ElasticIndexFilter: %s", err)
    27  			}
    28  		}
    29  		for _, filter := range c.ElasticIndexFiltersInc {
    30  			err := AddElasticIndexFilter(filter, false)
    31  			if err != nil {
    32  				slog.Errorf("Error processing ElasticIndexFilterInc: %s", err)
    33  			}
    34  		}
    35  		if c.Elastic == nil {
    36  			// preserve the legacy defaults (localhost:9200)
    37  			c.Elastic = append(c.Elastic, conf.Elastic{})
    38  		}
    39  		for _, instance := range c.Elastic {
    40  			var indexInterval = time.Minute * 15
    41  			var clusterInterval = DefaultFreq
    42  			var err error
    43  			// preserve defaults of localhost:9200
    44  			if instance.Host == "" {
    45  				instance.Host = "localhost"
    46  			}
    47  			if instance.Port == 0 {
    48  				instance.Port = 9200
    49  			}
    50  			if instance.Scheme == "" {
    51  				instance.Scheme = "http"
    52  			}
    53  			if instance.Name == "" {
    54  				instance.Name = fmt.Sprintf("%v_%v", instance.Host, instance.Port)
    55  			}
    56  			if instance.Disable {
    57  				slog.Infof("Elastic instance %v is disabled. Skipping.", instance.Name)
    58  				continue
    59  			}
    60  			var creds string
    61  			if instance.User != "" || instance.Password != "" {
    62  				creds = fmt.Sprintf("%v:%v@", instance.User, instance.Password)
    63  			} else {
    64  				creds = ""
    65  			}
    66  			url := fmt.Sprintf("%v://%v%v:%v", instance.Scheme, creds, instance.Host, instance.Port)
    67  			if instance.IndexInterval != "" {
    68  				indexInterval, err = time.ParseDuration(instance.IndexInterval)
    69  				if err != nil {
    70  					panic(fmt.Errorf("Failed to parse IndexInterval: %v, err: %v", instance.IndexInterval, err))
    71  				}
    72  				slog.Infof("Using IndexInterval: %v for %v", indexInterval, instance.Name)
    73  			} else {
    74  				slog.Infof("Using default IndexInterval: %v for %v", indexInterval, instance.Name)
    75  			}
    76  			if instance.ClusterInterval != "" {
    77  				clusterInterval, err = time.ParseDuration(instance.ClusterInterval)
    78  				if err != nil {
    79  					panic(fmt.Errorf("Failed to parse ClusterInterval: %v, err: %v", instance.ClusterInterval, err))
    80  				}
    81  				slog.Infof("Using ClusterInterval: %v for %v", clusterInterval, instance.Name)
    82  			} else {
    83  				slog.Infof("Using default ClusterInterval: %v for %v", clusterInterval, instance.Name)
    84  			}
    85  			// for legacy reasons, keep localhost:9200 named elasticsearch / elasticsearch-indices
    86  			var name string
    87  			if instance.Name == "localhost_9200" {
    88  				name = "elasticsearch"
    89  			} else {
    90  				name = fmt.Sprintf("elasticsearch-%v", instance.Name)
    91  			}
    92  			collectors = append(collectors, &IntervalCollector{
    93  				F: func() (opentsdb.MultiDataPoint, error) {
    94  					return c_elasticsearch(false, instance)
    95  				},
    96  				name:     name,
    97  				Interval: clusterInterval,
    98  				Enable:   enableURL(url),
    99  			})
   100  			// keep legacy collector name if localhost_9200
   101  			if instance.Name == "localhost_9200" {
   102  				name = "elasticsearch-indices"
   103  			} else {
   104  				name = fmt.Sprintf("elasticsearch-indices-%v", instance.Name)
   105  			}
   106  			collectors = append(collectors, &IntervalCollector{
   107  				F: func() (opentsdb.MultiDataPoint, error) {
   108  					return c_elasticsearch(true, instance)
   109  				},
   110  				name:     name,
   111  				Interval: indexInterval,
   112  				Enable:   enableURL(url),
   113  			})
   114  		}
   115  	})
   116  }
   117  
   118  var (
   119  	elasticPreV1     = regexp.MustCompile(`^0\.`)
   120  	elasticStatusMap = map[string]int{
   121  		"green":  0,
   122  		"yellow": 1,
   123  		"red":    2,
   124  	}
   125  	elasticIndexFilters    = make([]*regexp.Regexp, 0)
   126  	elasticIndexFiltersInc = make([]*regexp.Regexp, 0)
   127  )
   128  
   129  func AddElasticIndexFilter(s string, exclude bool) error {
   130  	re, err := regexp.Compile(s)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	if exclude {
   135  		slog.Infof("Added ES Index Filter: %v", s)
   136  		elasticIndexFilters = append(elasticIndexFilters, re)
   137  	} else {
   138  		slog.Infof("Added ES Index Inclusion Filter: %v", s)
   139  		elasticIndexFiltersInc = append(elasticIndexFiltersInc, re)
   140  	}
   141  	return nil
   142  }
   143  
   144  type structProcessor struct {
   145  	elasticVersion string
   146  	md             *opentsdb.MultiDataPoint
   147  }
   148  
   149  // structProcessor.add() takes in a metric name prefix, an arbitrary struct, and a tagset.
   150  // The processor recurses through the struct and builds metrics. The field tags direct how
   151  // the field should be processed, as well as the metadata for the resulting metric.
   152  //
   153  // The field tags used are described as follows:
   154  //
   155  // version: typically set to '1' or '2'.
   156  //	This is compared against the elastic cluster version. If the version from the tag
   157  //      does not match the version in production, the metric will not be sent for this field.
   158  //
   159  // exclude:
   160  //      If this tag is set to 'true', a metric will not be sent for this field.
   161  //
   162  // rate: one of 'gauge', 'counter', 'rate'
   163  //	This tag dictates the metadata.RateType we send.
   164  //
   165  // unit: 'bytes', 'pages', etc
   166  //	This tag dictates the metadata.Unit we send.
   167  //
   168  // metric:
   169  //      This is the metric name which will be sent. If not present, the 'json'
   170  //      tag is sent as the metric name.
   171  //
   172  // Special handling:
   173  //
   174  // Metrics having the json tag suffix of 'in_milliseconds' are automagically
   175  // divided by 1000 and sent as seconds. The suffix is stripped from the name.
   176  //
   177  // Metrics having the json tag suffix of 'in_bytes' are automatically sent as
   178  // gauge bytes. The suffix is stripped from the metric name.
   179  func (s *structProcessor) add(prefix string, st interface{}, ts opentsdb.TagSet) {
   180  	t := reflect.TypeOf(st)
   181  	valueOf := reflect.ValueOf(st)
   182  	for i := 0; i < t.NumField(); i++ {
   183  		field := t.Field(i)
   184  		value := valueOf.Field(i).Interface()
   185  		if field.Tag.Get("exclude") == "true" {
   186  			continue
   187  		}
   188  		var (
   189  			jsonTag    = field.Tag.Get("json")
   190  			metricTag  = field.Tag.Get("metric")
   191  			versionTag = field.Tag.Get("version")
   192  			rateTag    = field.Tag.Get("rate")
   193  			unitTag    = field.Tag.Get("unit")
   194  		)
   195  		metricName := jsonTag
   196  		if metricTag != "" {
   197  			metricName = metricTag
   198  		}
   199  		if metricName == "" {
   200  			slog.Errorf("Unable to determine metric name for field %s. Skipping.", field.Name)
   201  			continue
   202  		}
   203  		if versionTag == "" || strings.HasPrefix(s.elasticVersion, versionTag) {
   204  			switch value := value.(type) {
   205  			case int, float64: // Number types in our structs are only ints and float64s.
   206  				// Turn all millisecond metrics into seconds
   207  				if strings.HasSuffix(metricName, "_in_millis") {
   208  					switch value.(type) {
   209  					case int:
   210  						value = float64(value.(int)) / 1000
   211  					case float64:
   212  						value = value.(float64) / 1000
   213  					}
   214  					unitTag = "seconds"
   215  					metricName = strings.TrimSuffix(metricName, "_in_millis")
   216  				}
   217  				// Set rate and unit for all "_in_bytes" metrics, and strip the "_in_bytes"
   218  				if strings.HasSuffix(metricName, "_in_bytes") {
   219  					if rateTag == "" {
   220  						rateTag = "gauge"
   221  					}
   222  					unitTag = "bytes"
   223  					metricName = strings.TrimSuffix(metricName, "_in_bytes")
   224  				}
   225  				Add(s.md, prefix+"."+metricName, value, ts, metadata.RateType(rateTag), metadata.Unit(unitTag), field.Tag.Get("desc"))
   226  			case string:
   227  				// The json data has a lot of strings, and we don't care about em.
   228  			default:
   229  				// If we hit another struct, recurse
   230  				if reflect.ValueOf(value).Kind() == reflect.Struct {
   231  					s.add(prefix+"."+metricName, value, ts)
   232  				} else {
   233  					slog.Errorf("Field %s for metric %s is non-numeric type. Cannot record as a metric.\n", field.Name, prefix+"."+metricName)
   234  				}
   235  			}
   236  		}
   237  	}
   238  }
   239  
   240  func c_elasticsearch(collectIndices bool, instance conf.Elastic) (opentsdb.MultiDataPoint, error) {
   241  	slog.Infof("Updating ES stats for %v", instance)
   242  	var status ElasticStatus
   243  	if err := esReq(instance, "/", "", &status); err != nil {
   244  		return nil, err
   245  	}
   246  	var clusterStats ElasticClusterStats
   247  	if err := esReq(instance, esStatsURL(status.Version.Number), "", &clusterStats); err != nil {
   248  		return nil, err
   249  	}
   250  	var clusterState ElasticClusterState
   251  	if err := esReq(instance, "/_cluster/state/master_node", "", &clusterState); err != nil {
   252  		return nil, err
   253  	}
   254  	var clusterHealth ElasticHealth
   255  	if err := esReq(instance, "/_cluster/health", "level=indices", &clusterHealth); err != nil {
   256  		return nil, err
   257  	}
   258  	var indexStats ElasticIndexStats
   259  	if err := esReq(instance, "/_stats", "", &indexStats); err != nil {
   260  		return nil, err
   261  	}
   262  	var md opentsdb.MultiDataPoint
   263  	s := structProcessor{elasticVersion: status.Version.Number, md: &md}
   264  	ts := opentsdb.TagSet{"cluster": clusterStats.ClusterName}
   265  	isMaster := false
   266  	// As we're pulling _local stats here, this will only process 1 node.
   267  	for nodeID, nodeStats := range clusterStats.Nodes {
   268  		isMaster = nodeID == clusterState.MasterNode
   269  		if isMaster {
   270  			s.add("elastic.health.cluster", clusterHealth, ts)
   271  			if statusCode, ok := elasticStatusMap[clusterHealth.Status]; ok {
   272  				Add(&md, "elastic.health.cluster.status", statusCode, ts, metadata.Gauge, metadata.StatusCode, "The current status of the cluster. Zero for green, one for yellow, two for red.")
   273  			}
   274  			indexStatusCount := map[string]int{
   275  				"green":  0,
   276  				"yellow": 0,
   277  				"red":    0,
   278  			}
   279  			for _, index := range clusterHealth.Indices {
   280  				indexStatusCount[index.Status] += 1
   281  			}
   282  			for status, count := range indexStatusCount {
   283  				Add(&md, "elastic.health.cluster.index_status_count", count, opentsdb.TagSet{"status": status}.Merge(ts), metadata.Gauge, metadata.Unit("indices"), "Index counts by status.")
   284  			}
   285  		}
   286  		s.add("elastic", nodeStats, ts)
   287  		// These are index stats in aggregate for this node.
   288  		s.add("elastic.indices.local", nodeStats.Indices, ts)
   289  		s.add("elastic.jvm.gc", nodeStats.JVM.GC.Collectors.Old, opentsdb.TagSet{"gc": "old"}.Merge(ts))
   290  		s.add("elastic.jvm.gc", nodeStats.JVM.GC.Collectors.Young, opentsdb.TagSet{"gc": "young"}.Merge(ts))
   291  	}
   292  	if collectIndices && isMaster {
   293  		for k, index := range indexStats.Indices {
   294  			if esSkipIndex(k) {
   295  				slog.Infof("Skipping index: %v", k)
   296  				continue
   297  			}
   298  			slog.Infof("Pulling index stats: %v", k)
   299  			ts := opentsdb.TagSet{"index_name": k, "cluster": clusterStats.ClusterName}
   300  			if indexHealth, ok := clusterHealth.Indices[k]; ok {
   301  				s.add("elastic.health.indices", indexHealth, ts)
   302  				if status, ok := elasticStatusMap[indexHealth.Status]; ok {
   303  					Add(&md, "elastic.health.indices.status", status, ts, metadata.Gauge, metadata.StatusCode, "The current status of the index. Zero for green, one for yellow, two for red.")
   304  				}
   305  			}
   306  			s.add("elastic.indices.cluster", index.Primaries, ts)
   307  		}
   308  	}
   309  	return md, nil
   310  }
   311  
   312  func esSkipIndex(index string) bool {
   313  	for _, re := range elasticIndexFilters {
   314  		if re.MatchString(index) {
   315  			return true
   316  		}
   317  	}
   318  	for _, re := range elasticIndexFiltersInc {
   319  		if re.MatchString(index) {
   320  			return false
   321  		}
   322  	}
   323  	return len(elasticIndexFiltersInc) > 0
   324  }
   325  
   326  func esReq(instance conf.Elastic, path, query string, v interface{}) error {
   327  	up := url.UserPassword(instance.User, instance.Password)
   328  	u := &url.URL{
   329  		Scheme:   instance.Scheme,
   330  		User:     up,
   331  		Host:     fmt.Sprintf("%v:%v", instance.Host, instance.Port),
   332  		Path:     path,
   333  		RawQuery: query,
   334  	}
   335  	resp, err := http.Get(u.String())
   336  	if err != nil {
   337  		slog.Errorf("Error querying Elasticsearch: %v", err)
   338  		return nil
   339  	}
   340  	defer resp.Body.Close()
   341  	if resp.StatusCode != http.StatusOK {
   342  		// Drain up to 512 bytes and close the body to let the Transport reuse the connection
   343  		io.CopyN(ioutil.Discard, resp.Body, 512)
   344  		return nil
   345  	}
   346  	j := json.NewDecoder(resp.Body)
   347  	return j.Decode(v)
   348  }
   349  
   350  func esStatsURL(version string) string {
   351  	if elasticPreV1.MatchString(version) {
   352  		return "/_cluster/nodes/_local/stats"
   353  	}
   354  	return "/_nodes/_local/stats"
   355  }
   356  
   357  type ElasticHealth struct {
   358  	ActivePrimaryShards         int                           `json:"active_primary_shards" desc:"The number of active primary shards. Each document is stored in a single primary shard and then when it is indexed it is copied the replicas of that shard."`
   359  	ActiveShards                int                           `json:"active_shards" desc:"The number of active shards."`
   360  	ActiveShardsPercentAsNumber float64                       `json:"active_shards_percent_as_number" version:"2"` // 2.0 only
   361  	ClusterName                 string                        `json:"cluster_name"`
   362  	DelayedUnassignedShards     int                           `json:"delayed_unassigned_shards" version:"2"` // 2.0 only
   363  	Indices                     map[string]ElasticIndexHealth `json:"indices" exclude:"true"`
   364  	InitializingShards          int                           `json:"initializing_shards" desc:"The number of initalizing shards."`
   365  	NumberOfDataNodes           int                           `json:"number_of_data_nodes"`
   366  	NumberOfInFlightFetch       int                           `json:"number_of_in_flight_fetch" version:"2"` // 2.0 only
   367  	NumberOfNodes               int                           `json:"number_of_nodes"`
   368  	NumberOfPendingTasks        int                           `json:"number_of_pending_tasks"`
   369  	RelocatingShards            int                           `json:"relocating_shards" desc:"The number of shards relocating."`
   370  	Status                      string                        `json:"status" desc:"The current status of the cluster. 0: green, 1: yellow, 2: red."`
   371  	TaskMaxWaitingInQueueMillis int                           `json:"task_max_waiting_in_queue_millis" version:"2"` // 2.0 only
   372  	TimedOut                    bool                          `json:"timed_out" exclude:"true"`
   373  	UnassignedShards            int                           `json:"unassigned_shards" version:"2"` // 2.0 only
   374  }
   375  
   376  type ElasticIndexHealth struct {
   377  	ActivePrimaryShards int    `json:"active_primary_shards" desc:"The number of active primary shards. Each document is stored in a single primary shard and then when it is indexed it is copied the replicas of that shard."`
   378  	ActiveShards        int    `json:"active_shards" desc:"The number of active shards."`
   379  	InitializingShards  int    `json:"initializing_shards" desc:"The number of initalizing shards."`
   380  	NumberOfReplicas    int    `json:"number_of_replicas" desc:"The number of replicas."`
   381  	NumberOfShards      int    `json:"number_of_shards" desc:"The number of shards."`
   382  	RelocatingShards    int    `json:"relocating_shards" desc:"The number of shards relocating."`
   383  	Status              string `json:"status" desc:"The current status of the index. 0: green, 1: yellow, 2: red."`
   384  	UnassignedShards    int    `json:"unassigned_shards"`
   385  }
   386  
   387  type ElasticIndexStats struct {
   388  	All    ElasticIndex `json:"_all"`
   389  	Shards struct {
   390  		Failed     float64 `json:"failed"`
   391  		Successful float64 `json:"successful"`
   392  		Total      float64 `json:"total"`
   393  	} `json:"_shards"`
   394  	Indices map[string]ElasticIndex `json:"indices"`
   395  }
   396  
   397  type ElasticIndex struct {
   398  	Primaries ElasticIndexDetails `json:"primaries"`
   399  	Total     ElasticIndexDetails `json:"total"`
   400  }
   401  
   402  type ElasticIndexDetails struct {
   403  	Completion struct {
   404  		SizeInBytes int `json:"size_in_bytes" desc:"Size of the completion index (used for auto-complete functionallity)."`
   405  	} `json:"completion"`
   406  	Docs struct {
   407  		Count   int `json:"count" rate:"gauge" rate:"gauge" unit:"documents" desc:"The number of documents in the index."`
   408  		Deleted int `json:"deleted" rate:"gauge" unit:"documents" desc:"The number of deleted documents in the index."`
   409  	} `json:"docs"`
   410  	Fielddata struct {
   411  		Evictions         int `json:"evictions" rate:"counter" unit:"evictions" desc:"The number of cache evictions for field data."`
   412  		MemorySizeInBytes int `json:"memory_size_in_bytes" desc:"The amount of memory used for field data."`
   413  	} `json:"fielddata"`
   414  	FilterCache struct { // 1.0 only
   415  		Evictions         int `json:"evictions" version:"1" rate:"counter" unit:"evictions" desc:"The number of cache evictions for filter data."` // 1.0 only
   416  		MemorySizeInBytes int `json:"memory_size_in_bytes" version:"1" desc:"The amount of memory used for filter data."`                          // 1.0 only
   417  	} `json:"filter_cache"`
   418  	Flush struct {
   419  		Total             int `json:"total" rate:"counter" unit:"flushes" desc:"The number of flush operations. The flush process of an index basically frees memory from the index by flushing data to the index storage and clearing the internal transaction log."`
   420  		TotalTimeInMillis int `json:"total_time_in_millis" rate:"counter" unit:"seconds" desc:"The total amount of time spent on flush operations. The flush process of an index basically frees memory from the index by flushing data to the index storage and clearing the internal transaction log."`
   421  	} `json:"flush"`
   422  	Get struct {
   423  		Current             int `json:"current" rate:"gauge" unit:"gets" desc:"The current number of get operations. Gets get a typed JSON document from the index based on its id."`
   424  		ExistsTimeInMillis  int `json:"exists_time_in_millis" rate:"counter" unit:"seconds" desc:"The total amount of time spent on get exists operations. Gets exists sees if a document exists."`
   425  		ExistsTotal         int `json:"exists_total" rate:"counter" unit:"get exists" desc:"The total number of get exists operations. Gets exists sees if a document exists."`
   426  		MissingTimeInMillis int `json:"missing_time_in_millis" rate:"counter" unit:"seconds" desc:"The total amount of time spent trying to get documents that turned out to be missing."`
   427  		MissingTotal        int `json:"missing_total" rate:"counter" unit:"operations" desc:"The total number of operations that tried to get a document that turned out to be missing."`
   428  		TimeInMillis        int `json:"time_in_millis" rate:"counter" unit:"seconds" desc:"The total amount of time spent on get operations. Gets get a typed JSON document from the index based on its id."`
   429  		Total               int `json:"total" rate:"counter" unit:"operations" desc:"The total number of get operations. Gets get a typed JSON document from the index based on its id."`
   430  	} `json:"get"`
   431  	IDCache struct { // 1.0 only
   432  		MemorySizeInBytes int `json:"memory_size_in_bytes" version:"1" desc:"The size of the id cache."` // 1.0 only
   433  	} `json:"id_cache"`
   434  	Indexing struct {
   435  		DeleteCurrent        int  `json:"delete_current" rate:"gauge" unit:"documents" desc:"The current number of documents being deleted via indexing commands (such as a delete query)."`
   436  		DeleteTimeInMillis   int  `json:"delete_time_in_millis" rate:"counter" unit:"seconds" desc:"The time spent deleting documents."`
   437  		DeleteTotal          int  `json:"delete_total" rate:"counter" unit:"documents" desc:"The total number of documents deleted."`
   438  		IndexCurrent         int  `json:"index_current" rate:"gauge" unit:"documents" desc:"The current number of documents being indexed."`
   439  		IndexTimeInMillis    int  `json:"index_time_in_millis" rate:"counter" unit:"seconds" desc:"The total amount of time spent indexing documents."`
   440  		IndexTotal           int  `json:"index_total" rate:"counter" unit:"documents" desc:"The total number of documents indexed."`
   441  		IsThrottled          bool `json:"is_throttled" exclude:"true"`
   442  		NoopUpdateTotal      int  `json:"noop_update_total"`
   443  		ThrottleTimeInMillis int  `json:"throttle_time_in_millis"`
   444  	} `json:"indexing"`
   445  	Merges struct {
   446  		Current                    int `json:"current" rate:"gauge" unit:"merges" desc:"The current number of merge operations. In elastic Lucene segments are merged behind the scenes. It is possible these can impact search performance."`
   447  		CurrentDocs                int `json:"current_docs" rate:"gauge" unit:"documents" desc:"The current number of documents that have an underlying merge operation going on. In elastic Lucene segments are merged behind the scenes. It is possible these can impact search performance."`
   448  		CurrentSizeInBytes         int `json:"current_size_in_bytes" desc:"The current number of bytes being merged. In elastic Lucene segments are merged behind the scenes. It is possible these can impact search performance."`
   449  		Total                      int `json:"total" rate:"counter" unit:"merges" desc:"The total number of merges. In elastic Lucene segments are merged behind the scenes. It is possible these can impact search performance."`
   450  		TotalAutoThrottleInBytes   int `json:"total_auto_throttle_in_bytes" version:"2"` // 2.0 only
   451  		TotalDocs                  int `json:"total_docs" rate:"counter" unit:"documents" desc:"The total number of documents that have had an underlying merge operation. In elastic Lucene segments are merged behind the scenes. It is possible these can impact search performance."`
   452  		TotalSizeInBytes           int `json:"total_size_in_bytes" desc:"The total number of bytes merged. In elastic Lucene segments are merged behind the scenes. It is possible these can impact search performance."`
   453  		TotalStoppedTimeInMillis   int `json:"total_stopped_time_in_millis" version:"2"`   // 2.0 only
   454  		TotalThrottledTimeInMillis int `json:"total_throttled_time_in_millis" version:"2"` // 2.0 only
   455  		TotalTimeInMillis          int `json:"total_time_in_millis" rate:"counter" unit:"seconds" desc:"The total amount of time spent on merge operations. In elastic Lucene segments are merged behind the scenes. It is possible these can impact search performance."`
   456  	} `json:"merges"`
   457  	Percolate struct {
   458  		Current           int    `json:"current" rate:"gauge" unit:"operations" desc:"The current number of percolate operations."`
   459  		MemorySize        string `json:"memory_size"`
   460  		MemorySizeInBytes int    `json:"memory_size_in_bytes" desc:"The amount of memory used for the percolate index. Percolate is a reverse query to document operation."`
   461  		Queries           int    `json:"queries" rate:"counter" unit:"queries" desc:"The total number of percolate queries. Percolate is a reverse query to document operation."`
   462  		TimeInMillis      int    `json:"time_in_millis" rate:"counter" unit:"seconds" desc:"The total amount of time spent on percolating. Percolate is a reverse query to document operation."`
   463  		Total             int    `json:"total" rate:"gauge" unit:"operations" desc:"The total number of percolate operations. Percolate is a reverse query to document operation."`
   464  	} `json:"percolate"`
   465  	QueryCache struct {
   466  		CacheCount        int `json:"cache_count" version:"2"` // 2.0 only
   467  		CacheSize         int `json:"cache_size" version:"2"`  // 2.0 only
   468  		Evictions         int `json:"evictions"`
   469  		HitCount          int `json:"hit_count"`
   470  		MemorySizeInBytes int `json:"memory_size_in_bytes"`
   471  		MissCount         int `json:"miss_count"`
   472  		TotalCount        int `json:"total_count" version:"2"` // 2.0 only
   473  	} `json:"query_cache"`
   474  	Recovery struct {
   475  		CurrentAsSource      int `json:"current_as_source"`
   476  		CurrentAsTarget      int `json:"current_as_target"`
   477  		ThrottleTimeInMillis int `json:"throttle_time_in_millis"`
   478  	} `json:"recovery"`
   479  	Refresh struct {
   480  		Total             int `json:"total" rate:"counter" unit:"refresh" desc:"The total number of refreshes. Refreshing makes all operations performed since the last search available."`
   481  		TotalTimeInMillis int `json:"total_time_in_millis" rate:"counter" unit:"seconds" desc:"The total amount of time spent on refreshes. Refreshing makes all operations performed since the last search available."`
   482  	} `json:"refresh"`
   483  	RequestCache struct { // 2.0 only
   484  		Evictions         int `json:"evictions" version:"2"`            // 2.0 only
   485  		HitCount          int `json:"hit_count" version:"2"`            // 2.0 only
   486  		MemorySizeInBytes int `json:"memory_size_in_bytes" version:"2"` // 2.0 only
   487  		MissCount         int `json:"miss_count" version:"2"`           // 2.0 only
   488  	} `json:"request_cache"`
   489  	Search struct {
   490  		FetchCurrent       int `json:"fetch_current" rate:"gauge" unit:"documents" desc:"The current number of documents being fetched. Fetching is a phase of querying in a distributed search."`
   491  		FetchTimeInMillis  int `json:"fetch_time_in_millis" rate:"counter" unit:"seconds" desc:"The total time spent fetching documents. Fetching is a phase of querying in a distributed search."`
   492  		FetchTotal         int `json:"fetch_total" rate:"counter" unit:"documents" desc:"The total number of documents fetched. Fetching is a phase of querying in a distributed search."`
   493  		OpenContexts       int `json:"open_contexts" rate:"gauge" unit:"contexts" desc:"The current number of open contexts. A search is left open when srolling (i.e. pagination)."`
   494  		QueryCurrent       int `json:"query_current" rate:"gauge" unit:"queries" desc:"The current number of queries."`
   495  		QueryTimeInMillis  int `json:"query_time_in_millis" rate:"counter" unit:"seconds" desc:"The total amount of time spent querying."`
   496  		QueryTotal         int `json:"query_total" rate:"counter" unit:"queries" desc:"The total number of queries."`
   497  		ScrollCurrent      int `json:"scroll_current" version:"2"`        // 2.0 only
   498  		ScrollTimeInMillis int `json:"scroll_time_in_millis" version:"2"` // 2.0 only
   499  		ScrollTotal        int `json:"scroll_total" version:"2"`          // 2.0 only
   500  	} `json:"search"`
   501  	Segments struct {
   502  		Count                       int `json:"count" rate:"counter" unit:"segments" desc:"The number of segments that make up the index."`
   503  		DocValuesMemoryInBytes      int `json:"doc_values_memory_in_bytes" version:"2"` // 2.0 only
   504  		FixedBitSetMemoryInBytes    int `json:"fixed_bit_set_memory_in_bytes"`
   505  		IndexWriterMaxMemoryInBytes int `json:"index_writer_max_memory_in_bytes"`
   506  		IndexWriterMemoryInBytes    int `json:"index_writer_memory_in_bytes"`
   507  		MemoryInBytes               int `json:"memory_in_bytes" desc:"The total amount of memory used for Lucene segments."`
   508  		NormsMemoryInBytes          int `json:"norms_memory_in_bytes" version:"2"`         // 2.0 only
   509  		StoredFieldsMemoryInBytes   int `json:"stored_fields_memory_in_bytes" version:"2"` // 2.0 only
   510  		TermVectorsMemoryInBytes    int `json:"term_vectors_memory_in_bytes" version:"2"`  // 2.0 only
   511  		TermsMemoryInBytes          int `json:"terms_memory_in_bytes" version:"2"`         // 2.0 only
   512  		VersionMapMemoryInBytes     int `json:"version_map_memory_in_bytes"`
   513  	} `json:"segments"`
   514  	Store struct {
   515  		SizeInBytes          int `json:"size_in_bytes" unit:"bytes" desc:"The current size of the store."`
   516  		ThrottleTimeInMillis int `json:"throttle_time_in_millis" rate:"gauge" unit:"seconds" desc:"The amount of time that merges where throttled."`
   517  	} `json:"store"`
   518  	Suggest struct {
   519  		Current      int `json:"current" rate:"gauge" unit:"suggests" desc:"The current number of suggest operations."`
   520  		TimeInMillis int `json:"time_in_millis" rate:"gauge" unit:"seconds" desc:"The total amount of time spent on suggest operations."`
   521  		Total        int `json:"total" rate:"gauge" unit:"suggests" desc:"The total number of suggest operations."`
   522  	} `json:"suggest"`
   523  	Translog struct {
   524  		Operations  int `json:"operations" rate:"gauge" unit:"operations" desc:"The total number of translog operations. The transaction logs (or write ahead logs) ensure atomicity of operations."`
   525  		SizeInBytes int `json:"size_in_bytes" desc:"The current size of transaction log. The transaction log (or write ahead log) ensure atomicity of operations."`
   526  	} `json:"translog"`
   527  	Warmer struct {
   528  		Current           int `json:"current" rate:"gauge" unit:"operations" desc:"The current number of warmer operations. Warming registers search requests in the background to speed up actual search requests."`
   529  		Total             int `json:"total" rate:"gauge" unit:"operations" desc:"The total number of warmer operations. Warming registers search requests in the background to speed up actual search requests."`
   530  		TotalTimeInMillis int `json:"total_time_in_millis" rate:"gauge" unit:"seconds" desc:"The total time spent on warmer operations. Warming registers search requests in the background to speed up actual search requests."`
   531  	} `json:"warmer"`
   532  }
   533  
   534  type ElasticStatus struct {
   535  	Status  int    `json:"status"`
   536  	Name    string `json:"name"`
   537  	Version struct {
   538  		Number string `json:"number"`
   539  	} `json:"version"`
   540  }
   541  
   542  type ElasticClusterStats struct {
   543  	ClusterName string `json:"cluster_name"`
   544  	Nodes       map[string]struct {
   545  		Attributes struct {
   546  			Master string `json:"master"`
   547  		} `json:"attributes"`
   548  		Breakers struct {
   549  			Fielddata ElasticBreakersStat `json:"fielddata"`
   550  			Parent    ElasticBreakersStat `json:"parent"`
   551  			Request   ElasticBreakersStat `json:"request"`
   552  		} `json:"breakers" exclude:"true"`
   553  		FS struct {
   554  			Data []struct {
   555  				AvailableInBytes     int    `json:"available_in_bytes"`
   556  				Dev                  string `json:"dev" version:"1"`                      // 1.0 only
   557  				DiskIoOp             int    `json:"disk_io_op" version:"1"`               // 1.0 only
   558  				DiskIoSizeInBytes    int    `json:"disk_io_size_in_bytes" version:"1"`    // 1.0 only
   559  				DiskQueue            string `json:"disk_queue" version:"1"`               // 1.0 only
   560  				DiskReadSizeInBytes  int    `json:"disk_read_size_in_bytes" version:"1"`  // 1.0 only
   561  				DiskReads            int    `json:"disk_reads" version:"1"`               // 1.0 only
   562  				DiskServiceTime      string `json:"disk_service_time" version:"1"`        // 1.0 only
   563  				DiskWriteSizeInBytes int    `json:"disk_write_size_in_bytes" version:"1"` // 1.0 only
   564  				DiskWrites           int    `json:"disk_writes" version:"1"`              // 1.0 only
   565  				FreeInBytes          int    `json:"free_in_bytes"`
   566  				Mount                string `json:"mount"`
   567  				Path                 string `json:"path"`
   568  				TotalInBytes         int    `json:"total_in_bytes"`
   569  				Type                 string `json:"type" version:"2"` // 2.0 only
   570  			} `json:"data"`
   571  			Timestamp int `json:"timestamp"`
   572  			Total     struct {
   573  				AvailableInBytes     int    `json:"available_in_bytes"`
   574  				DiskIoOp             int    `json:"disk_io_op" version:"1"`               // 1.0 only
   575  				DiskIoSizeInBytes    int    `json:"disk_io_size_in_bytes" version:"1"`    // 1.0 only
   576  				DiskQueue            string `json:"disk_queue" version:"1"`               // 1.0 only
   577  				DiskReadSizeInBytes  int    `json:"disk_read_size_in_bytes" version:"1"`  // 1.0 only
   578  				DiskReads            int    `json:"disk_reads" version:"1"`               // 1.0 only
   579  				DiskServiceTime      string `json:"disk_service_time" version:"1"`        // 1.0 only
   580  				DiskWriteSizeInBytes int    `json:"disk_write_size_in_bytes" version:"1"` // 1.0 only
   581  				DiskWrites           int    `json:"disk_writes" version:"1"`              // 1.0 only
   582  				FreeInBytes          int    `json:"free_in_bytes"`
   583  				TotalInBytes         int    `json:"total_in_bytes"`
   584  			} `json:"total"`
   585  		} `json:"fs" exclude:"true"`
   586  		Host string `json:"host"`
   587  		HTTP struct {
   588  			CurrentOpen int `json:"current_open"`
   589  			TotalOpened int `json:"total_opened"`
   590  		} `json:"http"`
   591  		Indices ElasticIndexDetails `json:"indices" exclude:"true"` // Stored under elastic.indices.local namespace.
   592  		//IP      []string            `json:"ip" exclude:"true"`	// Incompatible format between 5.x and previous, and not used in collector
   593  		JVM struct {
   594  			BufferPools struct {
   595  				Direct struct {
   596  					Count                int `json:"count"`
   597  					TotalCapacityInBytes int `json:"total_capacity_in_bytes"`
   598  					UsedInBytes          int `json:"used_in_bytes"`
   599  				} `json:"direct"`
   600  				Mapped struct {
   601  					Count                int `json:"count"`
   602  					TotalCapacityInBytes int `json:"total_capacity_in_bytes"`
   603  					UsedInBytes          int `json:"used_in_bytes"`
   604  				} `json:"mapped"`
   605  			} `json:"buffer_pools"`
   606  			Classes struct { // 2.0 only
   607  				CurrentLoadedCount int `json:"current_loaded_count" version:"2"` // 2.0 only
   608  				TotalLoadedCount   int `json:"total_loaded_count" version:"2"`   // 2.0 only
   609  				TotalUnloadedCount int `json:"total_unloaded_count" version:"2"` // 2.0 only
   610  			} `json:"classes"`
   611  			GC struct {
   612  				Collectors struct {
   613  					Old struct {
   614  						CollectionCount        int `json:"collection_count"`
   615  						CollectionTimeInMillis int `json:"collection_time_in_millis"`
   616  					} `json:"old"`
   617  					Young struct {
   618  						CollectionCount        int `json:"collection_count"`
   619  						CollectionTimeInMillis int `json:"collection_time_in_millis"`
   620  					} `json:"young"`
   621  				} `json:"collectors"`
   622  			} `json:"gc" exclude:"true"` // This is recorded manually so we can tag the GC collector type.
   623  			Mem struct {
   624  				HeapCommittedInBytes    int `json:"heap_committed_in_bytes" metric:"heap_committed"`
   625  				HeapMaxInBytes          int `json:"heap_max_in_bytes"`
   626  				HeapUsedInBytes         int `json:"heap_used_in_bytes" metric:"heap_used"`
   627  				HeapUsedPercent         int `json:"heap_used_percent"`
   628  				NonHeapCommittedInBytes int `json:"non_heap_committed_in_bytes"`
   629  				NonHeapUsedInBytes      int `json:"non_heap_used_in_bytes"`
   630  				Pools                   struct {
   631  					Old struct {
   632  						MaxInBytes      int `json:"max_in_bytes"`
   633  						PeakMaxInBytes  int `json:"peak_max_in_bytes"`
   634  						PeakUsedInBytes int `json:"peak_used_in_bytes"`
   635  						UsedInBytes     int `json:"used_in_bytes"`
   636  					} `json:"old"`
   637  					Survivor struct {
   638  						MaxInBytes      int `json:"max_in_bytes"`
   639  						PeakMaxInBytes  int `json:"peak_max_in_bytes"`
   640  						PeakUsedInBytes int `json:"peak_used_in_bytes"`
   641  						UsedInBytes     int `json:"used_in_bytes"`
   642  					} `json:"survivor"`
   643  					Young struct {
   644  						MaxInBytes      int `json:"max_in_bytes"`
   645  						PeakMaxInBytes  int `json:"peak_max_in_bytes"`
   646  						PeakUsedInBytes int `json:"peak_used_in_bytes"`
   647  						UsedInBytes     int `json:"used_in_bytes"`
   648  					} `json:"young"`
   649  				} `json:"pools" exclude:"true"`
   650  			} `json:"mem"`
   651  			Threads struct {
   652  				Count     int `json:"count"`
   653  				PeakCount int `json:"peak_count"`
   654  			} `json:"threads"`
   655  			Timestamp      int `json:"timestamp"`
   656  			UptimeInMillis int `json:"uptime_in_millis"`
   657  		} `json:"jvm"`
   658  		Name    string   `json:"name"`
   659  		Network struct { // 1.0 only
   660  			TCP struct { // 1.0 only
   661  				ActiveOpens  int `json:"active_opens" version:"1"`  // 1.0 only
   662  				AttemptFails int `json:"attempt_fails" version:"1"` // 1.0 only
   663  				CurrEstab    int `json:"curr_estab" version:"1"`    // 1.0 only
   664  				EstabResets  int `json:"estab_resets" version:"1"`  // 1.0 only
   665  				InErrs       int `json:"in_errs" version:"1"`       // 1.0 only
   666  				InSegs       int `json:"in_segs" version:"1"`       // 1.0 only
   667  				OutRsts      int `json:"out_rsts" version:"1"`      // 1.0 only
   668  				OutSegs      int `json:"out_segs" version:"1"`      // 1.0 only
   669  				PassiveOpens int `json:"passive_opens" version:"1"` // 1.0 only
   670  				RetransSegs  int `json:"retrans_segs" version:"1"`  // 1.0 only
   671  			} `json:"tcp"`
   672  		} `json:"network"`
   673  		OS struct {
   674  			CPU struct { // 1.0 only
   675  				Idle   int `json:"idle" version:"1"`   // 1.0 only
   676  				Stolen int `json:"stolen" version:"1"` // 1.0 only
   677  				Sys    int `json:"sys" version:"1"`    // 1.0 only
   678  				Usage  int `json:"usage" version:"1"`  // 1.0 only
   679  				User   int `json:"user" version:"1"`   // 1.0 only
   680  			} `json:"cpu"`
   681  			//			LoadAverage []float64 `json:"load_average"` // 1.0 only
   682  			//			LoadAverage float64 `json:"load_average"` // 2.0 only
   683  			Mem struct {
   684  				ActualFreeInBytes int `json:"actual_free_in_bytes" version:"1"` // 1.0 only
   685  				ActualUsedInBytes int `json:"actual_used_in_bytes" version:"1"` // 1.0 only
   686  				FreeInBytes       int `json:"free_in_bytes"`
   687  				FreePercent       int `json:"free_percent"`
   688  				TotalInBytes      int `json:"total_in_bytes" version:"2"` // 2.0 only
   689  				UsedInBytes       int `json:"used_in_bytes"`
   690  				UsedPercent       int `json:"used_percent"`
   691  			} `json:"mem"`
   692  			Swap struct {
   693  				FreeInBytes  int `json:"free_in_bytes"`
   694  				TotalInBytes int `json:"total_in_bytes" version:"2"` // 2.0 only
   695  				UsedInBytes  int `json:"used_in_bytes"`
   696  			} `json:"swap"`
   697  			Timestamp      int `json:"timestamp"`
   698  			UptimeInMillis int `json:"uptime_in_millis"`
   699  		} `json:"os" exclude:"true"` // These are OS-wide stats, and are already gathered by other collectors.
   700  		Process struct {
   701  			CPU struct {
   702  				Percent       int `json:"percent" exclude:"true"`
   703  				SysInMillis   int `json:"sys_in_millis" version:"1"` // 1.0 only
   704  				TotalInMillis int `json:"total_in_millis"`
   705  				UserInMillis  int `json:"user_in_millis" version:"1"` // 1.0 only
   706  			} `json:"cpu"`
   707  			MaxFileDescriptors int `json:"max_file_descriptors" version:"2"` // 2.0 only
   708  			Mem                struct {
   709  				ResidentInBytes     int `json:"resident_in_bytes" metric:"resident" version:"1"` // 1.0 only
   710  				ShareInBytes        int `json:"share_in_bytes" metric:"shared" version:"1"`      // 1.0 only
   711  				TotalVirtualInBytes int `json:"total_virtual_in_bytes" metric:"total_virtual"`
   712  			} `json:"mem"`
   713  			OpenFileDescriptors int `json:"open_file_descriptors"`
   714  			Timestamp           int `json:"timestamp" exclude:"true"`
   715  		} `json:"process"`
   716  		Script struct { // 2.0 only
   717  			CacheEvictions int `json:"cache_evictions" version:"2"` // 2.0 only
   718  			Compilations   int `json:"compilations" version:"2"`    // 2.0 only
   719  		} `json:"script"`
   720  		ThreadPool struct {
   721  			Bulk              ElasticThreadPoolStat `json:"bulk"`
   722  			FetchShardStarted ElasticThreadPoolStat `json:"fetch_shard_started" version:"2"` // 2.0 only
   723  			FetchShardStore   ElasticThreadPoolStat `json:"fetch_shard_store" version:"2"`   // 2.0 only
   724  			Flush             ElasticThreadPoolStat `json:"flush"`
   725  			Generic           ElasticThreadPoolStat `json:"generic"`
   726  			Get               ElasticThreadPoolStat `json:"get"`
   727  			Index             ElasticThreadPoolStat `json:"index"`
   728  			Listener          ElasticThreadPoolStat `json:"listener"`
   729  			Management        ElasticThreadPoolStat `json:"management"`
   730  			Merge             ElasticThreadPoolStat `json:"merge" version:"1"` // 1.0 only
   731  			Optimize          ElasticThreadPoolStat `json:"optimize"`
   732  			Percolate         ElasticThreadPoolStat `json:"percolate"`
   733  			Refresh           ElasticThreadPoolStat `json:"refresh"`
   734  			Search            ElasticThreadPoolStat `json:"search"`
   735  			Snapshot          ElasticThreadPoolStat `json:"snapshot"`
   736  			Suggest           ElasticThreadPoolStat `json:"suggest"`
   737  			Warmer            ElasticThreadPoolStat `json:"warmer"`
   738  		} `json:"thread_pool" exclude:"true"`
   739  		Timestamp int `json:"timestamp"`
   740  		Transport struct {
   741  			RxCount       int `json:"rx_count"`
   742  			RxSizeInBytes int `json:"rx_size_in_bytes"`
   743  			ServerOpen    int `json:"server_open"`
   744  			TxCount       int `json:"tx_count"`
   745  			TxSizeInBytes int `json:"tx_size_in_bytes"`
   746  		} `json:"transport"`
   747  		TransportAddress string `json:"transport_address"`
   748  	} `json:"nodes"`
   749  }
   750  
   751  type ElasticThreadPoolStat struct {
   752  	Active    int `json:"active"`
   753  	Completed int `json:"completed"`
   754  	Largest   int `json:"largest"`
   755  	Queue     int `json:"queue"`
   756  	Rejected  int `json:"rejected"`
   757  	Threads   int `json:"threads"`
   758  }
   759  
   760  type ElasticBreakersStat struct {
   761  	EstimatedSize        string  `json:"estimated_size"`
   762  	EstimatedSizeInBytes int     `json:"estimated_size_in_bytes"`
   763  	LimitSize            string  `json:"limit_size"`
   764  	LimitSizeInBytes     int     `json:"limit_size_in_bytes"`
   765  	Overhead             float64 `json:"overhead"`
   766  	Tripped              int     `json:"tripped"`
   767  }
   768  
   769  type ElasticClusterState struct {
   770  	MasterNode string `json:"master_node"`
   771  }