master
go 97 lines 1.59 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package filepersister
4
5 import (
6 "context"
7 "log/slog"
8 "os"
9 "time"
10
11 "github.com/netdata/netdata/go/plugins/logger"
12 )
13
14 type Data interface {
15 Bytes() ([]byte, error)
16 Updated() <-chan struct{}
17 }
18
19 func Save(path string, data interface{ Bytes() ([]byte, error) }) {
20 if path == "" {
21 return
22 }
23 New(path).flush(data)
24 }
25
26 func New(path string) *Persister {
27 return &Persister{
28 Logger: logger.New().With(
29 slog.String("component", "file persister"),
30 slog.String("file", path),
31 ),
32 FlushEvery: time.Minute * 1,
33 filepath: path,
34 flushCh: make(chan struct{}, 1),
35 }
36 }
37
38 type Persister struct {
39 *logger.Logger
40
41 FlushEvery time.Duration
42
43 data Data
44 filepath string
45 flushCh chan struct{}
46 }
47
48 func (p *Persister) Run(ctx context.Context, data Data) {
49 p.Info("instance is started")
50 defer func() { p.Info("instance is stopped") }()
51
52 p.data = data
53
54 tk := time.NewTicker(p.FlushEvery)
55 defer tk.Stop()
56 defer p.flush(p.data)
57
58 for {
59 select {
60 case <-ctx.Done():
61 return
62 case <-p.data.Updated():
63 p.triggerFlush()
64 case <-tk.C:
65 p.tryFlush()
66 }
67 }
68 }
69
70 func (p *Persister) triggerFlush() {
71 select {
72 case p.flushCh <- struct{}{}:
73 default:
74 // already has a pending flush
75 }
76 }
77
78 func (p *Persister) tryFlush() {
79 select {
80 case <-p.flushCh:
81 p.flush(p.data)
82 default:
83 // no pending flush
84 }
85 }
86
87 func (p *Persister) flush(data interface{ Bytes() ([]byte, error) }) {
88 bs, err := data.Bytes()
89 if err != nil {
90 p.Debugf("failed to marshal data: %v", err)
91 return
92 }
93
94 _ = os.WriteFile(p.filepath, bs, 0644)
95
96 p.Debug("file persisted successfully")
97 }