Skip to content

Commit 1b85fbb

Browse files
committed
optionally collect /_scheduler/jobs (CouchDB 2.x only)
1 parent db0bd6a commit 1b85fbb

File tree

8 files changed

+186
-6
lines changed

8 files changed

+186
-6
lines changed

couchdb-exporter.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type exporterConfigType struct {
2323
couchdbInsecure bool
2424
databases string
2525
databaseViews bool
26+
schedulerJobs bool
2627
}
2728

2829
var exporterConfig exporterConfigType
@@ -37,6 +38,7 @@ func init() {
3738
flag.BoolVar(&exporterConfig.couchdbInsecure, "couchdb.insecure", true, "Ignore server certificate if using https")
3839
flag.StringVar(&exporterConfig.databases, "databases", "", fmt.Sprintf("Comma separated list of database names, or '%s'", lib.AllDbs))
3940
flag.BoolVar(&exporterConfig.databaseViews, "databases.views", true, "Collect view details of every observed database")
41+
flag.BoolVar(&exporterConfig.schedulerJobs, "scheduler.jobs", false, "Collect active replication jobs (CouchDB 2.x+ only)")
4042

4143
flag.BoolVar(&glogadapt.Logging.ToStderr, "logtostderr", false, "log to standard error instead of files")
4244
flag.BoolVar(&glogadapt.Logging.AlsoToStderr, "alsologtostderr", false, "log to standard error as well as files")
@@ -62,8 +64,9 @@ func main() {
6264
Username: *&exporterConfig.couchdbUsername,
6365
Password: *&exporterConfig.couchdbPassword},
6466
lib.CollectorConfig{
65-
Databases: databases,
66-
CollectViews: *&exporterConfig.databaseViews,
67+
Databases: databases,
68+
CollectViews: *&exporterConfig.databaseViews,
69+
CollectSchedulerJobs: *&exporterConfig.schedulerJobs,
6770
},
6871
*&exporterConfig.couchdbInsecure)
6972
prometheus.MustRegister(exporter)

