master
go 130 lines 3.02 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package yugabytedb
4
5 import (
6 "context"
7 "database/sql"
8 "errors"
9 "fmt"
10 "sync"
11 "time"
12
13 "github.com/netdata/netdata/go/plugins/pkg/funcapi"
14 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
15 )
16
17 var errSQLDSNNotSet = errors.New("SQL DSN is not set")
18
19 // funcRouter routes method calls to appropriate function handlers.
20 // Owns shared SQL connection used by all function handlers.
21 type funcRouter struct {
22 collector *Collector // for config (Functions.DSN, logger)
23
24 handlers map[string]funcapi.MethodHandler
25
26 // Shared SQL connection
27 db *sql.DB
28 dbMu sync.Mutex
29
30 // Column detection cache for pg_stat_statements
31 pgStatStatementsColumns map[string]bool
32 pgStatStatementsColumnsMu sync.RWMutex
33 }
34
35 func newFuncRouter(c *Collector) *funcRouter {
36 r := &funcRouter{
37 collector: c,
38 handlers: make(map[string]funcapi.MethodHandler),
39 }
40 r.handlers[topQueriesMethodID] = newFuncTopQueries(r)
41 r.handlers[runningQueriesMethodID] = newFuncRunningQueries(r)
42 return r
43 }
44
45 // Compile-time interface check.
46 var _ funcapi.MethodHandler = (*funcRouter)(nil)
47
48 func (r *funcRouter) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
49 if h, ok := r.handlers[method]; ok {
50 return h.MethodParams(ctx, method)
51 }
52 return nil, fmt.Errorf("unknown method: %s", method)
53 }
54
55 func (r *funcRouter) Handle(ctx context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
56 if h, ok := r.handlers[method]; ok {
57 return h.Handle(ctx, method, params)
58 }
59 return funcapi.NotFoundResponse(method)
60 }
61
62 func (r *funcRouter) Cleanup(ctx context.Context) {
63 for _, h := range r.handlers {
64 h.Cleanup(ctx)
65 }
66 r.dbMu.Lock()
67 defer r.dbMu.Unlock()
68 if r.db != nil {
69 _ = r.db.Close()
70 r.db = nil
71 }
72 }
73
74 // ensureDB lazily initializes the SQL connection.
75 func (r *funcRouter) ensureDB(ctx context.Context) error {
76 r.dbMu.Lock()
77 defer r.dbMu.Unlock()
78
79 if r.db != nil {
80 return nil
81 }
82 if r.collector.Functions.DSN == "" {
83 return errSQLDSNNotSet
84 }
85
86 db, err := sql.Open("pgx", r.collector.Functions.DSN)
87 if err != nil {
88 return fmt.Errorf("error opening SQL connection: %w", err)
89 }
90 db.SetMaxOpenConns(1)
91 db.SetMaxIdleConns(1)
92 db.SetConnMaxLifetime(10 * time.Minute)
93
94 timeout := r.sqlTimeout()
95 pingCtx, cancel := context.WithTimeout(ctx, timeout)
96 defer cancel()
97 if err := db.PingContext(pingCtx); err != nil {
98 _ = db.Close()
99 return fmt.Errorf("error pinging SQL connection: %w", err)
100 }
101
102 r.db = db
103 return nil
104 }
105
106 func (r *funcRouter) sqlTimeout() time.Duration {
107 if r.collector.Timeout.Duration() > 0 {
108 return r.collector.Timeout.Duration()
109 }
110 return time.Second
111 }
112
113 func (r *funcRouter) topQueriesLimit() int {
114 return r.collector.topQueriesLimit()
115 }
116
117 func yugabyteMethods() []funcapi.MethodConfig {
118 return []funcapi.MethodConfig{
119 topQueriesMethodConfig(),
120 runningQueriesMethodConfig(),
121 }
122 }
123
124 func yugabyteFunctionHandler(job collectorapi.RuntimeJob) funcapi.MethodHandler {
125 c, ok := job.Collector().(*Collector)
126 if !ok {
127 return nil
128 }
129 return c.funcRouter
130 }