Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cherry-pick the changes from the fork branch #28

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
de4cec9
feat: Type conversion for custom port
cobolbaby Jul 25, 2021
e7957b8
feat: Rename the up metric #27
cobolbaby Jul 25, 2021
55122a8
fix: remove TODO
cobolbaby Jul 25, 2021
45dfe95
fix: support the db larger than 1T
cobolbaby Jul 25, 2021
2a743b4
feat: support the metric related to db replication
cobolbaby Jul 25, 2021
885b7ce
chore: optimize the container build
cobolbaby Jul 25, 2021
f4f6b0f
chore: bump node.js 14.17.3
cobolbaby Jul 25, 2021
14b01ec
feat: redirect root path by default
cobolbaby Jul 25, 2021
e555b89
fix: client.Guage typo error
cobolbaby Jul 26, 2021
ff7efc0
fix: Invalid object name 'distribution.dbo.MSdistribution_agents'.
cobolbaby Jul 26, 2021
6ac8550
docs: fix the container build instruction
cobolbaby Jul 26, 2021
9e97232
fix: DB_NAME() maybe return null #29
cobolbaby Jul 26, 2021
acc1f30
0.5.1
cobolbaby Jul 26, 2021
9fdc27d
chore: npm script
cobolbaby Jul 26, 2021
5c50f4a
feat: add metrics mssql_connections_per_client && mssql_transactions
cobolbaby Jul 26, 2021
4aeefcf
fix: Dockerfile maintainer Label
cobolbaby Jul 27, 2021
38c32db
optimize: rename the metrics mssql_client_connections && mssql_trans…
cobolbaby Jul 27, 2021
c4a1a20
feat: recommend mssql_client_connections instead of mssql_connections
cobolbaby Jul 27, 2021
948426c
fix: mssql_transactions_total divided by database
cobolbaby Jul 28, 2021
d6d2281
feat: add buffer io metric; unified the connection metrics
cobolbaby Jul 28, 2021
6cc0e12
feat: add metrics mssql_lazy_write_total and mssql_page_checkpoint_t…
cobolbaby Jul 29, 2021
957e129
fix: exporter crashes with ConnectionError: Connection lost - read EC…
cobolbaby Aug 4, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
*.iml
/node_modules/
/.idea/
node_modules
.idea
.vscode
.env
package-lock.json
9 changes: 6 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FROM node:8.4.0-alpine
MAINTAINER Pierre Awaragi (pierre@awaragi.com)
FROM node:14.17.3-alpine
LABEL maintainer="Pierre Awaragi (pierre@awaragi.com)"

# Create a directory where our app will be placed
RUN mkdir -p /usr/src/app
Expand All @@ -8,11 +8,14 @@ RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Copy dependency definitions
COPY package.json *.js /usr/src/app/
COPY package.json ./

# Install dependecies
RUN npm install --production

# Copy application
COPY *.js ./

# Expose the port the app runs in
EXPOSE 4000

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ for example:

### building and pushing image to dockerhub

`npm run push`
`npm run docker:build`

### Launch a mock mssql server

Expand Down
24 changes: 17 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,29 @@ const metrics = require('./metrics').metrics;
let config = {
connect: {
server: process.env["SERVER"],
userName: process.env["USERNAME"],
password: process.env["PASSWORD"],
authentication: {
type: 'default',
options: {
userName: process.env["USERNAME"], // update me
password: process.env["PASSWORD"], // update me
}
},
options: {
port: process.env["PORT"] || 1433,
port: parseInt(process.env["PORT"]) || 1433,
encrypt: true,
rowCollectionOnRequestCompletion: true
}
},
port: process.env["EXPOSE"] || 4000
port: parseInt(process.env["EXPOSE"]) || 4000
};

if (!config.connect.server) {
throw new Error("Missing SERVER information")
}
if (!config.connect.userName) {
if (!config.connect.authentication.options.userName) {
throw new Error("Missing USERNAME information")
}
if (!config.connect.password) {
if (!config.connect.authentication.options.password) {
throw new Error("Missing PASSWORD information")
}

Expand All @@ -52,6 +57,7 @@ async function connect() {
connection.on('end', () => {
debug("Connection to database ended");
});
connection.connect();
});

}
Expand Down Expand Up @@ -93,6 +99,10 @@ async function collect(connection) {
}
}

app.get('/', (req, res) => {
res.redirect('/metrics');
})