couchdb-exporter_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ func couchdbResponse(t *testing.T, versionSuffix string) Handler {
7676
} else if r.URL.Path == "/_active_tasks" {
7777
file := readFile(t, fmt.Sprintf("./testdata/active-tasks-%s.json", versionSuffix))
7878
w.Write([]byte(file))
79+
} else if r.URL.Path == "/_scheduler/jobs" {
80+
file := readFile(t, fmt.Sprintf("./testdata/scheduler-jobs-%s.json", versionSuffix))
81+
w.Write([]byte(file))
7982
} else if r.URL.Path == "/example" {
8083
file := readFile(t, fmt.Sprintf("./testdata/example-meta-%s.json", versionSuffix))
8184
w.Write([]byte(file))
@@ -113,8 +116,9 @@ func performCouchdbStatsTest(t *testing.T, couchdbVersion string, expectedMetric
113116
server := httptest.NewServer(handler)
114117

115118
e := lib.NewExporter(server.URL, basicAuth, lib.CollectorConfig{
116-
Databases: []string{"example", "another-example"},
117-
CollectViews: true,
119+
Databases: []string{"example", "another-example"},
120+
CollectViews: true,
121+
CollectSchedulerJobs: true,
118122
}, true)
119123

120124
ch := make(chan prometheus.Metric)
@@ -163,7 +167,7 @@ func TestCouchdbStatsV1(t *testing.T) {
163167
}
164168

165169
func TestCouchdbStatsV2(t *testing.T) {
166-
performCouchdbStatsTest(t, "v2", 103, 4712, 58570, 14)
170+
performCouchdbStatsTest(t, "v2", 106, 4712, 58570, 15)
167171
}
168172

169173
func TestCouchdbStatsV1Integration(t *testing.T) {

lib/collector-v2.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,18 @@ func (e *Exporter) collectV2(stats Stats, exposedHttpStatusCodes []string, colle
8080
}
8181
}
8282

83+
if collectorConfig.CollectSchedulerJobs {
84+
for _, job := range stats.SchedulerJobsResponse.Jobs {
85+
e.schedulerJobs.WithLabelValues(
86+
job.Node,
87+
job.ID,
88+
job.Database,
89+
job.DocID,
90+
job.Source,
91+
job.Target).Set(float64(len(job.History)))
92+
}
93+
}
94+
8395
activeTasksByNode := make(map[string]ActiveTaskTypes)
8496
for _, task := range stats.ActiveTasksResponse {
8597
if task.Type == "replication" {

lib/collector.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type CollectorConfig struct {
2121
Databases []string
2222
ObservedDatabases []string
2323
CollectViews bool
24+
CollectSchedulerJobs bool
2425
}
2526

2627
type ActiveTaskTypes struct {
@@ -74,6 +75,8 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
7475

7576
e.viewStaleness.Describe(ch)
7677

78+
e.schedulerJobs.Describe(ch)
79+
7780
e.requestCount.Describe(ch)
7881
}
7982

@@ -114,6 +117,8 @@ func (e *Exporter) resetAllMetrics() {
114117
e.activeTasksReplicationLastUpdate,
115118

116119
e.viewStaleness,
120+
121+
e.schedulerJobs,
117122
}
118123
e.resetMetrics(metrics)
119124
}
@@ -205,6 +210,8 @@ func (e *Exporter) collect(ch chan<- prometheus.Metric) error {
205210

206211
e.viewStaleness.Collect(ch)
207212

213+
e.schedulerJobs.Collect(ch)
214+
208215
e.requestCount.Collect(ch)
209216

210217
return nil

lib/couchdb-client.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ func (c *CouchdbClient) getStats(config CollectorConfig) (Stats, error) {
161161
return Stats{}, err
162162
}
163163
}
164+
schedulerJobs := SchedulerJobsResponse{}
165+
if config.CollectSchedulerJobs {
166+
schedulerJobs, err = c.getSchedulerJobs()
167+
}
164168
activeTasks, err := c.getActiveTasks()
165169
if err != nil {
166170
return Stats{}, err
@@ -174,6 +178,7 @@ func (c *CouchdbClient) getStats(config CollectorConfig) (Stats, error) {
174178
DatabasesTotal: len(databasesList),
175179
DatabaseStatsByDbName: databaseStats,
176180
ActiveTasksResponse: activeTasks,
181+
SchedulerJobsResponse: schedulerJobs,
177182
ApiVersion: "2"}, nil
178183
} else {
179184
urisByNode := map[string]string{
@@ -280,6 +285,27 @@ func (c *CouchdbClient) enhanceWithViewUpdateSeq(dbStatsByDbName map[string]Data
280285
return nil
281286
}
282287

288+
// CouchDB 2.x+ only
289+
func (c *CouchdbClient) getSchedulerJobs() (SchedulerJobsResponse, error) {
290+
data, err := c.Request("GET", fmt.Sprintf("%s/_scheduler/jobs", c.BaseUri), nil)
291+
if err != nil {
292+
return SchedulerJobsResponse{}, fmt.Errorf("error reading scheduler jobs: %v", err)
293+
}
294+
295+
var schedulerJobs SchedulerJobsResponse
296+
err = json.Unmarshal(data, &schedulerJobs)
297+
if err != nil {
298+
return SchedulerJobsResponse{}, fmt.Errorf("error unmarshalling scheduler jobs: %v", err)
299+
}
300+
//for _, job := range schedulerJobs.Jobs {
301+
// replDoc, err := c.Request("GET", fmt.Sprintf("%s/%s/%s", c.BaseUri, job.Database, job.DocID), nil)
302+
// if err != nil {
303+
// return SchedulerJobsResponse{}, fmt.Errorf("error reading replication doc '%s/%s': %v", job.Database, job.DocID, err)
304+
// }
305+
//}
306+
return schedulerJobs, nil
307+
}
308+
283309
func (c *CouchdbClient) getActiveTasks() (ActiveTasksResponse, error) {
284310
data, err := c.Request("GET", fmt.Sprintf("%s/_active_tasks", c.BaseUri), nil)
285311
if err != nil {

lib/couchdb-stats.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package lib
22

3-
import "encoding/json"
3+
import (
4+
"encoding/json"
5+
"time"
6+
)
47

58
type Counter struct {
69
// v1.x api
@@ -152,10 +155,32 @@ type ActiveTask struct {
152155

153156
type ActiveTasksResponse []ActiveTask
154157

158+
type SchedulerJobsResponse struct {
159+
TotalRows int `json:"total_rows"`
160+
Offset int `json:"offset"`
161+
Jobs []struct {
162+
Database string `json:"database"`
163+
ID string `json:"id"`
164+
Pid string `json:"pid"`
165+
Source string `json:"source"`
166+
Target string `json:"target"`
167+
User string `json:"user"`
168+
DocID string `json:"doc_id"`
169+
History []struct {
170+
Timestamp time.Time `json:"timestamp"`
171+
Type string `json:"type"`
172+
} `json:"history"`
173+
Node string `json:"node"`
174+
StartTime time.Time `json:"start_time"`
175+
} `json:"jobs"`
176+
}
177+
155178
type Stats struct {
156179
StatsByNodeName map[string]StatsResponse
157180
DatabasesTotal int
158181
DatabaseStatsByDbName DatabaseStatsByDbName
159182
ActiveTasksResponse ActiveTasksResponse
183+
// SchedulerJobsResponse: CouchDB 2.x+ only
184+
SchedulerJobsResponse SchedulerJobsResponse
160185
ApiVersion string
161186
}

lib/exporter.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type Exporter struct {
5353
activeTasksReplicationLastUpdate *prometheus.GaugeVec
5454

5555
viewStaleness *prometheus.GaugeVec
56+
57+
schedulerJobs *prometheus.GaugeVec
5658
}
5759

5860
func NewExporter(uri string, basicAuth BasicAuth, collectorConfig CollectorConfig, insecure bool) *Exporter {
@@ -324,5 +326,14 @@ func NewExporter(uri string, basicAuth BasicAuth, collectorConfig CollectorConfi
324326
Help: "the view's staleness (the view's update_seq compared to the database's update_seq)",
325327
},
326328
[]string{"db_name", "design_doc_name", "view_name", "shard_begin", "shard_end"}),
329+
330+
schedulerJobs: prometheus.NewGaugeVec(
331+
prometheus.GaugeOpts{
332+
Namespace: namespace,
333+
Subsystem: "scheduler",
334+
Name: "jobs",
335+
Help: "scheduler jobs",
336+
},
337+
[]string{"node_name", "job_id", "db_name", "doc_id", "source", "target"}),
327338
}
328339
}

testdata/scheduler-jobs-v2.json

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
{
2+
"total_rows": 3,
3+
"offset": 0,
4+
"jobs": [
5+
{
6+
"database": "_replicator",
7+
"id": "1ebc7c8a11b299a779ffb807e089c17e+create_target",
8+
"pid": "<0.10858.6>",
9+
"source": "http://source:5984/source-db/",
10+
"target": "http://target:5984/target-db/",
11+
"user": "admin",
12+
"doc_id": "replicate-once",
13+
"history": [
14+
{
15+
"timestamp": "2019-03-10T21:11:07Z",
16+
"type": "started"
17+
},
18+
{
19+
"timestamp": "2019-03-10T21:11:07Z",
20+
"type": "added"
21+
}
22+
],
23+
"node": "[email protected]",
24+
"start_time": "2019-03-10T21:11:07Z"
25+
},
26+
{
27+
"database": "_replicator",
28+
"id": "2ae1733d6570280350d3e24c4929ebdf+continuous+create_target",
29+
"pid": null,
30+
"source": "http://source:5984/source-db/",
31+
"target": "http://target:5984/target-db/",
32+
"user": "admin",
33+
"doc_id": "replicate-continuous-error",
34+
"history": [
35+
{
36+
"timestamp": "2019-03-10T22:42:12Z",
37+
"type": "crashed",
38+
"reason": "{replication_auth_error,\n {session_request_failed,\"http://localhost:15984/_session\",\"root\",\n {conn_failed,{error,econnrefused}}}}"
39+
},
40+
{
41+
"timestamp": "2019-03-10T22:42:07Z",
42+
"type": "started"
43+
},
44+
{
45+
"timestamp": "2019-03-10T22:40:10Z",
46+
"type": "crashed",
47+
"reason": "{replication_auth_error,\n {session_request_failed,\"http://localhost:15984/_session\",\"root\",\n {conn_failed,{error,econnrefused}}}}"
48+
},
49+
{
50+
"timestamp": "2019-03-10T22:40:07Z",
51+
"type": "started"
52+
},
53+
{
54+
"timestamp": "2019-03-10T22:38:45Z",
55+
"type": "crashed",
56+
"reason": "{replication_auth_error,\n {session_request_failed,\"http://localhost:15984/_session\",\"root\",\n {conn_failed,{error,econnrefused}}}}"
57+
},
58+
{
59+
"timestamp": "2019-03-10T22:38:40Z",
60+
"type": "started"
61+
},
62+
{
63+
"timestamp": "2019-03-10T22:38:40Z",
64+
"type": "added"
65+
}
66+
],
67+
"node": "[email protected]",
68+
"start_time": "2019-03-10T22:38:40Z"
69+
},
70+
{
71+
"database": "_replicator",
72+
"id": "382782aeedd59468b52add2b0d7389a2+continuous+create_target",
73+
"pid": "<0.25596.0>",
74+
"source": "http://source:5984/source-db/",
75+
"target": "http://target:5984/target-db/",
76+
"user": "admin",
77+
"doc_id": "replicate-continuous",
78+
"history": [
79+
{
80+
"timestamp": "2019-03-10T21:07:40Z",
81+
"type": "started"
82+
},
83+
{
84+
"timestamp": "2019-03-10T21:07:40Z",
85+
"type": "added"
86+
}
87+
],
88+
"node": "[email protected]",
89+
"start_time": "2019-03-10T21:07:40Z"
90+
}
91+
]
92+
}

0 commit comments

Comments
 (0)