youtubebeat/vendor/github.com/elastic/beats/metricbeat/module/elasticsearch/index/data_xpack.go

344 lines
10 KiB
Go

// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package index
import (
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/elastic/beats/libbeat/common"
s "github.com/elastic/beats/libbeat/common/schema"
c "github.com/elastic/beats/libbeat/common/schema/mapstriface"
"github.com/elastic/beats/metricbeat/helper/elastic"
"github.com/elastic/beats/metricbeat/mb"
"github.com/elastic/beats/metricbeat/module/elasticsearch"
)
var (
// Based on https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDoc.java#L127-L203
xpackSchema = s.Schema{
"uuid": c.Str("uuid"),
"primaries": c.Dict("primaries", indexStatsSchema),
"total": c.Dict("total", indexStatsSchema),
}
indexStatsSchema = s.Schema{
"docs": c.Dict("docs", s.Schema{
"count": c.Int("count"),
}),
"fielddata": c.Dict("fielddata", s.Schema{
"memory_size_in_bytes": c.Int("memory_size_in_bytes"),
"evictions": c.Int("evictions"),
}),
"indexing": c.Dict("indexing", s.Schema{
"index_total": c.Int("index_total"),
"index_time_in_millis": c.Int("index_time_in_millis"),
"throttle_time_in_millis": c.Int("throttle_time_in_millis"),
}),
"merges": c.Dict("merges", s.Schema{
"total_size_in_bytes": c.Int("total_size_in_bytes"),
}),
"query_cache": c.Dict("query_cache", cacheStatsSchema),
"request_cache": c.Dict("request_cache", cacheStatsSchema),
"search": c.Dict("search", s.Schema{
"query_total": c.Int("query_total"),
"query_time_in_millis": c.Int("query_time_in_millis"),
}),
"segments": c.Dict("segments", s.Schema{
"count": c.Int("count"),
"memory_in_bytes": c.Int("memory_in_bytes"),
"terms_memory_in_bytes": c.Int("terms_memory_in_bytes"),
"stored_fields_memory_in_bytes": c.Int("stored_fields_memory_in_bytes"),
"term_vectors_memory_in_bytes": c.Int("term_vectors_memory_in_bytes"),
"norms_memory_in_bytes": c.Int("norms_memory_in_bytes"),
"points_memory_in_bytes": c.Int("points_memory_in_bytes"),
"doc_values_memory_in_bytes": c.Int("doc_values_memory_in_bytes"),
"index_writer_memory_in_bytes": c.Int("index_writer_memory_in_bytes"),
"version_map_memory_in_bytes": c.Int("version_map_memory_in_bytes"),
"fixed_bit_set_memory_in_bytes": c.Int("fixed_bit_set_memory_in_bytes"),
}),
"store": c.Dict("store", s.Schema{
"size_in_bytes": c.Int("size_in_bytes"),
}),
"refresh": c.Dict("refresh", s.Schema{
"total_time_in_millis": c.Int("total_time_in_millis"),
}),
}
cacheStatsSchema = s.Schema{
"memory_size_in_bytes": c.Int("memory_size_in_bytes"),
"evictions": c.Int("evictions"),
"hit_count": c.Int("hit_count"),
"miss_count": c.Int("miss_count"),
}
)
func eventsMappingXPack(r mb.ReporterV2, m *MetricSet, info elasticsearch.Info, content []byte) error {
var indicesStruct IndicesStruct
err := json.Unmarshal(content, &indicesStruct)
if err != nil {
err = errors.Wrap(err, "failure parsing Indices Stats Elasticsearch API response")
m.Log.Error(err)
return err
}
clusterStateMetrics := []string{"metadata", "routing_table"}
clusterState, err := elasticsearch.GetClusterState(m.HTTP, m.HTTP.GetURI(), clusterStateMetrics)
if err != nil {
err = errors.Wrap(err, "failure retrieving cluster state from Elasticsearch")
m.Log.Error(err)
return err
}
for name, index := range indicesStruct.Indices {
event := mb.Event{}
indexStats, err := xpackSchema.Apply(index)
if err != nil {
m.Log.Error(errors.Wrap(err, "failure applying index stats schema"))
continue
}
indexStats["index"] = name
err = addClusterStateFields(name, indexStats, clusterState)
if err != nil {
m.Log.Error(errors.Wrap(err, "failure adding cluster state fields"))
continue
}
event.RootFields = common.MapStr{
"cluster_uuid": info.ClusterID,
"timestamp": common.Time(time.Now()),
"interval_ms": m.Module().Config().Period / time.Millisecond,
"type": "index_stats",
"index_stats": indexStats,
}
event.Index = elastic.MakeXPackMonitoringIndexName(elastic.Elasticsearch)
r.Event(event)
}
return nil
}
// Fields added here are based on same fields being added by internal collection in
// https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDoc.java#L62-L124
func addClusterStateFields(indexName string, indexStats, clusterState common.MapStr) error {
indexMetadata, err := getClusterStateMetricForIndex(clusterState, indexName, "metadata")
if err != nil {
return errors.Wrap(err, "failed to get index metadata from cluster state")
}
indexRoutingTable, err := getClusterStateMetricForIndex(clusterState, indexName, "routing_table")
if err != nil {
return errors.Wrap(err, "failed to get index routing table from cluster state")
}
shards, err := getShardsFromRoutingTable(indexRoutingTable)
if err != nil {
return errors.Wrap(err, "failed to get shards from routing table")
}
created, err := getIndexCreated(indexMetadata)
if err != nil {
return errors.Wrap(err, "failed to get index creation time")
}
indexStats.Put("created", created)
// "index_stats.version.created", <--- don't think this is being used in the UI, so can we skip it?
// "index_stats.version.upgraded", <--- don't think this is being used in the UI, so can we skip it?
status, err := getIndexStatus(shards)
if err != nil {
return errors.Wrap(err, "failed to get index status")
}
indexStats.Put("status", status)
shardStats, err := getIndexShardStats(shards)
if err != nil {
return errors.Wrap(err, "failed to get index shard stats")
}
indexStats.Put("shards", shardStats)
return nil
}
func getClusterStateMetricForIndex(clusterState common.MapStr, index, metricKey string) (common.MapStr, error) {
fieldKey := metricKey + ".indices." + index
value, err := clusterState.GetValue(fieldKey)
if err != nil {
return nil, err
}
metric, ok := value.(map[string]interface{})
if !ok {
return nil, elastic.MakeErrorForMissingField(fieldKey, elastic.Elasticsearch)
}
return common.MapStr(metric), nil
}
func getIndexStatus(shards map[string]interface{}) (string, error) {
if len(shards) == 0 {
// No shards, index is red
return "red", nil
}
areAllPrimariesStarted := true
areAllReplicasStarted := true
for indexName, indexShard := range shards {
is, ok := indexShard.([]interface{})
if !ok {
return "", fmt.Errorf("shards is not an array")
}
for shardIdx, shard := range is {
s, ok := shard.(map[string]interface{})
if !ok {
return "", fmt.Errorf("%v.shards[%v] is not a map", indexName, shardIdx)
}
shard := common.MapStr(s)
isPrimary := shard["primary"].(bool)
state := shard["state"].(string)
if isPrimary {
areAllPrimariesStarted = areAllPrimariesStarted && (state == "STARTED")
} else {
areAllReplicasStarted = areAllReplicasStarted && (state == "STARTED")
}
}
}
if areAllPrimariesStarted && areAllReplicasStarted {
return "green", nil
}
if areAllPrimariesStarted && !areAllReplicasStarted {
return "yellow", nil
}
return "red", nil
}
func getIndexShardStats(shards common.MapStr) (common.MapStr, error) {
primaries := 0
replicas := 0
activePrimaries := 0
activeReplicas := 0
unassignedPrimaries := 0
unassignedReplicas := 0
initializing := 0
relocating := 0
for indexName, indexShard := range shards {
is, ok := indexShard.([]interface{})
if !ok {
return nil, fmt.Errorf("shards is not an array")
}
for shardIdx, shard := range is {
s, ok := shard.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%v.shards[%v] is not a map", indexName, shardIdx)
}
shard := common.MapStr(s)
isPrimary := shard["primary"].(bool)
state := shard["state"].(string)
if isPrimary {
primaries++
switch state {
case "STARTED":
activePrimaries++
case "UNASSIGNED":
unassignedPrimaries++
}
} else {
replicas++
switch state {
case "STARTED":
activeReplicas++
case "UNASSIGNED":
unassignedReplicas++
}
}
switch state {
case "INITIALIZING":
initializing++
case "RELOCATING":
relocating++
}
}
}
return common.MapStr{
"total": primaries + replicas,
"primaries": primaries,
"replicas": replicas,
"active_total": activePrimaries + activeReplicas,
"active_primaries": activePrimaries,
"active_replicas": activeReplicas,
"unassigned_total": unassignedPrimaries + unassignedReplicas,
"unassigned_primaries": unassignedPrimaries,
"unassigned_replicas": unassignedReplicas,
"initializing": initializing,
"relocating": relocating,
}, nil
}
func getIndexCreated(indexMetadata common.MapStr) (int64, error) {
v, err := indexMetadata.GetValue("settings.index.creation_date")
if err != nil {
return 0, err
}
c, ok := v.(string)
if !ok {
return 0, elastic.MakeErrorForMissingField("settings.index.creation_date", elastic.Elasticsearch)
}
return strconv.ParseInt(c, 10, 64)
}
func getShardsFromRoutingTable(indexRoutingTable common.MapStr) (map[string]interface{}, error) {
s, err := indexRoutingTable.GetValue("shards")
if err != nil {
return nil, err
}
shards, ok := s.(map[string]interface{})
if !ok {
return nil, elastic.MakeErrorForMissingField("shards", elastic.Elasticsearch)
}
return shards, nil
}