master
go 238 lines 6.67 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package mssql
4
5 import (
6 "context"
7 "database/sql"
8 _ "embed"
9 "errors"
10 "strings"
11 "sync"
12 "time"
13
14 "github.com/netdata/netdata/go/plugins/pkg/confopt"
15 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
16 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/cloudauth"
17
18 _ "github.com/microsoft/go-mssqldb"
19 _ "github.com/microsoft/go-mssqldb/azuread"
20 )
21
22 //go:embed "config_schema.json"
23 var configSchema string
24
25 func init() {
26 collectorapi.Register("mssql", collectorapi.Creator{
27 JobConfigSchema: configSchema,
28 Defaults: collectorapi.Defaults{
29 UpdateEvery: 10,
30 },
31 Create: func() collectorapi.CollectorV1 { return New() },
32 Config: func() any { return &Config{} },
33 Methods: mssqlMethods,
34 MethodHandler: mssqlFunctionHandler,
35 })
36 }
37
38 func New() *Collector {
39 return &Collector{
40 Config: Config{
41 DSN: "sqlserver://localhost:1433",
42 Timeout: confopt.Duration(time.Second * 5),
43 Functions: FunctionsConfig{
44 TopQueries: TopQueriesConfig{
45 Limit: 500,
46 TimeWindowDays: 7,
47 },
48 },
49 },
50
51 charts: instanceCharts.Copy(),
52
53 seenDatabases: make(map[string]bool),
54 seenDatabasesWithLog: make(map[string]bool),
55 seenWaitTypes: make(map[string]bool),
56 seenLockTypes: make(map[string]bool),
57 seenLockStatsTypes: make(map[string]bool),
58 seenJobs: make(map[string]bool),
59 seenReplications: make(map[string]bool),
60
61 seenAGs: make(map[string]bool),
62 seenAGReplicas: make(map[string]bool),
63 seenAGDatabaseReplicas: make(map[string]bool),
64 seenAGClusterMembers: make(map[string]bool),
65 seenAGPageRepairDBs: make(map[string]bool),
66 }
67 }
68
69 type Config struct {
70 Vnode string `yaml:"vnode,omitempty" json:"vnode"`
71 UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
72 DSN string `yaml:"dsn" json:"dsn"`
73 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
74 CloudAuth cloudauth.Config `yaml:"cloud_auth" json:"cloud_auth"`
75 Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
76 }
77
78 type FunctionsConfig struct {
79 TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
80 DeadlockInfo DeadlockInfoConfig `yaml:"deadlock_info,omitempty" json:"deadlock_info"`
81 ErrorInfo ErrorInfoConfig `yaml:"error_info,omitempty" json:"error_info"`
82 }
83
84 type TopQueriesConfig struct {
85 Disabled bool `yaml:"disabled" json:"disabled"`
86 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
87 Limit int `yaml:"limit,omitempty" json:"limit"`
88 TimeWindowDays int `yaml:"time_window_days,omitempty" json:"time_window_days"`
89 }
90
91 type DeadlockInfoConfig struct {
92 Disabled bool `yaml:"disabled" json:"disabled"`
93 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
94 UseRingBuffer bool `yaml:"use_ring_buffer" json:"use_ring_buffer"`
95 }
96
97 type ErrorInfoConfig struct {
98 Disabled bool `yaml:"disabled" json:"disabled"`
99 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
100 SessionName string `yaml:"session_name,omitempty" json:"session_name,omitempty"`
101 UseRingBuffer bool `yaml:"use_ring_buffer" json:"use_ring_buffer"`
102 }
103
104 func (c Config) topQueriesTimeout() time.Duration {
105 if c.Functions.TopQueries.Timeout == 0 {
106 return c.Timeout.Duration()
107 }
108 return c.Functions.TopQueries.Timeout.Duration()
109 }
110
111 func (c Config) topQueriesLimit() int {
112 if c.Functions.TopQueries.Limit <= 0 {
113 return 500
114 }
115 return c.Functions.TopQueries.Limit
116 }
117
118 func (c Config) topQueriesTimeWindowDays() int {
119 if c.Functions.TopQueries.TimeWindowDays == -1 {
120 return 0 // -1 means "query all history"
121 }
122 if c.Functions.TopQueries.TimeWindowDays <= 0 {
123 return 7
124 }
125 return c.Functions.TopQueries.TimeWindowDays
126 }
127
128 func (c Config) deadlockInfoTimeout() time.Duration {
129 if c.Functions.DeadlockInfo.Timeout == 0 {
130 return c.Timeout.Duration()
131 }
132 return c.Functions.DeadlockInfo.Timeout.Duration()
133 }
134
135 func (c Config) errorInfoTimeout() time.Duration {
136 if c.Functions.ErrorInfo.Timeout == 0 {
137 return c.Timeout.Duration()
138 }
139 return c.Functions.ErrorInfo.Timeout.Duration()
140 }
141
142 func (c Config) errorInfoSessionName() string {
143 if strings.TrimSpace(c.Functions.ErrorInfo.SessionName) == "" {
144 return "netdata_errors"
145 }
146 return c.Functions.ErrorInfo.SessionName
147 }
148
149 type Collector struct {
150 collectorapi.Base
151 Config `yaml:",inline" json:""`
152
153 charts *collectorapi.Charts
154
155 db *sql.DB
156
157 version string
158
159 seenDatabases map[string]bool
160 seenDatabasesWithLog map[string]bool
161 seenWaitTypes map[string]bool
162 seenLockTypes map[string]bool
163 seenLockStatsTypes map[string]bool
164 seenJobs map[string]bool
165 seenReplications map[string]bool
166
167 hadrEnabled bool // true if Always On AG is enabled on this instance
168 hadrChecked bool // true after the HADR check has been performed
169 majorVersion int // parsed from version string (11=2012, 12=2014, 13=2016, etc.)
170
171 seenAGs map[string]bool // key: ag_name
172 seenAGReplicas map[string]bool // key: ag_name + "_" + replica_server_name
173 seenAGDatabaseReplicas map[string]bool // key: ag_name + "_" + replica_server_name + "_" + db_name
174 seenAGClusterMembers map[string]bool // key: member_name
175 seenAGPageRepairDBs map[string]bool // key: database_name
176 agClusterChartAdded bool // true after cluster quorum chart has been added
177
178 // Query Store column cache (per-instance to handle different SQL Server versions)
179 queryStoreColsMu sync.RWMutex // protects queryStoreCols for concurrent access
180 queryStoreCols map[string]bool
181
182 funcRouter *funcRouter
183 }
184
185 func (c *Collector) Configuration() any {
186 return c.Config
187 }
188
189 func (c *Collector) Init(context.Context) error {
190 if c.DSN == "" {
191 return errors.New("config: dsn not set")
192 }
193 if err := c.CloudAuth.Validate(); err != nil {
194 return err
195 }
196 c.Debugf("using DSN [%s]", c.DSN)
197
198 c.funcRouter = newFuncRouter(c)
199
200 return nil
201 }
202
203 func (c *Collector) Check(context.Context) error {
204 mx, err := c.collect()
205 if err != nil {
206 return err
207 }
208 if len(mx) == 0 {
209 return errors.New("no metrics collected")
210 }
211 return nil
212 }
213
214 func (c *Collector) Charts() *collectorapi.Charts {
215 return c.charts
216 }
217
218 func (c *Collector) Collect(context.Context) map[string]int64 {
219 mx, err := c.collect()
220 if err != nil {
221 c.Error(err)
222 return nil
223 }
224 return mx
225 }
226
227 func (c *Collector) Cleanup(ctx context.Context) {
228 if c.funcRouter != nil {
229 c.funcRouter.Cleanup(ctx)
230 }
231 if c.db == nil {
232 return
233 }
234 if err := c.db.Close(); err != nil {
235 c.Errorf("cleanup: error closing database connection: %v", err)
236 }
237 c.db = nil
238 }