@cryptotaxi247 / netdata-1 / commits / f6100564c

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