Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UUID to Prometheus metrics if requested #1240

Merged
merged 16 commits into from
Oct 12, 2022
Prev Previous commit
Next Next commit
Add state provider
  • Loading branch information
AlekSi committed Oct 10, 2022
commit 4ab5f84a69ecaa24ff59aa8119d51748164603fb
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ node_modules/
# test coverage results, generated files like commit.txt, etc.
*.txt

# runtime state
state.json

# for now
testdata/fuzz/
records/
Expand Down
29 changes: 24 additions & 5 deletions cmd/ferretdb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"

Expand All @@ -32,6 +33,7 @@ import (
"github.com/FerretDB/FerretDB/internal/handlers/registry"
"github.com/FerretDB/FerretDB/internal/util/debug"
"github.com/FerretDB/FerretDB/internal/util/logging"
"github.com/FerretDB/FerretDB/internal/util/state"
"github.com/FerretDB/FerretDB/internal/util/version"
)

Expand All @@ -42,9 +44,9 @@ var cli struct {
ProxyAddr string `default:"127.0.0.1:37017" help:"Proxy address."`
DebugAddr string `default:"127.0.0.1:8088" help:"Debug address."`
LogLevel string `default:"${default_log_level}" help:"${help_log_level}"`
Mode string `default:"${default_mode}" help:"${help_mode}" enum:"${enum_mode}"`

Handler string `default:"pg" help:"${help_handler}"`
StateDir string `default:"." help:"Process state directory."`
Mode string `default:"${default_mode}" help:"${help_mode}" enum:"${enum_mode}"`
Handler string `default:"pg" help:"${help_handler}"`

PostgresURL string `default:"postgres://postgres@127.0.0.1:5432/ferretdb" help:"PostgreSQL URL for 'pg' handler."`

Expand All @@ -54,8 +56,8 @@ var cli struct {
Version bool `default:"false" help:"Print version to stdout and exit."`

Test struct {
ConnTimeout time.Duration `default:"0" help:"For testing: client connection timeout."`
RecordsDir string `default:"" help:"For testing: directory for record files."`
ConnTimeout time.Duration `default:"0" help:"Testing flag: client connection timeout."`
RecordsDir string `default:"" help:"Testing flag: directory for record files."`
} `embed:"" prefix:"test-"`
}

Expand Down Expand Up @@ -116,13 +118,30 @@ func run() {
return
}

stateFile, err := filepath.Abs(filepath.Join(cli.StateDir, "state.json"))
if err != nil {
logger.Fatal("Failed to get path for state file", zap.Error(err))
}
logger.Debug("State file", zap.String("filename", stateFile))

p, err := state.NewProvider(stateFile)
if err != nil {
logger.Fatal("Failed to create state provider", zap.Error(err))
}

s, err := p.Get()
if err != nil {
logger.Fatal("Failed to get state", zap.Error(err))
}

startFields := []zap.Field{
zap.String("version", info.Version),
zap.String("commit", info.Commit),
zap.String("branch", info.Branch),
zap.Bool("dirty", info.Dirty),
zap.Bool("debug", info.Debug),
zap.Reflect("buildEnvironment", info.BuildEnvironment.Map()),
zap.String("uuid", s.UUID),
}
logger.Info("Starting FerretDB "+info.Version+"...", startFields...)

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.19
require (
github.com/AlekSi/pointer v1.2.0
github.com/alecthomas/kong v0.6.1
github.com/google/uuid v1.3.0
github.com/jackc/pgconn v1.13.0
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
github.com/jackc/pgx/v4 v4.17.2
Expand Down Expand Up @@ -33,7 +34,6 @@ require (
github.com/go-openapi/swag v0.21.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.2 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
Expand Down
2 changes: 1 addition & 1 deletion internal/clientconn/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func (c *conn) run(ctx context.Context) (err error) {

// if test record path is set, split netConn reader to write to file and bufr
if c.testRecordsDir != "" {
if err := os.MkdirAll(c.testRecordsDir, 0o755); err != nil {
if err := os.MkdirAll(c.testRecordsDir, 0o777); err != nil {
return err
}

Expand Down
103 changes: 103 additions & 0 deletions internal/util/state/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2021 FerretDB Inc.
//
// 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 state stores FerretDB process state.
package state

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"

"github.com/google/uuid"

"github.com/FerretDB/FerretDB/internal/util/must"
)

// State represents FerretDB process state.
type State struct {
UUID string `json:"uuid"`
}

// Provider provides access to FerretDB process state.
type Provider struct {
filename string

rw sync.RWMutex
s State
}

// NewProvider creates a new Provider that stores state in the given file.
func NewProvider(filename string) (*Provider, error) {
p := &Provider{
filename: filename,
}

if _, err := p.Get(); err != nil {
return nil, err
}

return p, nil
}

// Get returns the current process state.
//
// It is okay to call this function often.
// The caller should not cache result; Provider does everything needed itself.
func (p *Provider) Get() (*State, error) {
// return different copies to each caller
p.rw.RLock()
s := p.s
p.rw.RUnlock()

if s.UUID != "" {
return &s, nil
}

// happy path: try to read UUID, ignore all errors

b, _ := os.ReadFile(p.filename)
_ = json.Unmarshal(b, &s)

if s.UUID != "" {
// store a copy
p.rw.Lock()
p.s = s
p.rw.Unlock()

return &s, nil
}

// regenerate and save UUID if any error occurred

s.UUID = must.NotFail(uuid.NewRandom()).String()
b = must.NotFail(json.Marshal(s))

if err := os.MkdirAll(filepath.Dir(p.filename), 0o777); err != nil && !os.IsExist(err) {
return nil, fmt.Errorf("failed to create state directory: %w", err)
}

if err := os.WriteFile(p.filename, b, 0o666); err != nil {
return nil, fmt.Errorf("failed to write state file: %w", err)
}

// store a copy
p.rw.Lock()
p.s = s
p.rw.Unlock()

return &s, nil
}
61 changes: 61 additions & 0 deletions internal/util/state/state_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2021 FerretDB Inc.
//
// 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 state

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestState(t *testing.T) {
filename := filepath.Join(t.TempDir(), "state.json")
p1, err := NewProvider(filename)
require.NoError(t, err)

s1, err := p1.Get()
require.NoError(t, err)
assert.NotEmpty(t, s1.UUID)

s2, err := p1.Get()
require.NoError(t, err)
assert.Equal(t, s1, s2)
assert.NotSame(t, s1, s2)

s3, err := p1.Get()
require.NoError(t, err)
assert.Equal(t, s1, s3)
assert.NotSame(t, s1, s3)

p2, err := NewProvider(filename)
require.NoError(t, err)

s4, err := p2.Get()
require.NoError(t, err)
assert.Equal(t, s1, s4)
assert.NotSame(t, s1, s4)

require.NoError(t, os.Remove(filename))

p3, err := NewProvider(filename)
require.NoError(t, err)

s5, err := p3.Get()
require.NoError(t, err)
assert.NotEqual(t, s1, s5)
}
6 changes: 3 additions & 3 deletions internal/util/version/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func main() {
defer wg.Done()

b := runGit("describe", "--tags", "--dirty")
must.NoError(os.WriteFile(filepath.Join("gen", "version.txt"), b, 0o644))
must.NoError(os.WriteFile(filepath.Join("gen", "version.txt"), b, 0o666))
}()

// git rev-parse HEAD > gen/commit.txt
Expand All @@ -59,7 +59,7 @@ func main() {
defer wg.Done()

b := runGit("rev-parse", "HEAD")
must.NoError(os.WriteFile(filepath.Join("gen", "commit.txt"), b, 0o644))
must.NoError(os.WriteFile(filepath.Join("gen", "commit.txt"), b, 0o666))
}()

// git branch --show-current > gen/branch.txt
Expand All @@ -68,7 +68,7 @@ func main() {
defer wg.Done()

b := runGit("branch", "--show-current")
must.NoError(os.WriteFile(filepath.Join("gen", "branch.txt"), b, 0o644))
must.NoError(os.WriteFile(filepath.Join("gen", "branch.txt"), b, 0o666))
}()

wg.Wait()
Expand Down