diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 2cd4313384d5..23147238cf03 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -117,6 +117,7 @@ https://github.com/elastic/beats/compare/v6.0.0-beta2...master[Check the HEAD di - Add Kubernetes manifests to deploy Metricbeat. {pull}5349[5349] - Add etcd module. {issue}4970[4970] - Add ip address of docker containers to event. {pull}5379[5379] +- Add ceph osd tree infomation to metricbeat {pull}5498[5498] *Packetbeat* diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index ab11a7cb77ba..ff63740b46e0 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -1192,6 +1192,117 @@ type: long Last updated +[float] +== osd_tree fields + +ceph osd tree info + + + +[float] +=== `ceph.osd_tree.id` + +type: long + +osd or bucket node id + + +[float] +=== `ceph.osd_tree.name` + +type: text + +osd or bucket node name + + +[float] +=== `ceph.osd_tree.type` + +type: keyword + +osd or bucket node type, illegal type include osd, host, root etc. + + +[float] +=== `ceph.osd_tree.type_id` + +type: long + +osd or bucket node typeID + + +[float] +=== `ceph.osd_tree.children` + +type: text + +bucket children list, seperate by comma. + + +[float] +=== `ceph.osd_tree.crush_weight` + +type: float + +osd node crush weight + + +[float] +=== `ceph.osd_tree.depth` + +type: long + +node depth + + +[float] +=== `ceph.osd_tree.exists` + +type: boolean + +is node still exist or not(1-yes, 0-no) + + +[float] +=== `ceph.osd_tree.primary_affinity` + +type: float + +the weight of reading data from primary osd + + +[float] +=== `ceph.osd_tree.reweight` + +type: long + +the reweight of osd + + +[float] +=== `ceph.osd_tree.status` + +type: keyword + +status of osd, it should be up or down + + +[float] +=== `ceph.osd_tree.device_class` + +type: keyword + +the device class of osd, like hdd, ssd etc. + + +[float] +=== `ceph.osd_tree.father` + +type: keyword + +the parent node of this osd or bucket node + + [float] == pool_disk fields diff --git a/metricbeat/docs/modules/ceph.asciidoc b/metricbeat/docs/modules/ceph.asciidoc index 5b2843395c1e..273c346e50d3 100644 --- a/metricbeat/docs/modules/ceph.asciidoc +++ b/metricbeat/docs/modules/ceph.asciidoc @@ -21,7 +21,7 @@ in <>. Here is an example configuration: ---- metricbeat.modules: - module: ceph - metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk"] + metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk", "osd_tree"] period: 10s hosts: ["localhost:5000"] ---- @@ -39,6 +39,8 @@ The following metricsets are available: * <> +* <> + * <> include::ceph/cluster_disk.asciidoc[] @@ -49,5 +51,7 @@ include::ceph/cluster_status.asciidoc[] include::ceph/monitor_health.asciidoc[] +include::ceph/osd_tree.asciidoc[] + include::ceph/pool_disk.asciidoc[] diff --git a/metricbeat/docs/modules/ceph/osd_tree.asciidoc b/metricbeat/docs/modules/ceph/osd_tree.asciidoc new file mode 100644 index 000000000000..a6b08c502b4d --- /dev/null +++ b/metricbeat/docs/modules/ceph/osd_tree.asciidoc @@ -0,0 +1,19 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-metricset-ceph-osd_tree]] +include::../../../module/ceph/osd_tree/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/ceph/osd_tree/_meta/data.json[] +---- diff --git a/metricbeat/include/list.go b/metricbeat/include/list.go index 693c10e71384..3a891863687f 100644 --- a/metricbeat/include/list.go +++ b/metricbeat/include/list.go @@ -17,6 +17,7 @@ import ( _ "github.com/elastic/beats/metricbeat/module/ceph/cluster_health" _ "github.com/elastic/beats/metricbeat/module/ceph/cluster_status" _ "github.com/elastic/beats/metricbeat/module/ceph/monitor_health" + _ "github.com/elastic/beats/metricbeat/module/ceph/osd_tree" _ "github.com/elastic/beats/metricbeat/module/ceph/pool_disk" _ "github.com/elastic/beats/metricbeat/module/couchbase" _ "github.com/elastic/beats/metricbeat/module/couchbase/bucket" diff --git a/metricbeat/metricbeat.reference.yml b/metricbeat/metricbeat.reference.yml index 53d585a83a00..d2943591bc8a 100644 --- a/metricbeat/metricbeat.reference.yml +++ b/metricbeat/metricbeat.reference.yml @@ -119,7 +119,7 @@ metricbeat.modules: #-------------------------------- Ceph Module -------------------------------- - module: ceph - metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk"] + metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk", "osd_tree"] period: 10s hosts: ["localhost:5000"] diff --git a/metricbeat/module/ceph/_meta/config.yml b/metricbeat/module/ceph/_meta/config.yml index 21ee68217332..a069c8e69b4b 100644 --- a/metricbeat/module/ceph/_meta/config.yml +++ b/metricbeat/module/ceph/_meta/config.yml @@ -1,4 +1,4 @@ - module: ceph - metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk"] + metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk", "osd_tree"] period: 10s hosts: ["localhost:5000"] diff --git a/metricbeat/module/ceph/_meta/testdata/osd_tree_sample_response.json b/metricbeat/module/ceph/_meta/testdata/osd_tree_sample_response.json new file mode 100644 index 000000000000..1f43d7c68f15 --- /dev/null +++ b/metricbeat/module/ceph/_meta/testdata/osd_tree_sample_response.json @@ -0,0 +1,56 @@ +{ + "output": { + "nodes": [ + { + "children": [ + -3 + ], + "id": -1, + "name": "default", + "type": "root", + "type_id": 10 + }, + { + "children": [ + 1, + 0 + ], + "id": -3, + "name": "ceph-mon1", + "pool_weights": {}, + "type": "host", + "type_id": 1 + }, + { + "crush_weight": 0.048691, + "depth": 2, + "device_class": "hdd", + "exists": 1, + "id": 0, + "name": "osd.0", + "pool_weights": {}, + "primary_affinity": 1.0, + "reweight": 1.0, + "status": "up", + "type": "osd", + "type_id": 0 + }, + { + "crush_weight": 0.048691, + "depth": 2, + "device_class": "hdd", + "exists": 1, + "id": 1, + "name": "osd.1", + "pool_weights": {}, + "primary_affinity": 1.0, + "reweight": 1.0, + "status": "up", + "type": "osd", + "type_id": 0 + } + ], + "stray": [] + }, + "status": "OK" +} \ No newline at end of file diff --git a/metricbeat/module/ceph/osd_tree/_meta/data.json b/metricbeat/module/ceph/osd_tree/_meta/data.json new file mode 100644 index 000000000000..b6def740376e --- /dev/null +++ b/metricbeat/module/ceph/osd_tree/_meta/data.json @@ -0,0 +1,35 @@ +{ + "@timestamp": "2017-11-01T07:26:34.876Z", + "@metadata": { + "beat": "metricbeat", + "type": "doc", + "version": "7.0.0-alpha1" + }, + "ceph": { + "osd_tree": { + "status": "up", + "name": "osd.0", + "type": "osd", + "primary_affinity": 1, + "exists": true, + "id": 0, + "type_id": 0, + "crush_weight": 0.048691, + "device_class": "hdd", + "reweight": 1, + "father": "ceph-mon1", + "depth": 2 + } + }, + "metricset": { + "rtt": 1331122, + "module": "ceph", + "name": "osd_tree", + "host": "192.168.56.131:5000" + }, + "beat": { + "hostname": "centos7gui", + "version": "7.0.0-alpha1", + "name": "centos7gui" + } +} \ No newline at end of file diff --git a/metricbeat/module/ceph/osd_tree/_meta/docs.asciidoc b/metricbeat/module/ceph/osd_tree/_meta/docs.asciidoc new file mode 100644 index 000000000000..4a14002a18be --- /dev/null +++ b/metricbeat/module/ceph/osd_tree/_meta/docs.asciidoc @@ -0,0 +1,3 @@ +=== Ceph cluster_health metricset + +This is the `osd_tree` metricset of the Ceph module. \ No newline at end of file diff --git a/metricbeat/module/ceph/osd_tree/_meta/fields.yml b/metricbeat/module/ceph/osd_tree/_meta/fields.yml new file mode 100644 index 000000000000..5a19be7203e2 --- /dev/null +++ b/metricbeat/module/ceph/osd_tree/_meta/fields.yml @@ -0,0 +1,57 @@ +- name: osd_tree + type: group + description: > + ceph osd tree info + fields: + - name: id + type: long + description: > + osd or bucket node id + - name: name + type: text + description: > + osd or bucket node name + - name: type + type: keyword + description: > + osd or bucket node type, illegal type include osd, host, root etc. + - name: type_id + type: long + description: > + osd or bucket node typeID + - name: children + type: text + description: > + bucket children list, seperate by comma. + - name: crush_weight + type: float + description: > + osd node crush weight + - name: depth + type: long + description: > + node depth + - name: exists + type: boolean + description: > + is node still exist or not(1-yes, 0-no) + - name: primary_affinity + type: float + description: > + the weight of reading data from primary osd + - name: reweight + type: long + description: > + the reweight of osd + - name: status + type: keyword + description: > + status of osd, it should be up or down + - name: device_class + type: keyword + description: > + the device class of osd, like hdd, ssd etc. + - name: father + type: keyword + description: > + the parent node of this osd or bucket node diff --git a/metricbeat/module/ceph/osd_tree/data.go b/metricbeat/module/ceph/osd_tree/data.go new file mode 100644 index 000000000000..abec86d61699 --- /dev/null +++ b/metricbeat/module/ceph/osd_tree/data.go @@ -0,0 +1,99 @@ +package osd_tree + +import ( + "encoding/json" + "strconv" + "strings" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" +) + +type Node struct { + ID int64 `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + TypeID int64 `json:"type_id"` + Children []int64 `json:"children"` + + CrushWeight float64 `json:"crush_weight"` + Depth int64 `json:"depth"` + Exist int64 `json:"exists"` + PrimaryAffinity float64 `json:"primary_affinity"` + Reweight float64 `json:"reweight"` + Status string `json:"status"` + DeviceClass string `json:"device_class"` +} + +type Output struct { + Nodes []Node `json:"nodes"` +} + +type OsdTreeRequest struct { + Status string `json:"status"` + Output Output `json:"output"` +} + +func eventsMapping(content []byte) ([]common.MapStr, error) { + var d OsdTreeRequest + err := json.Unmarshal(content, &d) + if err != nil { + logp.Err("Error: ", err) + return nil, err + } + + nodeList := d.Output.Nodes + + //generate fatherNode and children map + fatherMap := make(map[string]string) + childrenMap := make(map[string]string) + + for _, node := range nodeList { + if node.ID >= 0 { + //it's osd node + continue + } + childrenList := []string{} + for _, child := range node.Children { + childIDStr := strconv.FormatInt(child, 10) + childrenList = append(childrenList, childIDStr) + fatherMap[childIDStr] = node.Name + } + //generate bucket node's children list + childrenMap[node.Name] = strings.Join(childrenList, ",") + } + + //osd node list + events := []common.MapStr{} + for _, node := range nodeList { + nodeInfo := common.MapStr{} + if node.ID < 0 { + //bucket node + nodeInfo["children"] = childrenMap[node.Name] + } else { + //osd node + nodeInfo["crush_weight"] = node.CrushWeight + nodeInfo["depth"] = node.Depth + nodeInfo["primary_affinity"] = node.PrimaryAffinity + nodeInfo["reweight"] = node.Reweight + nodeInfo["status"] = node.Status + nodeInfo["device_class"] = node.DeviceClass + if node.Exist > 0 { + nodeInfo["exists"] = true + } else { + nodeInfo["exists"] = false + } + } + nodeInfo["id"] = node.ID + nodeInfo["name"] = node.Name + nodeInfo["type"] = node.Type + nodeInfo["type_id"] = node.TypeID + + idStr := strconv.FormatInt(node.ID, 10) + nodeInfo["father"] = fatherMap[idStr] + + events = append(events, nodeInfo) + } + + return events, nil +} diff --git a/metricbeat/module/ceph/osd_tree/osd_tree.go b/metricbeat/module/ceph/osd_tree/osd_tree.go new file mode 100644 index 000000000000..c54cf5f9a63d --- /dev/null +++ b/metricbeat/module/ceph/osd_tree/osd_tree.go @@ -0,0 +1,58 @@ +package osd_tree + +import ( + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/common/cfgwarn" + "github.com/elastic/beats/metricbeat/helper" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/mb/parse" +) + +const ( + defaultScheme = "http" + defaultPath = "/api/v0.1/osd/tree" +) + +var ( + hostParser = parse.URLHostParserBuilder{ + DefaultScheme: defaultScheme, + DefaultPath: defaultPath, + }.Build() +) + +func init() { + if err := mb.Registry.AddMetricSet("ceph", "osd_tree", New, hostParser); err != nil { + panic(err) + } +} + +type MetricSet struct { + mb.BaseMetricSet + *helper.HTTP +} + +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The ceph osd_tree metricset is beta") + + http := helper.NewHTTP(base) + http.SetHeader("Accept", "application/json") + + return &MetricSet{ + base, + http, + }, nil +} + +func (m *MetricSet) Fetch() ([]common.MapStr, error) { + content, err := m.HTTP.FetchContent() + if err != nil { + return nil, err + } + + events, err := eventsMapping(content) + if err != nil { + return nil, err + } + + return events, nil +} diff --git a/metricbeat/module/ceph/osd_tree/osd_tree_integration_test.go b/metricbeat/module/ceph/osd_tree/osd_tree_integration_test.go new file mode 100644 index 000000000000..12464c6b131c --- /dev/null +++ b/metricbeat/module/ceph/osd_tree/osd_tree_integration_test.go @@ -0,0 +1,48 @@ +package osd_tree + +import ( + "fmt" + "os" + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" +) + +func TestData(t *testing.T) { + f := mbtest.NewEventsFetcher(t, getConfig()) + err := mbtest.WriteEvents(f, t) + if err != nil { + t.Fatal("write", err) + } +} + +func getConfig() map[string]interface{} { + return map[string]interface{}{ + "module": "ceph", + "metricsets": []string{"osd_tree"}, + "hosts": getTestCephHost(), + } +} + +const ( + cephDefaultHost = "127.0.0.1" + cephDefaultPort = "5000" +) + +func getTestCephHost() string { + return fmt.Sprintf("%v:%v", + getenv("CEPH_HOST", cephDefaultHost), + getenv("CEPH_PORT", cephDefaultPort), + ) +} + +func getenv(name, defaultValue string) string { + return strDefault(os.Getenv(name), defaultValue) +} + +func strDefault(a, defaults string) string { + if len(a) == 0 { + return defaults + } + return a +} diff --git a/metricbeat/module/ceph/osd_tree/osd_tree_test.go b/metricbeat/module/ceph/osd_tree/osd_tree_test.go new file mode 100644 index 000000000000..4a4763239520 --- /dev/null +++ b/metricbeat/module/ceph/osd_tree/osd_tree_test.go @@ -0,0 +1,88 @@ +package osd_tree + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "path/filepath" + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + "github.com/stretchr/testify/assert" +) + +func TestFetchEventContents(t *testing.T) { + absPath, err := filepath.Abs("../_meta/testdata/") + assert.NoError(t, err) + + response, err := ioutil.ReadFile(absPath + "/osd_tree_sample_response.json") + assert.NoError(t, err) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Header().Set("Content-Type", "application/json;") + w.Write([]byte(response)) + })) + defer server.Close() + + config := map[string]interface{}{ + "module": "ceph", + "metricsets": []string{"osd_tree"}, + "hosts": []string{server.URL}, + } + + f := mbtest.NewEventsFetcher(t, config) + events, err := f.Fetch() + event := events[0] + + t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event.StringToPrint()) + + //check root bucket info + nodeInfo := events[0] + assert.EqualValues(t, "default", nodeInfo["name"]) + assert.EqualValues(t, "root", nodeInfo["type"]) + assert.EqualValues(t, "-3", nodeInfo["children"]) + assert.EqualValues(t, -1, nodeInfo["id"]) + assert.EqualValues(t, 10, nodeInfo["type_id"]) + assert.EqualValues(t, "", nodeInfo["father"]) + + //check host bucket info + nodeInfo = events[1] + assert.EqualValues(t, "ceph-mon1", nodeInfo["name"]) + assert.EqualValues(t, "host", nodeInfo["type"]) + assert.EqualValues(t, "1,0", nodeInfo["children"]) + assert.EqualValues(t, -3, nodeInfo["id"]) + assert.EqualValues(t, 1, nodeInfo["type_id"]) + assert.EqualValues(t, "default", nodeInfo["father"]) + + //check osd bucket info + nodeInfo = events[2] + assert.EqualValues(t, "up", nodeInfo["status"]) + assert.EqualValues(t, "osd.0", nodeInfo["name"]) + assert.EqualValues(t, "osd", nodeInfo["type"]) + assert.EqualValues(t, 1, nodeInfo["primary_affinity"]) + assert.EqualValues(t, true, nodeInfo["exists"]) + assert.EqualValues(t, 0, nodeInfo["id"]) + assert.EqualValues(t, 0, nodeInfo["type_id"]) + assert.EqualValues(t, 0.048691, nodeInfo["crush_weight"]) + assert.EqualValues(t, "hdd", nodeInfo["device_class"]) + assert.EqualValues(t, 1, nodeInfo["reweight"]) + assert.EqualValues(t, "ceph-mon1", nodeInfo["father"]) + assert.EqualValues(t, 2, nodeInfo["depth"]) + + nodeInfo = events[3] + assert.EqualValues(t, "up", nodeInfo["status"]) + assert.EqualValues(t, "osd.1", nodeInfo["name"]) + assert.EqualValues(t, "osd", nodeInfo["type"]) + assert.EqualValues(t, 1, nodeInfo["primary_affinity"]) + assert.EqualValues(t, true, nodeInfo["exists"]) + assert.EqualValues(t, 1, nodeInfo["id"]) + assert.EqualValues(t, 0, nodeInfo["type_id"]) + assert.EqualValues(t, 0.048691, nodeInfo["crush_weight"]) + assert.EqualValues(t, "hdd", nodeInfo["device_class"]) + assert.EqualValues(t, 1, nodeInfo["reweight"]) + assert.EqualValues(t, "ceph-mon1", nodeInfo["father"]) + assert.EqualValues(t, 2, nodeInfo["depth"]) + +} diff --git a/metricbeat/modules.d/ceph.yml.disabled b/metricbeat/modules.d/ceph.yml.disabled index 21ee68217332..a069c8e69b4b 100644 --- a/metricbeat/modules.d/ceph.yml.disabled +++ b/metricbeat/modules.d/ceph.yml.disabled @@ -1,4 +1,4 @@ - module: ceph - metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk"] + metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk", "osd_tree"] period: 10s hosts: ["localhost:5000"]