SQL Server Jobs (windows.plugin) (#21182)
thiagoftsm committed
Oct 24, 2025 at 11:23 UTC
f6100564c059ec772ff053a30e668658cee62de1
4 files changed
+168
-3
src/collectors/windows.plugin/metadata.yaml
+28
-2
@@ -2036,7 +2036,18 @@ modules:
2036
GO
2037
```
2038
2039
- 3. **Configure SQL Server Network Settings**
2039
+ 3. **Enable Query Job status**
2040
+
2041
+ Connect to your database and grant `SELECT` permission to `netdata_user`:
2042
+
2043
+ ```tsql
2044
+ USE msdb;
2045
+ GO
2046
+ GRANT SELECT ON SCHEMA::[dbo] TO netdata_user;
2047
+ GO
2048
+ ```
2049
+
2050
+ 4. **Configure SQL Server Network Settings**
2051
2052
Enable SQL Server to accept TCP connections:
2053
@@ -2049,7 +2060,7 @@ modules:
2060
- Enter a port number in the `TCP Port` field (default is `1433`)
2061
- Select `SQL Server Services` and restart your SQL Server instance
2062
2052
- 4. **Configure SQL Server Authentication (Optional)**
2063
+ 5. **Configure SQL Server Authentication (Optional)**
2064
2065
If you're using SQL Server authentication (rather than Windows authentication):
2066
@@ -2297,6 +2308,21 @@ modules:
2308
chart_type: line
2309
dimensions:
2310
- name: blocked
2311
+ - name: MSSQL Jobs
2312
+ description: "These metrics refer to the Microsoft SQL Servers jobs on host."
2313
+ labels:
2314
+ - name: mssql_instance
2315
+ description: The instance name.
2316
+ - name: job_name
2317
+ description: The job name.
2318
+ metrics:
2319
+ - name: mssql.instance_jobs_status
2320
+ description: Jobs running
2321
+ unit: jobs
2322
+ chart_type: line
2323
+ dimensions:
2324
+ - name: enabled
2325
+ - name: disabled
2326
- name: MSSQL Resource Locks
2327
description: "Monitors SQL Server resource locks by type. SQL Server uses locks to manage concurrent access to database resources during transactions, preventing conflicts when multiple users access the same data simultaneously. This metric tracks locks on different [resource types](https://learn.microsoft.com/en-us/sql/relational-databases/performance-monitor/sql-server-locks-object?view=sql-server-ver17) like rows, pages, tables, and databases."
2328
labels:
src/collectors/windows.plugin/perflib-mssql-queries.h
+2
@@ -27,6 +27,8 @@
27
#define NETDATA_DB_STATES (6)
28
#define NETDATA_QUERY_DATABASE_STATUS "SELECT name, state, is_read_only FROM sys.databases;"
29
30
+#define NETDATA_QUERY_JOBS_STATUS "SELECT name, enabled FROM msdb.dbo.sysjobs;"
31
+
32
// https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-wait-stats-transact-sql?view=sql-server-ver16
33
#define NETDATA_QUERY_CHECK_WAITS \
34
"SELECT \
src/collectors/windows.plugin/perflib-mssql.c
+137
-1
@@ -41,6 +41,7 @@ struct netdata_mssql_conn {
41
SQLHSTMT dbWaitsSTMT;
42
SQLHSTMT dbLocksSTMT;
43
SQLHSTMT dbSQLState;
44
+ SQLHSTMT dbSQLJobs;
45
46
BOOL is_connected;
47
};
@@ -58,6 +59,7 @@ enum netdata_mssql_metrics {
59
NETDATA_MSSQL_LOCKS,
60
NETDATA_MSSQL_WAITS,
61
NETDATA_MSSQL_BUFFER_MANAGEMENT,
62
+ NETDATA_MSSQL_JOBS,
63
64
NETDATA_MSSQL_METRICS_END
65
};
@@ -120,6 +122,7 @@ struct mssql_instance {
122
DICTIONARY *locks_instances;
123
124
DICTIONARY *databases;
125
+ DICTIONARY *sysjobs;
126
127
RRDSET *st_conn_memory;
128
RRDDIM *rd_conn_memory;
@@ -163,6 +166,16 @@ struct mssql_lock_instance {
166
RRDDIM *rd_deadLocks;
167
};
168
169
+struct mssql_db_jobs {
170
+ bool enabled;
171
+
172
+ RRDSET *st_status;
173
+ RRDDIM *rd_status_enabled;
174
+ RRDDIM *rd_status_disabled;
175
+
176
+ COUNTER_DATA MSSQLJOBState;
177
+};
178
+
179
struct mssql_db_instance {
180
struct mssql_instance *parent;
181
@@ -826,6 +839,57 @@ enddbstate:
839
netdata_MSSQL_release_results(mi->conn->dbSQLState);
840
}
841
842
+void metdata_mssql_fill_job_status(struct mssql_instance *mi)
843
+{
844
+ char job[SQLSERVER_MAX_NAME_LENGTH + 1];
845
+ BYTE state = 0;
846
+ SQLLEN col_job_len = 0;
847
+ SQLLEN col_state_len = 0;
848
+
849
+ static int next_try = NETDATA_MSSQL_NEXT_TRY - 1;
850
+
851
+ if (++next_try != NETDATA_MSSQL_NEXT_TRY)
852
+ return;
853
+
854
+ next_try = 0;
855
+
856
+ SQLRETURN ret;
857
+
858
+ ret = SQLExecDirect(mi->conn->dbSQLJobs, (SQLCHAR *)NETDATA_QUERY_JOBS_STATUS, SQL_NTS);
859
+ if (ret != SQL_SUCCESS) {
860
+ netdata_MSSQL_error(SQL_HANDLE_STMT, mi->conn->dbSQLJobs, NETDATA_MSSQL_ODBC_QUERY, mi->instanceID);
861
+ goto enddbjobs;
862
+ }
863
+
864
+ ret = SQLBindCol(mi->conn->dbSQLJobs, 1, SQL_C_CHAR, job, sizeof(job), &col_job_len);
865
+ if (ret != SQL_SUCCESS) {
866
+ netdata_MSSQL_error(SQL_HANDLE_STMT, mi->conn->dbSQLJobs, NETDATA_MSSQL_ODBC_PREPARE, mi->instanceID);
867
+ goto enddbjobs;
868
+ }
869
+
870
+ ret = SQLBindCol(mi->conn->dbSQLJobs, 2, SQL_C_TINYINT, &state, sizeof(state), &col_state_len);
871
+ if (ret != SQL_SUCCESS) {
872
+ netdata_MSSQL_error(SQL_HANDLE_STMT, mi->conn->dbSQLJobs, NETDATA_MSSQL_ODBC_PREPARE, mi->instanceID);
873
+ goto enddbjobs;
874
+ }
875
+
876
+ do {
877
+ ret = SQLFetch(mi->conn->dbSQLJobs);
878
+ if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
879
+ goto enddbjobs;
880
+ }
881
+
882
+ struct mssql_db_jobs *mdj = dictionary_set(mi->sysjobs, job, NULL, sizeof(*mdj));
883
+ if (!mdj)
884
+ continue;
885
+
886
+ mdj->MSSQLJOBState.current.Data = (ULONGLONG)state;
887
+ } while (true);
888
+
889
+enddbjobs:
890
+ netdata_MSSQL_release_results(mi->conn->dbSQLJobs);
891
+}
892
+
893
void metdata_mssql_fill_dictionary_from_db(struct mssql_instance *mi)
894
{
895
char dbname[SQLSERVER_MAX_NAME_LENGTH + 1];
@@ -976,6 +1040,12 @@ static bool netdata_MSSQL_initialize_connection(struct netdata_mssql_conn *nmc)
1040
retConn = FALSE;
1041
goto endMSSQLInitializationConnection;
1042
}
1043
+
1044
+ ret = SQLAllocHandle(SQL_HANDLE_STMT, nmc->netdataSQLHDBc, &nmc->dbSQLJobs);
1045
+ if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
1046
+ retConn = FALSE;
1047
+ goto endMSSQLInitializationConnection;
1048
+ }
1049
}
1050
1051
endMSSQLInitializationConnection:
@@ -1016,6 +1086,9 @@ static void initialize_mssql_objects(struct mssql_instance *mi, const char *inst
1086
strncpyz(&name[length], "Buffer Manager", sizeof(name) - length);
1087
mi->objectName[NETDATA_MSSQL_BUFFER_MANAGEMENT] = strdupz(name);
1088
1089
+ strncpyz(&name[length], "SystemJobs", sizeof(name) - length);
1090
+ mi->objectName[NETDATA_MSSQL_JOBS] = strdupz(name);
1091
+
1092
strncpyz(&name[length], "Memory Manager", sizeof(name) - length);
1093
mi->objectName[NETDATA_MSSQL_MEMORY] = strdupz(name);
1094
@@ -1085,6 +1158,11 @@ void dict_mssql_insert_databases_cb(const DICTIONARY_ITEM *item __maybe_unused,
1158
mdi->collecting_data = true;
1159
}
1160
1161
+void dict_mssql_insert_jobs_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused)
1162
+{
1163
+ UNUSED(value);
1164
+}
1165
+
1166
// Options
1167
void netdata_mount_mssql_connection_string(struct netdata_mssql_conn *dbInput)
1168
{
@@ -1231,6 +1309,12 @@ void dict_mssql_insert_cb(const DICTIONARY_ITEM *item __maybe_unused, void *valu
1309
dictionary_register_insert_callback(mi->databases, dict_mssql_insert_databases_cb, NULL);
1310
}
1311
1312
+ if (!mi->sysjobs) {
1313
+ mi->sysjobs = dictionary_create_advanced(
1314
+ DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct mssql_db_jobs));
1315
+ dictionary_register_insert_callback(mi->sysjobs, dict_mssql_insert_jobs_cb, NULL);
1316
+ }
1317
+
1318
if (!mi->waits) {
1319
mi->waits = dictionary_create_advanced(
1320
DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct mssql_db_waits));
@@ -1320,6 +1404,7 @@ int dict_mssql_query_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value,
1404
} else {
1405
metdata_mssql_fill_dictionary_from_db(mi);
1406
metdata_mssql_fill_mssql_status(mi);
1407
+ metdata_mssql_fill_job_status(mi);
1408
dictionary_sorted_walkthrough_read(mi->databases, dict_mssql_databases_run_queries, NULL);
1409
}
1410
@@ -2206,6 +2291,56 @@ static void do_mssql_bufferman_stats_sql(PERF_DATA_BLOCK *pDataBlock, struct mss
2291
dictionary_sorted_walkthrough_read(mi->databases, dict_mssql_buffman_stats_charts_cb, mi);
2292
}
2293
2294
+static void netdata_mssql_jobs_status(struct mssql_db_jobs *mdj, struct mssql_instance *mi, const char *job)
2295
+{
2296
+ if (!mdj->st_status) {
2297
+ char id[RRD_ID_LENGTH_MAX + 1];
2298
+ snprintfz(id, RRD_ID_LENGTH_MAX, "job_%s_instance_%s_status", job, mi->instanceID);
2299
+ netdata_fix_chart_name(id);
2300
+ mdj->st_status = rrdset_create_localhost(
2301
+ "mssql",
2302
+ id,
2303
+ NULL,
2304
+ "jobs",
2305
+ "mssql.instance_jobs_status",
2306
+ "Jobs running",
2307
+ "status",
2308
+ PLUGIN_WINDOWS_NAME,
2309
+ "PerflibMSSQL",
2310
+ PRIO_MSSQL_DATABASE_JOBS_STATUS,
2311
+ mi->update_every,
2312
+ RRDSET_TYPE_LINE);
2313
+
2314
+ mdj->rd_status_enabled =
2315
+ rrddim_add(mdj->st_status, "enabled", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2316
+
2317
+ mdj->rd_status_disabled =
2318
+ rrddim_add(mdj->st_status, "disabled", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2319
+
2320
+ rrdlabels_add(mdj->st_status->rrdlabels, "mssql_instance", mi->instanceID, RRDLABEL_SRC_AUTO);
2321
+ rrdlabels_add(mdj->st_status->rrdlabels, "job_name", job, RRDLABEL_SRC_AUTO);
2322
+ }
2323
+
2324
+ rrddim_set_by_pointer(
2325
+ mdj->st_status, mdj->rd_status_enabled, (collected_number)(mdj->MSSQLJOBState.current.Data == 1));
2326
+ rrddim_set_by_pointer(
2327
+ mdj->st_status, mdj->rd_status_disabled, (collected_number)(mdj->MSSQLJOBState.current.Data == 0));
2328
+ rrdset_done(mdj->st_status);
2329
+}
2330
+
2331
+int dict_mssql_sysjobs_chart_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
2332
+ const char *job = dictionary_acquired_item_name((DICTIONARY_ITEM *)item);
2333
+ struct mssql_db_jobs *mdj = value;
2334
+ struct mssql_instance *mi = data;
2335
+
2336
+ netdata_mssql_jobs_status(mdj, mi, job);
2337
+}
2338
+
2339
+static void do_mssql_job_status_sql(PERF_DATA_BLOCK *pDataBlock, struct mssql_instance *mi, int update_every)
2340
+{
2341
+ dictionary_sorted_walkthrough_read(mi->sysjobs, dict_mssql_sysjobs_chart_cb, mi);
2342
+}
2343
+
2344
static void mssql_database_backup_restore_chart(struct mssql_db_instance *mdi, const char *db, int update_every)
2345
{
2346
char id[RRD_ID_LENGTH_MAX + 1];
@@ -2910,12 +3045,13 @@ int dict_mssql_charts_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value
3045
do_mssql_locks,
3046
do_mssql_waits,
3047
do_mssql_bufferman_stats_sql,
3048
+ do_mssql_job_status_sql,
3049
3050
NULL};
3051
3052
DWORD i;
3053
PERF_DATA_BLOCK *pDataBlock;
2918
- static bool collect_perflib[NETDATA_MSSQL_METRICS_END] = {true, true, true, true, true, true, true, true, true};
3054
+ static bool collect_perflib[NETDATA_MSSQL_METRICS_END] = {true, true, true, true, true, true, true, true, true, true};
3055
for (i = 0; i < NETDATA_MSSQL_ACCESS_METHODS; i++) {
3056
if (!collect_perflib[i])
3057
continue;
src/collectors/windows.plugin/windows_plugin.h
+1
@@ -110,6 +110,7 @@ enum PERFLIB_PRIO {
110
111
PRIO_MSSQL_DATABASE_READONLY,
112
PRIO_MSSQL_DATABASE_STATE,
113
+ PRIO_MSSQL_DATABASE_JOBS_STATUS,
114
115
PRIO_MSSQL_DATABASE_DATA_FILE_SIZE,
116