Skip to content

Commit 8030c40

Browse files
authored
Merge branch 'main' into PMM-7-use-image-from-env-for-encrypted
2 parents 1d16f5f + cb3db89 commit 8030c40

File tree

17 files changed

+511
-2489
lines changed

17 files changed

+511
-2489
lines changed

.github/pull_request_template.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
[PMM-XXXX](https://jira.percona.com/browse/PMM-XXXX) (optional, if ticket reported)
22

3-
- [ ] Links to other linked pull requests (optional).
3+
- [ ] Links to related pull requests (optional).
44

55
---
66

7-
- [ ] Tests passed.
8-
- [ ] Fix conflicts with target branch.
9-
- [ ] Update jira ticket description if needed.
10-
- [ ] Attach screenshots/console output to confirm new behavior to jira ticket, if applicable.
7+
Below we provide a basic checklist of things that would make it a good PR:
8+
- Make sure to sign the CLA (Contributor License Agreement).
9+
- Make sure all tests pass.
10+
- Keep current with the target branch and fix conflicts if necessary.
11+
- Update jira ticket description if necessary.
12+
- Attach screenshots and/or console output to the jira ticket to confirm new behavior, if applicable.
13+
- Leave notes to the reviewers if you need to focus their attention on something specific.
1114

1215
Once all checks pass and the code is ready for review, please add `pmm-review-exporters` team as the reviewer. That would assign people from the review team automatically. Report any issues on our [Forum](https://forums.percona.com).

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
- name: Run GoReleaser
4646
uses: goreleaser/goreleaser-action@v5
4747
with:
48-
version: latest
48+
version: v1.18.2
4949
args: release --rm-dist
5050
env:
5151
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

CHANGELOG

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
Release 0.40.0
2+
3+
- Multi-target support
4+
- Update license to Apache 2.0
5+
- Add option --collector.dbstatsfreestorage to receive freeStorage stats from dbstats
6+
- New Current Op Metrics collector
7+
- New collector to collect data from the system profile
8+
- Add support for timestamp metrics with seconds precision
9+
- Update Go version
10+
- Update dependencies
11+
112
Release 0.39.0
213

314
- Fixed issue with connection to authenticated MongoDB Arbiter

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,17 @@ export MONGODB_PASSWORD=YYY
8686
mongodb_exporter_linux_amd64/mongodb_exporter --mongodb.uri=mongodb://127.0.0.1:17001 --mongodb.collstats-colls=db1.c1,db2.c2
8787
```
8888

89+
#### Multi-target support
90+
You can run the exporter specifying multiple URIs, devided by a comma in --mongodb.uri option or MONGODB_URI environment variable in order to monitor multiple mongodb instances with the a single mongodb_exporter instance.
91+
```sh
92+
--mongodb.uri=mongodb://user:[email protected]:27017/admin,mongodb://user2:[email protected]:27018/admin
93+
```
94+
In this case you can use the **/scrape** endpoint with the **target** parameter to retreive the specified tartget's metrics. When querying the data you can use just mongodb://host:port in the targer parameter without other parameters and, of course without host credentials
95+
```sh
96+
GET /scrape?target=mongodb://127.0.0.1:27018
97+
```
98+
99+
89100
#### Enabling collstats metrics gathering
90101
`--mongodb.collstats-colls` receives a list of databases and collections to monitor using collstats.
91102
Usage example: `--mongodb.collstats-colls=database1.collection1,database2.collection2`

REFERENCE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
|--web.listen-address|Address to listen on for web interface and telemetry|--web.listen-address=":9216"|
1515
|--web.telemetry-path|Metrics expose path|--web.telemetry-path="/metrics"|
1616
|--web.config|Path to the file having Prometheus TLS config for basic auth|--web.config=STRING|
17+
|--web.timeout-offset|Offset to subtract from the timeout in seconds|--web.timeout-offset=1|
1718
|--log.level|Only log messages with the given severity or above. Valid levels: [debug, info, warn, error, fatal]|--log.level="error"|
1819
|--collector.diagnosticdata|Enable collecting metrics from getDiagnosticData|
1920
|--collector.replicasetstatus|Enable collecting metrics from replSetGetStatus|
@@ -27,4 +28,4 @@
2728
|--collector.profile-time-ts=30|Set time for scrape slow queries| This interval must be synchronized with the Prometheus scrape interval|
2829
|--collector.profile|Enable collecting metrics from profile|
2930
|--metrics.overridedescendingindex| Enable descending index name override to replace -1 with _DESC ||
30-
|--version|Show version and exit|
31+
|--version|Show version and exit|

exporter/base_collector.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ func newBaseCollector(client *mongo.Client, logger *logrus.Logger) *baseCollecto
4343
func (d *baseCollector) Describe(ctx context.Context, ch chan<- *prometheus.Desc, collect func(mCh chan<- prometheus.Metric)) {
4444
select {
4545
case <-ctx.Done():
46-
return
46+
// don't interrupt, let mongodb_up metric to be registered if on timeout we still don't have client connected
47+
if d.client != nil {
48+
return
49+
}
4750
default:
4851
}
4952

exporter/exporter.go

Lines changed: 22 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,12 @@ import (
2121
"fmt"
2222
"net/http"
2323
_ "net/http/pprof"
24-
"os"
2524
"strconv"
2625
"sync"
2726
"time"
2827

2928
"github.com/prometheus/client_golang/prometheus"
3029
"github.com/prometheus/client_golang/prometheus/promhttp"
31-
"github.com/prometheus/common/promlog"
32-
"github.com/prometheus/exporter-toolkit/web"
3330
"github.com/sirupsen/logrus"
3431
"go.mongodb.org/mongo-driver/mongo"
3532

@@ -38,12 +35,10 @@ import (
3835

3936
// Exporter holds Exporter methods and attributes.
4037
type Exporter struct {
41-
path string
4238
client *mongo.Client
4339
clientMu sync.Mutex
4440
logger *logrus.Logger
4541
opts *Opts
46-
webListenAddress string
4742
lock *sync.Mutex
4843
totalCollectionsCount int
4944
}
@@ -56,10 +51,12 @@ type Opts struct {
5651
CollStatsLimit int
5752
CompatibleMode bool
5853
DirectConnect bool
54+
ConnectTimeoutMS int
5955
DisableDefaultRegistry bool
6056
DiscoveringMode bool
6157
GlobalConnPool bool
6258
ProfileTimeTS int
59+
TimeoutOffset int
6360

6461
CollectAll bool
6562
EnableDBStats bool
@@ -76,10 +73,8 @@ type Opts struct {
7673

7774
IndexStatsCollections []string
7875
Logger *logrus.Logger
79-
Path string
80-
URI string
81-
WebListenAddress string
82-
TLSConfigPath string
76+
77+
URI string
8378
}
8479

8580
var (
@@ -103,16 +98,9 @@ func New(opts *Opts) *Exporter {
10398

10499
ctx := context.Background()
105100

106-
if opts.Path == "" {
107-
opts.Logger.Warn("Web telemetry path \"\" invalid, falling back to \"/\" instead")
108-
opts.Path = "/"
109-
}
110-
111101
exp := &Exporter{
112-
path: opts.Path,
113102
logger: opts.Logger,
114103
opts: opts,
115-
webListenAddress: opts.WebListenAddress,
116104
lock: &sync.Mutex{},
117105
totalCollectionsCount: -1, // Not calculated yet. waiting the db connection.
118106
}
@@ -257,7 +245,7 @@ func (e *Exporter) getClient(ctx context.Context) (*mongo.Client, error) {
257245
return e.client, nil
258246
}
259247

260-
client, err := connect(context.Background(), e.opts.URI, e.opts.DirectConnect)
248+
client, err := connect(context.Background(), e.opts)
261249
if err != nil {
262250
return nil, err
263251
}
@@ -267,7 +255,7 @@ func (e *Exporter) getClient(ctx context.Context) (*mongo.Client, error) {
267255
}
268256

269257
// !e.opts.GlobalConnPool: create new client for every scrape.
270-
client, err := connect(ctx, e.opts.URI, e.opts.DirectConnect)
258+
client, err := connect(ctx, e.opts)
271259
if err != nil {
272260
return nil, err
273261
}
@@ -284,6 +272,7 @@ func (e *Exporter) Handler() http.Handler {
284272
if err != nil {
285273
seconds = 10
286274
}
275+
seconds -= e.opts.TimeoutOffset
287276

288277
var client *mongo.Client
289278
ctx, cancel := context.WithTimeout(r.Context(), time.Duration(seconds)*time.Second)
@@ -350,14 +339,15 @@ func (e *Exporter) Handler() http.Handler {
350339
gatherers = append(gatherers, prometheus.DefaultGatherer)
351340
}
352341

342+
var ti *topologyInfo
353343
if client != nil {
354344
// Topology can change between requests, so we need to get it every time.
355-
ti := newTopologyInfo(ctx, client, e.logger)
356-
357-
registry := e.makeRegistry(ctx, client, ti, requestOpts)
358-
gatherers = append(gatherers, registry)
345+
ti = newTopologyInfo(ctx, client, e.logger)
359346
}
360347

348+
registry := e.makeRegistry(ctx, client, ti, requestOpts)
349+
gatherers = append(gatherers, registry)
350+
361351
// Delegate http serving to Prometheus client library, which will call collector.Collect.
362352
h := promhttp.HandlerFor(gatherers, promhttp.HandlerOpts{
363353
ErrorHandling: promhttp.ContinueOnError,
@@ -368,41 +358,21 @@ func (e *Exporter) Handler() http.Handler {
368358
})
369359
}
370360

371-
// Run starts the exporter.
372-
func (e *Exporter) Run() {
373-
mux := http.DefaultServeMux
374-
mux.Handle(e.path, e.Handler())
375-
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
376-
w.Write([]byte(`<html>
377-
<head><title>MongoDB Exporter</title></head>
378-
<body>
379-
<h1>MongoDB Exporter</h1>
380-
<p><a href='/metrics'>Metrics</a></p>
381-
</body>
382-
</html>`))
383-
})
384-
385-
server := &http.Server{
386-
Handler: mux,
387-
}
388-
flags := &web.FlagConfig{
389-
WebListenAddresses: &[]string{e.webListenAddress},
390-
WebConfigFile: &e.opts.TLSConfigPath,
391-
}
392-
if err := web.ListenAndServe(server, flags, promlog.New(&promlog.Config{})); err != nil {
393-
e.logger.Errorf("error starting server: %v", err)
394-
os.Exit(1)
395-
}
396-
}
397-
398-
func connect(ctx context.Context, dsn string, directConnect bool) (*mongo.Client, error) {
399-
clientOpts, err := dsn_fix.ClientOptionsForDSN(dsn)
361+
func connect(ctx context.Context, opts *Opts) (*mongo.Client, error) {
362+
clientOpts, err := dsn_fix.ClientOptionsForDSN(opts.URI)
400363
if err != nil {
401364
return nil, fmt.Errorf("invalid dsn: %w", err)
402365
}
403-
clientOpts.SetDirect(directConnect)
366+
367+
clientOpts.SetDirect(opts.DirectConnect)
404368
clientOpts.SetAppName("mongodb_exporter")
405369

370+
if clientOpts.ConnectTimeout == nil {
371+
connectTimeout := time.Duration(opts.ConnectTimeoutMS) * time.Millisecond
372+
clientOpts.SetConnectTimeout(connectTimeout)
373+
clientOpts.SetServerSelectionTimeout(connectTimeout)
374+
}
375+
406376
client, err := mongo.Connect(ctx, clientOpts)
407377
if err != nil {
408378
return nil, fmt.Errorf("invalid MongoDB options: %w", err)

exporter/exporter_test.go

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ import (
1919
"context"
2020
"fmt"
2121
"io"
22+
"net"
2223
"net/http"
2324
"net/http/httptest"
25+
"strconv"
26+
"strings"
2427
"sync"
2528
"testing"
2629

30+
"github.com/prometheus/client_golang/prometheus/testutil"
2731
"github.com/sirupsen/logrus"
2832
"github.com/stretchr/testify/assert"
2933

@@ -61,8 +65,11 @@ func TestConnect(t *testing.T) {
6165

6266
t.Run("Connect without SSL", func(t *testing.T) {
6367
for name, port := range ports {
64-
dsn := fmt.Sprintf("mongodb://%s:%s/admin", hostname, port)
65-
client, err := connect(ctx, dsn, true)
68+
exporterOpts := &Opts{
69+
URI: fmt.Sprintf("mongodb://%s/admin", net.JoinHostPort(hostname, port)),
70+
DirectConnect: true,
71+
}
72+
client, err := connect(ctx, exporterOpts)
6673
assert.NoError(t, err, name)
6774
err = client.Disconnect(ctx)
6875
assert.NoError(t, err, name)
@@ -167,17 +174,17 @@ func TestMongoS(t *testing.T) {
167174
}
168175

169176
for _, test := range tests {
170-
dsn := fmt.Sprintf("mongodb://%s:%s/admin", hostname, test.port)
171-
client, err := connect(ctx, dsn, true)
172-
assert.NoError(t, err)
173-
174177
exporterOpts := &Opts{
175178
Logger: logrus.New(),
176-
URI: dsn,
179+
URI: fmt.Sprintf("mongodb://%s/admin", net.JoinHostPort(hostname, test.port)),
180+
DirectConnect: true,
177181
GlobalConnPool: false,
178182
EnableReplicasetStatus: true,
179183
}
180184

185+
client, err := connect(ctx, exporterOpts)
186+
assert.NoError(t, err)
187+
181188
e := New(exporterOpts)
182189

183190
rsgsc := newReplicationSetStatusCollector(ctx, client, e.opts.Logger,
@@ -195,17 +202,17 @@ func TestMongoS(t *testing.T) {
195202
func TestMongoUp(t *testing.T) {
196203
ctx := context.Background()
197204

198-
dsn := "mongodb://127.0.0.1:123456/admin"
199-
client, err := connect(ctx, dsn, true)
200-
assert.Error(t, err)
201-
202205
exporterOpts := &Opts{
203206
Logger: logrus.New(),
204-
URI: dsn,
207+
URI: "mongodb://127.0.0.1:123456/admin",
208+
DirectConnect: true,
205209
GlobalConnPool: false,
206210
CollectAll: true,
207211
}
208212

213+
client, err := connect(ctx, exporterOpts)
214+
assert.Error(t, err)
215+
209216
e := New(exporterOpts)
210217

211218
gc := newGeneralCollector(ctx, client, e.opts.Logger)
@@ -215,3 +222,52 @@ func TestMongoUp(t *testing.T) {
215222
res := r.Unregister(gc)
216223
assert.Equal(t, true, res)
217224
}
225+
226+
func TestMongoUpMetric(t *testing.T) {
227+
ctx := context.Background()
228+
229+
type testcase struct {
230+
URI string
231+
Want int
232+
}
233+
234+
testCases := []testcase{
235+
{URI: "mongodb://127.0.0.1:12345/admin", Want: 0},
236+
{URI: fmt.Sprintf("mongodb://127.0.0.1:%s/admin", tu.GetenvDefault("TEST_MONGODB_STANDALONE_PORT", "27017")), Want: 1},
237+
}
238+
239+
for _, tc := range testCases {
240+
exporterOpts := &Opts{
241+
Logger: logrus.New(),
242+
URI: tc.URI,
243+
ConnectTimeoutMS: 200,
244+
DirectConnect: true,
245+
GlobalConnPool: false,
246+
CollectAll: true,
247+
}
248+
249+
client, err := connect(ctx, exporterOpts)
250+
if tc.Want == 1 {
251+
assert.NoError(t, err, "Must be able to connect to %s", tc.URI)
252+
} else {
253+
assert.Error(t, err, "Must be unable to connect to %s", tc.URI)
254+
}
255+
256+
e := New(exporterOpts)
257+
gc := newGeneralCollector(ctx, client, e.opts.Logger)
258+
r := e.makeRegistry(ctx, client, new(labelsGetterMock), *e.opts)
259+
260+
expected := strings.NewReader(`
261+
# HELP mongodb_up Whether MongoDB is up.
262+
# TYPE mongodb_up gauge
263+
mongodb_up ` + strconv.Itoa(tc.Want) + "\n")
264+
filter := []string{
265+
"mongodb_up",
266+
}
267+
err = testutil.CollectAndCompare(gc, expected, filter...)
268+
assert.NoError(t, err, "mongodb_up metric should be %d", tc.Want)
269+
270+
res := r.Unregister(gc)
271+
assert.Equal(t, true, res)
272+
}
273+
}

0 commit comments

Comments
 (0)