-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
collectors/pool: add pool info collector
- Loading branch information
1 parent
53db874
commit 4c8fb2d
Showing
2 changed files
with
309 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
// Copyright 2016 DigitalOcean | ||
// | ||
// Licensed 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 collectors | ||
|
||
import ( | ||
"encoding/json" | ||
"log" | ||
|
||
"github.com/prometheus/client_golang/prometheus" | ||
) | ||
|
||
// PoolInfoCollector gives information about each pool that exists in a given | ||
// ceph cluster. | ||
type PoolInfoCollector struct { | ||
conn Conn | ||
|
||
PGNum *prometheus.GaugeVec | ||
|
||
PlacementPGNum *prometheus.GaugeVec | ||
|
||
MinSize *prometheus.GaugeVec | ||
|
||
ActualSize *prometheus.GaugeVec | ||
|
||
QuotaMaxBytes *prometheus.GaugeVec | ||
|
||
QuotaMaxObjects *prometheus.GaugeVec | ||
|
||
StripeWidth *prometheus.GaugeVec | ||
} | ||
|
||
// NewPoolInfoCollector displays information about each pool in the cluster. | ||
func NewPoolInfoCollector(conn Conn, cluster string) *PoolInfoCollector { | ||
var ( | ||
subSystem = "pool" | ||
poolLabels = []string{"pool", "profile"} | ||
) | ||
|
||
labels := make(prometheus.Labels) | ||
labels["cluster"] = cluster | ||
|
||
return &PoolUsageCollector{ | ||
conn: conn, | ||
|
||
PGNum: prometheus.NewGaugeVec( | ||
prometheus.GaugeOpts{ | ||
Namespace: cephNamespace, | ||
Subsystem: subSystem, | ||
Name: "pg_num", | ||
Help: "The total count of PGs alotted to a pool", | ||
ConstLabels: labels, | ||
}, | ||
poolLabels, | ||
), | ||
PlacementPGNum: prometheus.NewGaugeVec( | ||
prometheus.GaugeOpts{ | ||
Namespace: cephNamespace, | ||
Subsystem: subSystem, | ||
Name: "pgp_num", | ||
Help: "The total count of PGs alotted to a pool and used for placements", | ||
ConstLabels: labels, | ||
}, | ||
poolLabels, | ||
), | ||
MinSize: prometheus.NewGaugeVec( | ||
prometheus.GaugeOpts{ | ||
Namespace: cephNamespace, | ||
Subsystem: subSystem, | ||
Name: "min_size", | ||
Help: "Minimum number of copies or chunks of an object that need to be present for active I/O", | ||
ConstLabels: labels, | ||
}, | ||
poolLabels, | ||
), | ||
ActualSize: prometheus.NewGaugeVec( | ||
prometheus.GaugeOpts{ | ||
Namespace: cephNamespace, | ||
Subsystem: subSystem, | ||
Name: "size", | ||
Help: "Total copies or chunks of an object that need to be present for a healthy cluster", | ||
ConstLabels: labels, | ||
}, | ||
poolLabels, | ||
), | ||
QuotaMaxBytes: prometheus.NewGaugeVec( | ||
prometheus.GaugeOpts{ | ||
Namespace: cephNamespace, | ||
Subsystem: subSystem, | ||
Name: "quota_max_bytes", | ||
Help: "Maximum amount of bytes of data allowed in a pool", | ||
ConstLabels: labels, | ||
}, | ||
poolLabels, | ||
), | ||
QuotaMaxObjects: prometheus.NewGaugeVec( | ||
prometheus.GaugeOpts{ | ||
Namespace: cephNamespace, | ||
Subsystem: subSystem, | ||
Name: "quota_max_objects", | ||
Help: "Maximum amount of RADOS objects allowed in a pool", | ||
ConstLabels: labels, | ||
}, | ||
poolLabels, | ||
), | ||
StripeWidth: prometheus.NewGaugeVec( | ||
prometheus.GaugeOpts{ | ||
Namespace: cephNamespace, | ||
Subsystem: subSystem, | ||
Name: "stripe_width", | ||
Help: "Stripe width of a RADOS object in a pool", | ||
ConstLabels: labels, | ||
}, | ||
poolLabels, | ||
), | ||
} | ||
} | ||
|
||
func (p *PoolInfoCollector) collectorList() []prometheus.Collector { | ||
return []prometheus.Collector{ | ||
p.PGNum, | ||
p.PlacementPGNum, | ||
p.MinSize, | ||
p.ActualSize, | ||
p.QuotaMaxBytes, | ||
p.QuotaMaxObjects, | ||
p.StripeWidth, | ||
} | ||
} | ||
|
||
type cephPoolInfo struct { | ||
Pools []struct { | ||
Name string `json:"pool_name"` | ||
ActualSize float64 `json:"size"` | ||
MinSize float64 `json:"min_size"` | ||
PGNum float64 `json:"pg_num"` | ||
PlacementPGNum float64 `json:"pg_placement_num"` | ||
QuotaMaxBytes float64 `json:"quota_max_bytes"` | ||
QuotaMaxObjects float64 `json:"quota_max_objects"` | ||
Profile string `json:"erasure_code_profile"` | ||
StripeWidth float64 `json:"stripe_width"` | ||
} | ||
} | ||
|
||
func (p *PoolInfoCollector) collect() error { | ||
cmd := p.cephInfoCommand() | ||
buf, _, err := p.conn.MonCommand(cmd) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
stats := &cephPoolInfo{} | ||
if err := json.Unmarshal(buf, &stats.Pools); err != nil { | ||
return err | ||
} | ||
|
||
// Reset pool specfic metrics, pools can be removed | ||
p.PGNum.Reset() | ||
p.PlacementPGNum.Reset() | ||
p.MinSize.Reset() | ||
p.ActualSize.Reset() | ||
p.QuotaMaxBytes.Reset() | ||
p.QuotaMaxObjects.Reset() | ||
p.StripeWidth.Reset() | ||
|
||
for _, pool := range stats.Pools { | ||
p.PGNum.WithLabelValues(pool.Name, pool.Profile).Set(pool.PGNum) | ||
p.PlacementPGNum.WithLabelValues(pool.Name, pool.Profile).Set(pool.PlacementPGNum) | ||
p.MinSize.WithLabelValues(pool.Name, pool.Profile).Set(pool.MinSize) | ||
p.ActualSize.WithLabelValues(pool.Name, pool.Profile).Set(pool.ActualSize) | ||
p.QuotaMaxBytes.WithLabelValues(pool.Name, pool.Profile).Set(pool.QuotaMaxBytes) | ||
p.QuotaMaxObjects.WithLabelValues(pool.Name, pool.Profile).Set(pool.QuotaMaxObjects) | ||
p.StripeWidth.WithLabelValues(pool.Name, pool.Profile).Set(pool.StripeWidth) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (p *PoolInfoCollector) cephInfoCommand() []byte { | ||
cmd, err := json.Marshal(map[string]interface{}{ | ||
"prefix": "osd pool ls", | ||
"detail": "detail", | ||
"format": "json", | ||
}) | ||
if err != nil { | ||
// panic! because ideally in no world this hard-coded input | ||
// should fail. | ||
panic(err) | ||
} | ||
return cmd | ||
} | ||
|
||
// Describe fulfills the prometheus.Collector's interface and sends the descriptors | ||
// of pool's metrics to the given channel. | ||
func (p *PoolUsageCollector) Describe(ch chan<- *prometheus.Desc) { | ||
for _, metric := range p.collectorList() { | ||
metric.Describe(ch) | ||
} | ||
} | ||
|
||
// Collect extracts the current values of all the metrics and sends them to the | ||
// prometheus channel. | ||
func (p *PoolUsageCollector) Collect(ch chan<- prometheus.Metric) { | ||
if err := p.collect(); err != nil { | ||
log.Println("[ERROR] failed collecting pool usage metrics:", err) | ||
return | ||
} | ||
|
||
for _, metric := range p.collectorList() { | ||
metric.Collect(ch) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright 2016 DigitalOcean | ||
// | ||
// Licensed 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 collectors | ||
|
||
import ( | ||
"io/ioutil" | ||
"log" | ||
"net/http" | ||
"net/http/httptest" | ||
"regexp" | ||
"testing" | ||
|
||
"github.com/prometheus/client_golang/prometheus" | ||
) | ||
|
||
func TestPoolUsageCollector(t *testing.T) { | ||
log.SetOutput(ioutil.Discard) | ||
|
||
for _, tt := range []struct { | ||
input string | ||
reMatch, reUnmatch []*regexp.Regexp | ||
}{ | ||
{ | ||
input: ` | ||
[ | ||
{"pool_name": "rbd", "size": 3, "min_size": 2, "pg_num": 8192, "pg_placement_num": 8192, "quota_max_bytes": 1024, "quota_max_bytes": 2048, "erasure_code_profile": "ec-4-2", "stripe_width": 4096} | ||
]`, | ||
reMatch: []*regexp.Regexp{ | ||
regexp.MustCompile(`pool_size{cluster="ceph",pool="rbd",profile="ec-4-2"} 3`), | ||
regexp.MustCompile(`pool_min_size{cluster="ceph",pool="rbd",profile="ec-4-2"} 2`), | ||
regexp.MustCompile(`pool_pg_num{cluster="ceph",pool="rbd",profile="ec-4-2"} 8192`), | ||
regexp.MustCompile(`pool_pgp_num{cluster="ceph",pool="rbd",profile="ec-4-2"} 8192`), | ||
regexp.MustCompile(`pool_quota_max_bytes{cluster="ceph",pool="rbd",profile="ec-4-2"} 1024`), | ||
regexp.MustCompile(`pool_quota_max_objects{cluster="ceph",pool="rbd",profile="ec-4-2"} 2048`), | ||
regexp.MustCompile(`pool_stripe_width{cluster="ceph",pool="rbd",profile="ec-4-2"} 4096`), | ||
}, | ||
reUnmatch: []*regexp.Regexp{}, | ||
}, | ||
} { | ||
func() { | ||
collector := NewPoolInfoCollector(NewNoopConn(tt.input), "ceph") | ||
if err := prometheus.Register(collector); err != nil { | ||
t.Fatalf("collector failed to register: %s", err) | ||
} | ||
defer prometheus.Unregister(collector) | ||
|
||
server := httptest.NewServer(prometheus.Handler()) | ||
defer server.Close() | ||
|
||
resp, err := http.Get(server.URL) | ||
if err != nil { | ||
t.Fatalf("unexpected failed response from prometheus: %s", err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
buf, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
t.Fatalf("failed reading server response: %s", err) | ||
} | ||
|
||
for _, re := range tt.reMatch { | ||
if !re.Match(buf) { | ||
t.Errorf("failed matching: %q", re) | ||
} | ||
} | ||
|
||
for _, re := range tt.reUnmatch { | ||
if re.Match(buf) { | ||
t.Errorf("should not have matched: %q", re) | ||
} | ||
} | ||
}() | ||
} | ||
} |