app.get('/metrics', async (req, res) => {
res.contentType(client.register.contentType);

Expand All @@ -110,7 +120,7 @@ app.get('/metrics', async (req, res) => {
});

const server = app.listen(config.port, function () {
debug(`Prometheus-MSSQL Exporter listening on local port ${config.port} monitoring ${config.connect.userName}@${config.connect.server}:${config.connect.options.port}`);
debug(`Prometheus-MSSQL Exporter listening on local port ${config.port} monitoring ${config.connect.authentication.options.userName}@${config.connect.server}:${config.connect.options.port}`);
});

process.on('SIGINT', function () {
Expand Down
121 changes: 85 additions & 36 deletions metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const debug = require("debug")("metrics");
const client = require('prom-client');

// UP metric
const up = new client.Gauge({name: 'up', help: "UP Status"});
const up = new client.Gauge({name: 'mssql_up', help: "UP Status"});

// Query based metrics
// -------------------
Expand All @@ -24,19 +24,20 @@ const mssql_instance_local_time = {

const mssql_connections = {
metrics: {
mssql_connections: new client.Gauge({name: 'mssql_connections', help: 'Number of active connections', labelNames: ['database', 'state',]})
mssql_connections: new client.Gauge({name: 'mssql_connections', help: 'Number of active client connections', labelNames: ['client', 'database']})
},
query: `SELECT DB_NAME(sP.dbid)
, COUNT(sP.spid)
FROM sys.sysprocesses sP
GROUP BY DB_NAME(sP.dbid)`,
query: `SELECT host_name, DB_NAME(database_id), COUNT(*)
FROM sys.dm_exec_sessions
WHERE is_user_process=1
GROUP BY host_name, database_id`,
collect: function (rows, metrics) {
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const database = row[0].value;
const mssql_connections = row[1].value;
debug("Fetch number of connections for database", database, mssql_connections);
metrics.mssql_connections.set({database: database, state: 'current'}, mssql_connections);
const client = row[0].value;
const database = row[1].value;
const mssql_connections = row[2].value;
debug("Fetch number of connections for client", client, database, mssql_connections);
metrics.mssql_connections.set({client, database}, mssql_connections);
}
}
};
Expand All @@ -47,7 +48,7 @@ const mssql_deadlocks = {
},
query: `SELECT cntr_value
FROM sys.dm_os_performance_counters
where counter_name = 'Number of Deadlocks/sec' AND instance_name = '_Total'`,
WHERE counter_name = 'Number of Deadlocks/sec' AND instance_name = '_Total'`,
collect: function (rows, metrics) {
const mssql_deadlocks = rows[0][0].value;
debug("Fetch number of deadlocks/sec", mssql_deadlocks);
Expand All @@ -61,7 +62,7 @@ const mssql_user_errors = {
},
query: `SELECT cntr_value
FROM sys.dm_os_performance_counters
where counter_name = 'Errors/sec' AND instance_name = 'User Errors'`,
WHERE counter_name = 'Errors/sec' AND instance_name = 'User Errors'`,
collect: function (rows, metrics) {
const mssql_user_errors = rows[0][0].value;
debug("Fetch number of user errors/sec", mssql_user_errors);
Expand All @@ -75,7 +76,7 @@ const mssql_kill_connection_errors = {
},
query: `SELECT cntr_value
FROM sys.dm_os_performance_counters
where counter_name = 'Errors/sec' AND instance_name = 'Kill Connection Errors'`,
WHERE counter_name = 'Errors/sec' AND instance_name = 'Kill Connection Errors'`,
collect: function (rows, metrics) {
const mssql_kill_connection_errors = rows[0][0].value;
debug("Fetch number of kill connection errors/sec", mssql_kill_connection_errors);
Expand All @@ -94,7 +95,7 @@ const mssql_database_state = {
const database = row[0].value;
const mssql_database_state = row[1].value;
debug("Fetch state for database", database);
metrics.mssql_database_state.set({database: database}, mssql_database_state);
metrics.mssql_database_state.set({database}, mssql_database_state);
}
}
};
Expand All @@ -104,15 +105,15 @@ const mssql_log_growths = {
mssql_log_growths: new client.Gauge({name: 'mssql_log_growths', help: 'Total number of times the transaction log for the database has been expanded last restart', labelNames: ['database']}),
},
query: `SELECT rtrim(instance_name),cntr_value
FROM sys.dm_os_performance_counters where counter_name = 'Log Growths'
and instance_name <> '_Total'`,
FROM sys.dm_os_performance_counters
WHERE counter_name = 'Log Growths' AND instance_name <> '_Total'`,
collect: function (rows, metrics) {
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const database = row[0].value;
const mssql_log_growths = row[1].value;
debug("Fetch number log growths for database", database);
metrics.mssql_log_growths.set({database: database}, mssql_log_growths);
metrics.mssql_log_growths.set({database}, mssql_log_growths);
}
}
};
Expand All @@ -121,7 +122,7 @@ const mssql_database_filesize = {
metrics: {
mssql_database_filesize: new client.Gauge({name: 'mssql_database_filesize', help: 'Physical sizes of files used by database in KB, their names and types (0=rows, 1=log, 2=filestream,3=n/a 4=fulltext(before v2008 of MSSQL))', labelNames: ['database','logicalname','type','filename']}),
},
query: `SELECT DB_NAME(database_id) AS database_name, Name AS logical_name, type, physical_name, (size * 8) size_kb FROM sys.master_files`,
query: `SELECT DB_NAME(database_id) AS database_name, name AS logical_name, type, physical_name, (size * CAST(8 AS BIGINT)) size_kb FROM sys.master_files`,
collect: function (rows, metrics) {
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
Expand All @@ -131,21 +132,48 @@ const mssql_database_filesize = {
const filename = row[3].value
const mssql_database_filesize = row[4].value;
debug("Fetch size of files for database ", database);
metrics.mssql_database_filesize.set({database: database, logicalname: logicalname, type: type, filename: filename}, mssql_database_filesize);
metrics.mssql_database_filesize.set({database, logicalname, type, filename}, mssql_database_filesize);
}
}
};

const mssql_page_life_expectancy = {
const mssql_buffer_manager = {
metrics: {
mssql_page_life_expectancy: new client.Gauge({name: 'mssql_page_life_expectancy', help: 'Indicates the minimum number of seconds a page will stay in the buffer pool on this node without references. The traditional advice from Microsoft used to be that the PLE should remain above 300 seconds'})
mssql_page_read_total: new client.Gauge({name: 'mssql_page_read_total', help: 'Page reads/sec'}),
mssql_page_write_total: new client.Gauge({name: 'mssql_page_write_total', help: 'Page writes/sec'}),
mssql_page_life_expectancy: new client.Gauge({name: 'mssql_page_life_expectancy', help: 'Indicates the minimum number of seconds a page will stay in the buffer pool on this node without references. The traditional advice from Microsoft used to be that the PLE should remain above 300 seconds'}),
mssql_lazy_write_total: new client.Gauge({name: 'mssql_lazy_write_total', help: 'Lazy writes/sec'}),
mssql_page_checkpoint_total: new client.Gauge({name: 'mssql_page_checkpoint_total', help: 'Checkpoint pages/sec'}),
},
query: `SELECT TOP 1 cntr_value
FROM sys.dm_os_performance_counters with (nolock)where counter_name='Page life expectancy'`,
query: `
SELECT * FROM
(
SELECT rtrim(counter_name) as counter_name, cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name in ('Page reads/sec', 'Page writes/sec', 'Page life expectancy', 'Lazy writes/sec', 'Checkpoint pages/sec')
AND object_name = 'SQLServer:Buffer Manager'
) d
PIVOT
(
MAX(cntr_value)
FOR counter_name IN ([Page reads/sec], [Page writes/sec], [Page life expectancy], [Lazy writes/sec], [Checkpoint pages/sec])
) piv
`,
collect: function (rows, metrics) {
const mssql_page_life_expectancy = rows[0][0].value;
debug("Fetch page life expectancy", mssql_page_life_expectancy);
metrics.mssql_page_life_expectancy.set(mssql_page_life_expectancy)
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const page_read = row[0].value;
const page_write = row[1].value;
const page_life_expectancy = row[2].value;
const lazy_write_total = row[3].value;
const page_checkpoint_total = row[4].value;
debug("Fetch the disk io", page_read, page_write, page_life_expectancy);
metrics.mssql_page_read_total.set(page_read);
metrics.mssql_page_write_total.set(page_write);
metrics.mssql_page_life_expectancy.set(page_life_expectancy);
metrics.mssql_page_checkpoint_total.set(page_checkpoint_total);
metrics.mssql_lazy_write_total.set(lazy_write_total);
}
}
};

Expand All @@ -164,7 +192,7 @@ cast(DB_Name(a.database_id) as varchar) as name,
FROM
sys.dm_io_virtual_file_stats(null, null) a
INNER JOIN sys.master_files b ON a.database_id = b.database_id and a.file_id = b.file_id
group by a.database_id`,
GROUP BY a.database_id`,
collect: function (rows, metrics) {
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
Expand All @@ -175,11 +203,11 @@ group by a.database_id`,
const queued_read = row[4].value;
const queued_write = row[5].value;
debug("Fetch number of stalls for database", database);
metrics.mssql_io_stall_total.set({database: database}, stall);
metrics.mssql_io_stall.set({database: database, type: "read"}, read);
metrics.mssql_io_stall.set({database: database, type: "write"}, write);
metrics.mssql_io_stall.set({database: database, type: "queued_read"}, queued_read);
metrics.mssql_io_stall.set({database: database, type: "queued_write"}, queued_write);
metrics.mssql_io_stall_total.set({database}, stall);
metrics.mssql_io_stall.set({database, type: "read"}, read);
metrics.mssql_io_stall.set({database, type: "write"}, write);
metrics.mssql_io_stall.set({database, type: "queued_read"}, queued_read);
metrics.mssql_io_stall.set({database, type: "queued_write"}, queued_write);
}
}
};
Expand All @@ -189,7 +217,8 @@ const mssql_batch_requests = {
mssql_batch_requests: new client.Gauge({name: 'mssql_batch_requests', help: 'Number of Transact-SQL command batches received per second. This statistic is affected by all constraints (such as I/O, number of users, cachesize, complexity of requests, and so on). High batch requests mean good throughput'})
},
query: `SELECT TOP 1 cntr_value
FROM sys.dm_os_performance_counters where counter_name = 'Batch Requests/sec'`,
FROM sys.dm_os_performance_counters
WHERE counter_name = 'Batch Requests/sec'`,
collect: function (rows, metrics) {
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
Expand All @@ -200,13 +229,31 @@ FROM sys.dm_os_performance_counters where counter_name = 'Batch Requests/sec'`,
}
};

const mssql_transactions = {
metrics: {
mssql_transactions: new client.Gauge({name: 'mssql_transactions_total', help: 'TPS.', labelNames: ['database']})
},
query: `SELECT rtrim(instance_name), cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name = 'Transactions/sec' AND instance_name <> '_Total'`,
collect: function (rows, metrics) {
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const database = row[0].value;
const transactions = row[1].value;
debug("Fetch number of transactions per second", database, transactions);
metrics.mssql_transactions.set({database}, transactions);
}
}
};

const mssql_os_process_memory = {
metrics: {
mssql_page_fault_count: new client.Gauge({name: 'mssql_page_fault_count', help: 'Number of page faults since last restart'}),
mssql_memory_utilization_percentage: new client.Gauge({name: 'mssql_memory_utilization_percentage', help: 'Percentage of memory utilization'}),
},
query: `SELECT page_fault_count, memory_utilization_percentage
from sys.dm_os_process_memory`,
FROM sys.dm_os_process_memory`,
collect: function (rows, metrics) {
const page_fault_count = rows[0][0].value;
const memory_utilization_percentage = rows[0][1].value;
Expand All @@ -224,7 +271,7 @@ const mssql_os_sys_memory = {
mssql_available_page_file_kb: new client.Gauge({name: 'mssql_available_page_file_kb', help: 'Available page file in KB'}),
},
query: `SELECT total_physical_memory_kb, available_physical_memory_kb, total_page_file_kb, available_page_file_kb
from sys.dm_os_sys_memory`,
FROM sys.dm_os_sys_memory`,
collect: function (rows, metrics) {
const mssql_total_physical_memory_kb = rows[0][0].value;
const mssql_available_physical_memory_kb = rows[0][1].value;
Expand All @@ -238,6 +285,7 @@ from sys.dm_os_sys_memory`,
}
};


const metrics = [
mssql_instance_local_time,
mssql_connections,
Expand All @@ -247,9 +295,10 @@ const metrics = [
mssql_database_state,
mssql_log_growths,
mssql_database_filesize,
mssql_page_life_expectancy,
mssql_buffer_manager,
mssql_io_stall,
mssql_batch_requests,
mssql_transactions,
mssql_os_process_memory,
mssql_os_sys_memory
];
Expand Down
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "prometheus-mssql-exporter",
"version": "0.5.0",
"version": "0.5.1",
"main": "index.js",
"private": false,
"keywords": [
Expand All @@ -23,17 +23,16 @@
"url": "https://github.com/awaragi/prometheus-mssql-exporter/issues"
},
"scripts": {
"build": "docker build . -t awaragi/prometheus-mssql-exporter"
"docker:build": "docker build --rm -t awaragi/prometheus-mssql-exporter:v${npm_package_version} .",
"docker:push": "docker push awaragi/prometheus-mssql-exporter:v${npm_package_version}"
},
"dependencies": {
"debug": "^2.6.8",
"express": "4.15.2",
"prom-client": "9.1.1",
"tedious": "2.0.0"
"tedious": "^11.4.0"
},
"devDependencies": {
"nodemon": "^1.11.0"
}
}