├── .gitignore ├── LICENSE ├── README.md ├── connection.js ├── elasticsearch.js ├── events.js ├── fields.js ├── kibana.png ├── package-lock.json ├── package.json ├── pad.js ├── processor.js ├── reader.js ├── sqlmon.js ├── trace.js └── wait.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Soheil Rashidi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sqlmon 2 | Collects events from SQL Server and saves them to Elasticsearch for further analysis. 3 | 4 | You can use [sqltop](https://github.com/soheilpro/sqltop) or Kibana to analyze these data. 5 | 6 | ## Installation 7 | 8 | ``` 9 | npm install -g sqlmon 10 | ``` 11 | 12 | ## Sample 13 | ``` 14 | $ sqlmon \ 15 | --ss-address sql.example.com \ 16 | --ss-user sa \ 17 | --ss-password p@ssw0rd \ 18 | --es-address elasticsearch.example.com \ 19 | --trace-directory 'c:\trace' \ 20 | --events RPCCompleted SQLBatchCompleted \ 21 | --fields TextData TextDataHash QueryHash LoginName Duration Reads Writes CPU \ 22 | --duration 60s 23 | 24 | Connecting to sql.example.com... 25 | Creating trace... 26 | Trace file path: c:\trace\20190729103344280.trc 27 | Starting trace... 28 | Collecting events for 60s until 14:13:20... (Press [c] to stop) 29 | Stopping trace... 30 | Reading trace... 31 | Saving 18120 events to elasticsearch.example.com... 32 | 100.00% 18120/18120 33 | Disconnecting... 34 | ``` 35 | 36 | ## Usage 37 | ### Trace directory 38 | sqlmon needs to write trace files to a temporary directory on the server first. Use the `---trace-directory` option to specify the location of this directory: 39 | ``` 40 | --trace-directory 'c:\trace' 41 | ``` 42 | 43 | **Note 1**: The SQL Server service account (usually NT Service\MSSQLSERVER) must have Write permission on this directory. 44 | 45 | **Note 2**: These files are not removed when the program ends and must be manually deleted. 46 | 47 | ### Duration 48 | Use the `--duration` or `-d` parameter to specify the duration that the trace must run: 49 | 50 | ``` 51 | --duration 1m 52 | ``` 53 | 54 | You can use any specifier like `30s`, `10m`, `2h`, or `1d`. The default is `60s`. 55 | 56 | ### Max trace size 57 | By default, sqlmon limits the size of trace files to 1 GB. You can change this by using the `--max-size` option (in megabytes): 58 | 59 | ``` 60 | --max-size 2048 # 2 GB 61 | ``` 62 | 63 | ### Events to capture 64 | Use the `--events` parameter to specify the events to be captured: 65 | 66 | ``` 67 | --events RPCCompleted SQLBatchCompleted 68 | ``` 69 | 70 | See [all supported events](#supported-events-and-fields). 71 | 72 | ### Fields to include 73 | Use the `--fields` parameter to specify the fields to be included: 74 | 75 | ``` 76 | --fields TextData Reads Writes CPU Duration RowCounts 77 | ``` 78 | 79 | See [all supported fields](#supported-events-and-fields). 80 | 81 | ### Index name 82 | The default Elasticsearch index name is `sql-YY.MM.DD` which can be changed using the `--index-prefix` option: 83 | 84 | ``` 85 | --index-prefix trace- 86 | ``` 87 | 88 | ### Collect only 89 | If you only want to collect events without saving them to Elasticsearch, you can use the `--collect-only` option. 90 | 91 | ### Importing trace files 92 | If you have a saved trace file (created by either sqlmon or SQL Server Profiler), you can import it into Elasticsearch using the `--import` option: 93 | 94 | ``` 95 | --import 'c:\trace\20190728042508732.trc' 96 | ``` 97 | 98 | ### Delayed start 99 | You can use the `--delay` option to delay starting the operations until a later time: 100 | 101 | ``` 102 | --delay 1h 103 | ``` 104 | 105 | ### Hooks 106 | There are a few hooks defined that let you run any program at certain stages of the execution: 107 | 108 | ``` 109 | --schedule-hook ./myscript.sh 110 | --collection-start-hook ./myscript.sh 111 | --collection-end-hook ./myscript.sh 112 | --save-start-hook ./myscript.sh 113 | --save-end-hook ./myscript.sh 114 | --error-hook ./myscript.sh 115 | --interrupt-hook ./myscript.sh 116 | ``` 117 | 118 | The following environment variables are available to use in your hooks: 119 | * SQLMON_SQLSERVER_ADDRESS 120 | * SQLMON_SQLSERVER_PORT 121 | * SQLMON_SQLSERVER_USER 122 | * SQLMON_SQLSERVER_PASSWORD 123 | * SQLMON_SQLSERVER_TIMEOUT 124 | * SQLMON_ELASTICSEARCH_ADDRESS 125 | * SQLMON_ELASTICSEARCH_PORT 126 | * SQLMON_ELASTICSEARCH_TIMEOUT 127 | * SQLMON_TRACE_DIRECTORY 128 | * SQLMON_INDEX_PREFIX 129 | * SQLMON_EVENTS 130 | * SQLMON_FIELDS 131 | * SQLMON_DURATION 132 | * SQLMON_MAX_SIZE 133 | * SQLMON_BATCH_SIZE 134 | * SQLMON_DELAY 135 | * SQLMON_COLLECT_ONLY 136 | * SQLMON_IMPORT 137 | * SQLMON_START_DATETIME 138 | * SQLMON_START_TIME 139 | * SQLMON_TRACE_FILE_PATH 140 | * SQLMON_STOP_DATETIME 141 | * SQLMON_STOP_TIME 142 | * SQLMON_TRACE_FILE_PATH 143 | * SQLMON_ERROR 144 | 145 | ## Computed fields 146 | ### TextDataHash 147 | TextDataHash is the hash of the TextData field. 148 | 149 | ### QueryHash 150 | QueryHash is created by first removing all parameters and variables from TextData and then hashing it. It is useful for grouping queries to find the top resource consuming ones. 151 | 152 | Kibana 153 | 154 | ## Supported events and fields 155 | See [SQL Server Event Class Reference](https://docs.microsoft.com/en-us/sql/relational-databases/event-classes/sql-server-event-class-reference) for detailed information about these fields and events. 156 | 157 | ### Events 158 | * AssemblyLoad 159 | * Attention 160 | * AuditAddDBUserEvent 161 | * AuditAddLoginEvent 162 | * AuditAddLogintoServerRoleEvent 163 | * AuditAddMembertoDBRoleEvent 164 | * AuditAddRoleEvent 165 | * AuditAppRoleChangePasswordEvent 166 | * AuditBackupRestoreEvent 167 | * AuditBrokerConversation 168 | * AuditBrokerLogin 169 | * AuditChangeAuditEvent 170 | * AuditChangeDatabaseOwner 171 | * AuditDatabaseManagementEvent 172 | * AuditDatabaseObjectAccessEvent 173 | * AuditDatabaseObjectGDREvent 174 | * AuditDatabaseObjectManagementEvent 175 | * AuditDatabaseObjectTakeOwnershipEvent 176 | * AuditDatabaseOperationEvent 177 | * AuditDatabasePrincipalImpersonationEvent 178 | * AuditDatabasePrincipalManagementEvent 179 | * AuditDatabaseScopeGDR 180 | * AuditDBCCEvent 181 | * AuditFulltext 182 | * AuditLogin 183 | * AuditLoginChangePasswordEvent 184 | * AuditLoginChangePropertyEvent 185 | * AuditLoginFailed 186 | * AuditLoginGDREvent 187 | * AuditLogout 188 | * AuditObjectDerivedPermissionEvent 189 | * AuditObjectGDREvent 190 | * AuditSchemaObjectAccessEvent 191 | * AuditSchemaObjectManagementEvent 192 | * AuditSchemaObjectTakeOwnershipEvent 193 | * AuditServerAlterTraceEvent 194 | * AuditServerObjectGDREvent 195 | * AuditServerObjectManagementEvent 196 | * AuditServerObjectTakeOwnershipEvent 197 | * AuditServerOperationEvent 198 | * AuditServerPrincipalImpersonationEvent 199 | * AuditServerPrincipalManagementEvent 200 | * AuditServerScopeGDREvent 201 | * AuditServerStartsandStops 202 | * AuditStatementPermissionEvent 203 | * AutoStats 204 | * BackgroundJobError 205 | * BitmapWarning 206 | * BlockedProcessReport 207 | * BrokerActivation 208 | * BrokerConnection 209 | * BrokerConversation 210 | * BrokerConversationGroup 211 | * BrokerCorruptedMessage 212 | * BrokerForwardedMessageDropped 213 | * BrokerForwardedMessageSent 214 | * BrokerMessageClassify 215 | * BrokerMessageUndeliverable 216 | * BrokerQueueDisabled 217 | * BrokerRemoteMessageAcknowledgement 218 | * BrokerTransmission 219 | * CPUthresholdexceeded 220 | * CursorClose 221 | * CursorExecute 222 | * CursorImplicitConversion 223 | * CursorOpen 224 | * CursorPrepare 225 | * CursorRecompile 226 | * CursorUnprepare 227 | * DatabaseMirroringStateChange 228 | * DatabaseSuspectDataPage 229 | * DataFileAutoGrow 230 | * DataFileAutoShrink 231 | * DeadlockGraph 232 | * DegreeofParallelism 233 | * Deprecated 234 | * DeprecationAnnouncement 235 | * DeprecationFinalSupport 236 | * DTCTransaction 237 | * ErrorLog 238 | * EventLog 239 | * Exception 240 | * ExchangeSpillEvent 241 | * ExecPreparedSQL 242 | * ExecutionWarnings 243 | * ExistingConnection 244 | * FTCrawlAborted 245 | * FTCrawlStarted 246 | * FTCrawlStopped 247 | * HashWarning 248 | * LockAcquired 249 | * LockCancel 250 | * LockDeadlock 251 | * LockDeadlockChain 252 | * LockEscalation 253 | * LockReleased 254 | * LockTimeout 255 | * LockTimeoutNonZero 256 | * LogFileAutoGrow 257 | * LogFileAutoShrink 258 | * MissingColumnStatistics 259 | * MissingJoinPredicate 260 | * MountTape 261 | * ObjectAltered 262 | * ObjectCreated 263 | * ObjectDeleted 264 | * OLEDBCallEvent 265 | * OLEDBDataReadEvent 266 | * OLEDBErrors 267 | * OLEDBProviderInformation 268 | * OLEDBQueryInterfaceEvent 269 | * Performancestatistics 270 | * PlanGuideSuccessful 271 | * PlanGuideUnsuccessful 272 | * PreConnectCompleted 273 | * PreConnectStarting 274 | * PrepareSQL 275 | * ProgressReportOnlineIndexOperation 276 | * QNdynamics 277 | * QNparametertable 278 | * QNsubscription 279 | * QNtemplate 280 | * RPCCompleted 281 | * RPCOutputParameter 282 | * RPCStarting 283 | * ScanStarted 284 | * ScanStopped 285 | * ServerMemoryChange 286 | * ShowplanAll 287 | * ShowplanAllForQueryCompile 288 | * ShowplanStatisticsProfile 289 | * ShowplanText 290 | * ShowplanTextUnencoded 291 | * ShowplanXML 292 | * ShowplanXMLForQueryCompile 293 | * ShowplanXMLStatisticsProfile 294 | * SortWarnings 295 | * SPCacheHit 296 | * SPCacheInsert 297 | * SPCacheMiss 298 | * SPCacheRemove 299 | * SPCompleted 300 | * SPRecompile 301 | * SPStarting 302 | * SPStmtCompleted 303 | * SPStmtStarting 304 | * SQLBatchCompleted 305 | * SQLBatchStarting 306 | * SQLFullTextQuery 307 | * SQLStmtCompleted 308 | * SQLStmtRecompile 309 | * SQLStmtStarting 310 | * SQLTransaction 311 | * TMBeginTrancompleted 312 | * TMBeginTranstarting 313 | * TMCommitTrancompleted 314 | * TMCommitTranstarting 315 | * TMPromoteTrancompleted 316 | * TMPromoteTranstarting 317 | * TMRollbackTrancompleted 318 | * TMRollbackTranstarting 319 | * TMSaveTrancompleted 320 | * TMSaveTranstarting 321 | * TraceFileClose 322 | * TransactionLog 323 | * UnprepareSQL 324 | * User0 325 | * User1 326 | * User2 327 | * User3 328 | * User4 329 | * User5 330 | * User6 331 | * User7 332 | * User8 333 | * User9 334 | * UserErrorMessage 335 | * XQueryStaticType 336 | 337 | ### Fields 338 | * ApplicationName 339 | * BigintData1 340 | * BigintData2 341 | * BinaryData 342 | * ClientProcessID 343 | * ColumnPermissions 344 | * CPU 345 | * DatabaseID 346 | * DatabaseName 347 | * DBUserName 348 | * Duration 349 | * EndTime 350 | * Error 351 | * EventClass 352 | * EventSequence 353 | * EventSubClass 354 | * FileName 355 | * GUID 356 | * Handle 357 | * HostName 358 | * IndexID 359 | * IntegerData 360 | * IntegerData2 361 | * IsSystem 362 | * LineNumber 363 | * LinkedServerName 364 | * LoginName 365 | * LoginSid 366 | * MethodName 367 | * Mode 368 | * NestLevel 369 | * NTDomainName 370 | * NTUserName 371 | * ObjectID 372 | * ObjectID2 373 | * ObjectName 374 | * ObjectType 375 | * Offset 376 | * OwnerID 377 | * OwnerName 378 | * ParentName 379 | * Permissions 380 | * ProviderName 381 | * [QueryHash](#computed-fields) 382 | * Reads 383 | * RequestID 384 | * RoleName 385 | * RowCounts 386 | * ServerName 387 | * SessionLoginName 388 | * Severity 389 | * SourceDatabaseID 390 | * SPID 391 | * SqlHandle 392 | * StartTime 393 | * State 394 | * Success 395 | * TargetLoginName 396 | * TargetLoginSid 397 | * TargetUserName 398 | * TextData 399 | * [TextDataHash](#computed-fields) 400 | * TransactionID 401 | * Type 402 | * Writes 403 | * XactSequence 404 | 405 | ## Version History 406 | + **1.5** 407 | + Added authetication support for Elasticsearch. 408 | + **1.4** 409 | + Added TextDataHash field. 410 | + **1.3** 411 | + Added support for hooks. 412 | + **1.2** 413 | + Added --delay option. 414 | + **1.1** 415 | + Added --collect-only option. 416 | + **1.0** 417 | + Initial release. 418 | 419 | ## Author 420 | **Soheil Rashidi** 421 | 422 | + http://soheilrashidi.com 423 | + http://twitter.com/soheilpro 424 | + http://github.com/soheilpro 425 | 426 | ## Copyright and License 427 | Copyright 2019 Soheil Rashidi. 428 | 429 | Licensed under the The MIT License (the "License"); 430 | you may not use this work except in compliance with the License. 431 | You may obtain a copy of the License in the LICENSE file, or at: 432 | 433 | http://www.opensource.org/licenses/mit-license.php 434 | 435 | Unless required by applicable law or agreed to in writing, software 436 | distributed under the License is distributed on an "AS IS" BASIS, 437 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 438 | See the License for the specific language governing permissions and 439 | limitations under the License. 440 | -------------------------------------------------------------------------------- /connection.js: -------------------------------------------------------------------------------- 1 | const sql = require('mssql'); 2 | 3 | class Connection { 4 | config; 5 | connection; 6 | 7 | constructor(config) { 8 | this.config = config; 9 | } 10 | 11 | async open() { 12 | const userDomainMatch = /^(.*)\\(.*)$/.exec(this.config.user); 13 | 14 | if (userDomainMatch) { 15 | this.config.domain = userDomainMatch[1]; 16 | this.config.user = userDomainMatch[2]; 17 | } 18 | 19 | this.connection = await sql.connect(this.config); 20 | } 21 | 22 | async execute(query) { 23 | const result = await this.connection.request().query(query); 24 | 25 | return result.recordset; 26 | } 27 | 28 | async stream(query, max, onRows, onEnd) { 29 | const request = this.connection.request(); 30 | request.stream = true; 31 | 32 | let rows = []; 33 | 34 | request.on('row', async row => { 35 | rows.push(row); 36 | 37 | if (rows.length === max) { 38 | request.pause(); 39 | await onRows(rows); 40 | rows = []; 41 | request.resume(); 42 | } 43 | }); 44 | 45 | request.on('done', async () => { 46 | if (rows.length > 0) 47 | await onRows(rows); 48 | 49 | await onEnd(); 50 | }); 51 | 52 | request.query(query); 53 | } 54 | 55 | async close() { 56 | if (!this.connection) 57 | return; 58 | 59 | await this.connection.close(); 60 | 61 | this.connection = null; 62 | } 63 | } 64 | 65 | module.exports = Connection; 66 | -------------------------------------------------------------------------------- /elasticsearch.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const padLeft = require('./pad').padLeft; 3 | 4 | class Elasticsearch { 5 | config; 6 | 7 | constructor(config) { 8 | this.config = config; 9 | } 10 | 11 | createRequest(documents) { 12 | let data = ''; 13 | 14 | for (const document of documents) { 15 | const year = document['@timestamp'].getUTCFullYear(); 16 | const month = padLeft(document['@timestamp'].getUTCMonth() + 1, 2, '0'); 17 | const day = padLeft(document['@timestamp'].getUTCDate(), 2, '0'); 18 | const index = `${this.config.indexPrefix}${year}.${month}.${day}`; 19 | 20 | data += '{ "index" : { "_index" : "' + index + '" } }\n'; 21 | data += JSON.stringify(document) + '\n'; 22 | } 23 | 24 | return Buffer.from(data, 'utf8'); 25 | } 26 | 27 | sendRequest(data) { 28 | return new Promise((resolve, reject) => { 29 | const options = { 30 | hostname: this.config.address, 31 | port: this.config.port, 32 | path: '/_bulk', 33 | method: 'POST', 34 | headers: { 35 | 'Content-Length': data.length, 36 | 'Content-Type': 'application/json', 37 | }, 38 | timeout: this.config.timeout, 39 | }; 40 | 41 | if (this.config.username) 42 | options.headers['Authorization'] = `Basic ${ Buffer.from(`${ this.config.username }:${ this.config.password }`).toString('base64') }`; 43 | 44 | const request = http.request(options, function(response) { 45 | if (response.statusCode !== 200) 46 | return reject(new Error(response.statusMessage)); 47 | 48 | resolve(); 49 | }); 50 | 51 | request.on('error', function(error) { 52 | reject(error); 53 | }); 54 | 55 | request.end(data); 56 | }); 57 | } 58 | 59 | async insertDocuments(documents) { 60 | const data = this.createRequest(documents); 61 | 62 | await this.sendRequest(data); 63 | } 64 | } 65 | 66 | module.exports = Elasticsearch; 67 | -------------------------------------------------------------------------------- /events.js: -------------------------------------------------------------------------------- 1 | const Events = { 2 | RPCCompleted: { id: 10 }, 3 | RPCStarting: { id: 11 }, 4 | SQLBatchCompleted: { id: 12 }, 5 | SQLBatchStarting: { id: 13 }, 6 | AuditLogin: { id: 14 }, 7 | AuditLogout: { id: 15 }, 8 | Attention: { id: 16 }, 9 | ExistingConnection: { id: 17 }, 10 | AuditServerStartsandStops: { id: 18 }, 11 | DTCTransaction: { id: 19 }, 12 | AuditLoginFailed: { id: 20 }, 13 | EventLog: { id: 21 }, 14 | ErrorLog: { id: 22 }, 15 | LockReleased: { id: 23 }, 16 | LockAcquired: { id: 24 }, 17 | LockDeadlock: { id: 25 }, 18 | LockCancel: { id: 26 }, 19 | LockTimeout: { id: 27 }, 20 | DegreeofParallelism: { id: 28 }, 21 | Exception: { id: 33 }, 22 | SPCacheMiss: { id: 34 }, 23 | SPCacheInsert: { id: 35 }, 24 | SPCacheRemove: { id: 36 }, 25 | SPRecompile: { id: 37 }, 26 | SPCacheHit: { id: 38 }, 27 | Deprecated: { id: 39 }, 28 | SQLStmtStarting: { id: 40 }, 29 | SQLStmtCompleted: { id: 41 }, 30 | SPStarting: { id: 42 }, 31 | SPCompleted: { id: 43 }, 32 | SPStmtStarting: { id: 44 }, 33 | SPStmtCompleted: { id: 45 }, 34 | ObjectCreated: { id: 46 }, 35 | ObjectDeleted: { id: 47 }, 36 | SQLTransaction: { id: 50 }, 37 | ScanStarted: { id: 51 }, 38 | ScanStopped: { id: 52 }, 39 | CursorOpen: { id: 53 }, 40 | TransactionLog: { id: 54 }, 41 | HashWarning: { id: 55 }, 42 | AutoStats: { id: 58 }, 43 | LockDeadlockChain: { id: 59 }, 44 | LockEscalation: { id: 60 }, 45 | OLEDBErrors: { id: 61 }, 46 | ExecutionWarnings: { id: 67 }, 47 | ShowplanTextUnencoded: { id: 68 }, 48 | SortWarnings: { id: 69 }, 49 | CursorPrepare: { id: 70 }, 50 | PrepareSQL: { id: 71 }, 51 | ExecPreparedSQL: { id: 72 }, 52 | UnprepareSQL: { id: 73 }, 53 | CursorExecute: { id: 74 }, 54 | CursorRecompile: { id: 75 }, 55 | CursorImplicitConversion: { id: 76 }, 56 | CursorUnprepare: { id: 77 }, 57 | CursorClose: { id: 78 }, 58 | MissingColumnStatistics: { id: 79 }, 59 | MissingJoinPredicate: { id: 80 }, 60 | ServerMemoryChange: { id: 81 }, 61 | User0: { id: 82 }, 62 | User1: { id: 83 }, 63 | User2: { id: 84 }, 64 | User3: { id: 85 }, 65 | User4: { id: 86 }, 66 | User5: { id: 87 }, 67 | User6: { id: 88 }, 68 | User7: { id: 89 }, 69 | User8: { id: 90 }, 70 | User9: { id: 91 }, 71 | DataFileAutoGrow: { id: 92 }, 72 | LogFileAutoGrow: { id: 93 }, 73 | DataFileAutoShrink: { id: 94 }, 74 | LogFileAutoShrink: { id: 95 }, 75 | ShowplanText: { id: 96 }, 76 | ShowplanAll: { id: 97 }, 77 | ShowplanStatisticsProfile: { id: 98 }, 78 | RPCOutputParameter: { id: 100 }, 79 | AuditDatabaseScopeGDR: { id: 102 }, 80 | AuditObjectGDREvent: { id: 103 }, 81 | AuditAddLoginEvent: { id: 104 }, 82 | AuditLoginGDREvent: { id: 105 }, 83 | AuditLoginChangePropertyEvent: { id: 106 }, 84 | AuditLoginChangePasswordEvent: { id: 107 }, 85 | AuditAddLogintoServerRoleEvent: { id: 108 }, 86 | AuditAddDBUserEvent: { id: 109 }, 87 | AuditAddMembertoDBRoleEvent: { id: 110 }, 88 | AuditAddRoleEvent: { id: 111 }, 89 | AuditAppRoleChangePasswordEvent: { id: 112 }, 90 | AuditStatementPermissionEvent: { id: 113 }, 91 | AuditSchemaObjectAccessEvent: { id: 114 }, 92 | AuditBackupRestoreEvent: { id: 115 }, 93 | AuditDBCCEvent: { id: 116 }, 94 | AuditChangeAuditEvent: { id: 117 }, 95 | AuditObjectDerivedPermissionEvent: { id: 118 }, 96 | OLEDBCallEvent: { id: 119 }, 97 | OLEDBQueryInterfaceEvent: { id: 120 }, 98 | OLEDBDataReadEvent: { id: 121 }, 99 | ShowplanXML: { id: 122 }, 100 | SQLFullTextQuery: { id: 123 }, 101 | BrokerConversation: { id: 124 }, 102 | DeprecationAnnouncement: { id: 125 }, 103 | DeprecationFinalSupport: { id: 126 }, 104 | ExchangeSpillEvent: { id: 127 }, 105 | AuditDatabaseManagementEvent: { id: 128 }, 106 | AuditDatabaseObjectManagementEvent: { id: 129 }, 107 | AuditDatabasePrincipalManagementEvent: { id: 130 }, 108 | AuditSchemaObjectManagementEvent: { id: 131 }, 109 | AuditServerPrincipalImpersonationEvent: { id: 132 }, 110 | AuditDatabasePrincipalImpersonationEvent: { id: 133 }, 111 | AuditServerObjectTakeOwnershipEvent: { id: 134 }, 112 | AuditDatabaseObjectTakeOwnershipEvent: { id: 135 }, 113 | BrokerConversationGroup: { id: 136 }, 114 | BlockedProcessReport: { id: 137 }, 115 | BrokerConnection: { id: 138 }, 116 | BrokerForwardedMessageSent: { id: 139 }, 117 | BrokerForwardedMessageDropped: { id: 140 }, 118 | BrokerMessageClassify: { id: 141 }, 119 | BrokerTransmission: { id: 142 }, 120 | BrokerQueueDisabled: { id: 143 }, 121 | ShowplanXMLStatisticsProfile: { id: 146 }, 122 | DeadlockGraph: { id: 148 }, 123 | BrokerRemoteMessageAcknowledgement: { id: 149 }, 124 | TraceFileClose: { id: 150 }, 125 | AuditChangeDatabaseOwner: { id: 152 }, 126 | AuditSchemaObjectTakeOwnershipEvent: { id: 153 }, 127 | FTCrawlStarted: { id: 155 }, 128 | FTCrawlStopped: { id: 156 }, 129 | FTCrawlAborted: { id: 157 }, 130 | AuditBrokerConversation: { id: 158 }, 131 | AuditBrokerLogin: { id: 159 }, 132 | BrokerMessageUndeliverable: { id: 160 }, 133 | BrokerCorruptedMessage: { id: 161 }, 134 | UserErrorMessage: { id: 162 }, 135 | BrokerActivation: { id: 163 }, 136 | ObjectAltered: { id: 164 }, 137 | Performancestatistics: { id: 165 }, 138 | SQLStmtRecompile: { id: 166 }, 139 | DatabaseMirroringStateChange: { id: 167 }, 140 | ShowplanXMLForQueryCompile: { id: 168 }, 141 | ShowplanAllForQueryCompile: { id: 169 }, 142 | AuditServerScopeGDREvent: { id: 170 }, 143 | AuditServerObjectGDREvent: { id: 171 }, 144 | AuditDatabaseObjectGDREvent: { id: 172 }, 145 | AuditServerOperationEvent: { id: 173 }, 146 | AuditServerAlterTraceEvent: { id: 175 }, 147 | AuditServerObjectManagementEvent: { id: 176 }, 148 | AuditServerPrincipalManagementEvent: { id: 177 }, 149 | AuditDatabaseOperationEvent: { id: 178 }, 150 | AuditDatabaseObjectAccessEvent: { id: 180 }, 151 | TMBeginTranstarting: { id: 181 }, 152 | TMBeginTrancompleted: { id: 182 }, 153 | TMPromoteTranstarting: { id: 183 }, 154 | TMPromoteTrancompleted: { id: 184 }, 155 | TMCommitTranstarting: { id: 185 }, 156 | TMCommitTrancompleted: { id: 186 }, 157 | TMRollbackTranstarting: { id: 187 }, 158 | TMRollbackTrancompleted: { id: 188 }, 159 | LockTimeoutNonZero: { id: 189 }, 160 | ProgressReportOnlineIndexOperation: { id: 190 }, 161 | TMSaveTranstarting: { id: 191 }, 162 | TMSaveTrancompleted: { id: 192 }, 163 | BackgroundJobError: { id: 193 }, 164 | OLEDBProviderInformation: { id: 194 }, 165 | MountTape: { id: 195 }, 166 | AssemblyLoad: { id: 196 }, 167 | XQueryStaticType: { id: 198 }, 168 | QNsubscription: { id: 199 }, 169 | QNparametertable: { id: 200 }, 170 | QNtemplate: { id: 201 }, 171 | QNdynamics: { id: 202 }, 172 | BitmapWarning: { id: 212 }, 173 | DatabaseSuspectDataPage: { id: 213 }, 174 | CPUthresholdexceeded: { id: 214 }, 175 | PreConnectStarting: { id: 215 }, 176 | PreConnectCompleted: { id: 216 }, 177 | PlanGuideSuccessful: { id: 217 }, 178 | PlanGuideUnsuccessful: { id: 218 }, 179 | AuditFulltext: { id: 235 }, 180 | }; 181 | 182 | module.exports = Events; 183 | -------------------------------------------------------------------------------- /fields.js: -------------------------------------------------------------------------------- 1 | const XXH = require('xxhashjs'); 2 | 3 | function dateUTC(value) { 4 | if (value === null) 5 | return null; 6 | 7 | const date = new Date(value); 8 | 9 | return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds()); 10 | } 11 | 12 | function integer(value) { 13 | if (value === null) 14 | return null; 15 | 16 | return parseInt(value, 10); 17 | } 18 | 19 | function boolean(value) { 20 | if (value === null) 21 | return null; 22 | 23 | return value === 1; 24 | } 25 | 26 | function page(value) { 27 | if (value === null) 28 | return null; 29 | 30 | return parseInt(value, 10) * 8 * 1024; 31 | } 32 | 33 | function hex(value) { 34 | if (value === null) 35 | return null; 36 | 37 | return '0x' + value.toString('hex'); 38 | } 39 | 40 | function hash(value) { 41 | return XXH.h64(value || '', 0xabcd).toString(36); 42 | } 43 | 44 | function queryHash(value) { 45 | if (value === null) 46 | return null; 47 | 48 | if (value.indexOf('exec sp_executesql') === 0) { 49 | value = value.replace(/,@\w+=.*/g, ''); // Remove everything after ',@Param1=' 50 | value = value.replace(/varchar\(\d+\)/g, 'varchar()'); // Change 'varchar(xxx)' to 'varchar()' 51 | } 52 | 53 | value = value.replace(/[^0-9A-Za-z]+/g, ''); // Remove all non-alphanumeric characters 54 | 55 | return XXH.h64(value, 0xabcd).toString(36); 56 | } 57 | 58 | const Fields = { 59 | TextData: { id: 1, name: 'TextData' }, 60 | TextDataHash: { id: 1, name: 'TextData', transform: hash, alias: 'TextDataHash' }, 61 | QueryHash: { id: 1, name: 'TextData', transform: queryHash, alias: 'QueryHash' }, 62 | BinaryData: { id: 2, name: 'BinaryData', transform: hex }, 63 | DatabaseID: { id: 3, name: 'DatabaseID' }, 64 | TransactionID: { id: 4, name: 'TransactionID', transform: integer }, 65 | LineNumber: { id: 5, name: 'LineNumber' }, 66 | NTUserName: { id: 6, name: 'NTUserName' }, 67 | NTDomainName: { id: 7, name: 'NTDomainName' }, 68 | HostName: { id: 8, name: 'HostName' }, 69 | ClientProcessID: { id: 9, name: 'ClientProcessID' }, 70 | ApplicationName: { id: 10, name: 'ApplicationName' }, 71 | LoginName: { id: 11, name: 'LoginName' }, 72 | SPID: { id: 12, name: 'SPID' }, 73 | Duration: { id: 13, name: 'Duration', transform: integer }, 74 | StartTime: { id: 14, name: 'StartTime', transform: dateUTC, alias: '@timestamp' }, 75 | EndTime: { id: 15, name: 'EndTime', transform: dateUTC }, 76 | Reads: { id: 16, name: 'Reads', transform: page }, 77 | Writes: { id: 17, name: 'Writes', transform: page }, 78 | CPU: { id: 18, name: 'CPU' }, 79 | Permissions: { id: 19, name: 'Permissions', transform: integer }, 80 | Severity: { id: 20, name: 'Severity', transform: integer }, 81 | EventSubClass: { id: 21, name: 'EventSubClass' }, 82 | ObjectID: { id: 22, name: 'ObjectID' }, 83 | Success: { id: 23, name: 'Success', transform: boolean }, 84 | IndexID: { id: 24, name: 'IndexID' }, 85 | IntegerData: { id: 25, name: 'IntegerData' }, 86 | ServerName: { id: 26, name: 'ServerName' }, 87 | EventClass: { id: 27, name: 'EventClass' }, 88 | ObjectType: { id: 28, name: 'ObjectType' }, 89 | NestLevel: { id: 29, name: 'NestLevel' }, 90 | State: { id: 30, name: 'State', transform: integer }, 91 | Error: { id: 31, name: 'Error' }, 92 | Mode: { id: 32, name: 'Mode' }, 93 | Handle: { id: 33, name: 'Handle' }, // transform? 94 | ObjectName: { id: 34, name: 'ObjectName' }, 95 | DatabaseName: { id: 35, name: 'DatabaseName' }, 96 | FileName: { id: 36, name: 'FileName' }, 97 | OwnerName: { id: 37, name: 'OwnerName' }, 98 | RoleName: { id: 38, name: 'RoleName' }, 99 | TargetUserName: { id: 39, name: 'TargetUserName' }, 100 | DBUserName: { id: 40, name: 'DBUserName' }, 101 | LoginSid: { id: 41, name: 'LoginSid', transform: hex }, 102 | TargetLoginName: { id: 42, name: 'TargetLoginName' }, 103 | TargetLoginSid: { id: 43, name: 'TargetLoginSid', transform: hex }, 104 | ColumnPermissions: { id: 44, name: 'ColumnPermissions' }, 105 | LinkedServerName: { id: 45, name: 'LinkedServerName' }, 106 | ProviderName: { id: 46, name: 'ProviderName' }, 107 | MethodName: { id: 47, name: 'MethodName' }, 108 | RowCounts: { id: 48, name: 'RowCounts', transform: integer }, 109 | RequestID: { id: 49, name: 'RequestID' }, 110 | XactSequence: { id: 50, name: 'XactSequence', transform: integer }, 111 | EventSequence: { id: 51, name: 'EventSequence', transform: integer }, 112 | BigintData1: { id: 52, name: 'BigintData1', transform: integer }, 113 | BigintData2: { id: 53, name: 'BigintData2', transform: integer }, 114 | GUID: { id: 54, name: 'GUID' }, // transform? 115 | IntegerData2: { id: 55, name: 'IntegerData2' }, 116 | ObjectID2: { id: 56, name: 'ObjectID2', transform: integer }, 117 | Type: { id: 57, name: 'Type' }, 118 | OwnerID: { id: 58, name: 'OwnerID' }, 119 | ParentName: { id: 59, name: 'ParentName' }, 120 | IsSystem: { id: 60, name: 'IsSystem', transform: boolean }, 121 | Offset: { id: 61, name: 'Offset' }, 122 | SourceDatabaseID: { id: 62, name: 'SourceDatabaseID' }, 123 | SqlHandle: { id: 63, name: 'SqlHandle', transform: hex }, 124 | SessionLoginName: { id: 64, name: 'SessionLoginName' }, 125 | } 126 | 127 | module.exports = Fields; 128 | -------------------------------------------------------------------------------- /kibana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soheilpro/sqlmon/0bd69c8a542c9e45af4d845cfced6a89ebb7cf2f/kibana.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sqlmon", 3 | "version": "1.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "8.10.51", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.51.tgz", 10 | "integrity": "sha512-cArrlJp3Yv6IyFT/DYe+rlO8o3SIHraALbBW/+CcCYW/a9QucpLI+n2p4sRxAvl2O35TiecpX2heSZtJjvEO+Q==" 11 | }, 12 | "adal-node": { 13 | "version": "0.1.28", 14 | "resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.28.tgz", 15 | "integrity": "sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU=", 16 | "requires": { 17 | "@types/node": "^8.0.47", 18 | "async": ">=0.6.0", 19 | "date-utils": "*", 20 | "jws": "3.x.x", 21 | "request": ">= 2.52.0", 22 | "underscore": ">= 1.3.1", 23 | "uuid": "^3.1.0", 24 | "xmldom": ">= 0.1.x", 25 | "xpath.js": "~1.1.0" 26 | } 27 | }, 28 | "ajv": { 29 | "version": "6.10.2", 30 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", 31 | "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", 32 | "requires": { 33 | "fast-deep-equal": "^2.0.1", 34 | "fast-json-stable-stringify": "^2.0.0", 35 | "json-schema-traverse": "^0.4.1", 36 | "uri-js": "^4.2.2" 37 | } 38 | }, 39 | "ansi-styles": { 40 | "version": "3.2.1", 41 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 42 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 43 | "requires": { 44 | "color-convert": "^1.9.0" 45 | } 46 | }, 47 | "asn1": { 48 | "version": "0.2.4", 49 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 50 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 51 | "requires": { 52 | "safer-buffer": "~2.1.0" 53 | } 54 | }, 55 | "assert-plus": { 56 | "version": "1.0.0", 57 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 58 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 59 | }, 60 | "async": { 61 | "version": "3.1.0", 62 | "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", 63 | "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" 64 | }, 65 | "asynckit": { 66 | "version": "0.4.0", 67 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 68 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 69 | }, 70 | "aws-sign2": { 71 | "version": "0.7.0", 72 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 73 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 74 | }, 75 | "aws4": { 76 | "version": "1.8.0", 77 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 78 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 79 | }, 80 | "bcrypt-pbkdf": { 81 | "version": "1.0.2", 82 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 83 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 84 | "requires": { 85 | "tweetnacl": "^0.14.3" 86 | } 87 | }, 88 | "big-number": { 89 | "version": "1.0.0", 90 | "resolved": "https://registry.npmjs.org/big-number/-/big-number-1.0.0.tgz", 91 | "integrity": "sha512-cHUzdT+mMXd1ozht8n5ZwBlNiPO/4zCqqkyp3lF1TMPsRJLXUbQ7cKnfXRkrW475H5SOtSOP0HFeihNbpa53MQ==" 92 | }, 93 | "bl": { 94 | "version": "2.2.0", 95 | "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", 96 | "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", 97 | "requires": { 98 | "readable-stream": "^2.3.5", 99 | "safe-buffer": "^5.1.1" 100 | }, 101 | "dependencies": { 102 | "process-nextick-args": { 103 | "version": "2.0.1", 104 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 105 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 106 | }, 107 | "readable-stream": { 108 | "version": "2.3.6", 109 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 110 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 111 | "requires": { 112 | "core-util-is": "~1.0.0", 113 | "inherits": "~2.0.3", 114 | "isarray": "~1.0.0", 115 | "process-nextick-args": "~2.0.0", 116 | "safe-buffer": "~5.1.1", 117 | "string_decoder": "~1.1.1", 118 | "util-deprecate": "~1.0.1" 119 | } 120 | }, 121 | "string_decoder": { 122 | "version": "1.1.1", 123 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 124 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 125 | "requires": { 126 | "safe-buffer": "~5.1.0" 127 | } 128 | } 129 | } 130 | }, 131 | "buffer-equal-constant-time": { 132 | "version": "1.0.1", 133 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 134 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 135 | }, 136 | "camelcase": { 137 | "version": "5.3.1", 138 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 139 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" 140 | }, 141 | "caseless": { 142 | "version": "0.12.0", 143 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 144 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 145 | }, 146 | "cliui": { 147 | "version": "5.0.0", 148 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", 149 | "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", 150 | "requires": { 151 | "string-width": "^3.1.0", 152 | "strip-ansi": "^5.2.0", 153 | "wrap-ansi": "^5.1.0" 154 | }, 155 | "dependencies": { 156 | "ansi-regex": { 157 | "version": "4.1.0", 158 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 159 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" 160 | }, 161 | "is-fullwidth-code-point": { 162 | "version": "2.0.0", 163 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 164 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 165 | }, 166 | "string-width": { 167 | "version": "3.1.0", 168 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 169 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 170 | "requires": { 171 | "emoji-regex": "^7.0.1", 172 | "is-fullwidth-code-point": "^2.0.0", 173 | "strip-ansi": "^5.1.0" 174 | } 175 | }, 176 | "strip-ansi": { 177 | "version": "5.2.0", 178 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 179 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 180 | "requires": { 181 | "ansi-regex": "^4.1.0" 182 | } 183 | } 184 | } 185 | }, 186 | "color-convert": { 187 | "version": "1.9.3", 188 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 189 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 190 | "requires": { 191 | "color-name": "1.1.3" 192 | } 193 | }, 194 | "color-name": { 195 | "version": "1.1.3", 196 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 197 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 198 | }, 199 | "combined-stream": { 200 | "version": "1.0.8", 201 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 202 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 203 | "requires": { 204 | "delayed-stream": "~1.0.0" 205 | } 206 | }, 207 | "core-util-is": { 208 | "version": "1.0.2", 209 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 210 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 211 | }, 212 | "cuint": { 213 | "version": "0.2.2", 214 | "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", 215 | "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=" 216 | }, 217 | "dashdash": { 218 | "version": "1.14.1", 219 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 220 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 221 | "requires": { 222 | "assert-plus": "^1.0.0" 223 | } 224 | }, 225 | "date-utils": { 226 | "version": "1.2.21", 227 | "resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz", 228 | "integrity": "sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q=" 229 | }, 230 | "debug": { 231 | "version": "3.2.6", 232 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 233 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 234 | "requires": { 235 | "ms": "^2.1.1" 236 | } 237 | }, 238 | "decamelize": { 239 | "version": "1.2.0", 240 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 241 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 242 | }, 243 | "delayed-stream": { 244 | "version": "1.0.0", 245 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 246 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 247 | }, 248 | "depd": { 249 | "version": "1.1.2", 250 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 251 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 252 | }, 253 | "ecc-jsbn": { 254 | "version": "0.1.2", 255 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 256 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 257 | "requires": { 258 | "jsbn": "~0.1.0", 259 | "safer-buffer": "^2.1.0" 260 | } 261 | }, 262 | "ecdsa-sig-formatter": { 263 | "version": "1.0.11", 264 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 265 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 266 | "requires": { 267 | "safe-buffer": "^5.0.1" 268 | } 269 | }, 270 | "emoji-regex": { 271 | "version": "7.0.3", 272 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 273 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" 274 | }, 275 | "extend": { 276 | "version": "3.0.2", 277 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 278 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 279 | }, 280 | "extsprintf": { 281 | "version": "1.3.0", 282 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 283 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 284 | }, 285 | "fast-deep-equal": { 286 | "version": "2.0.1", 287 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 288 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 289 | }, 290 | "fast-json-stable-stringify": { 291 | "version": "2.0.0", 292 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 293 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 294 | }, 295 | "find-up": { 296 | "version": "3.0.0", 297 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 298 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 299 | "requires": { 300 | "locate-path": "^3.0.0" 301 | } 302 | }, 303 | "forever-agent": { 304 | "version": "0.6.1", 305 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 306 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 307 | }, 308 | "form-data": { 309 | "version": "2.3.3", 310 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 311 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 312 | "requires": { 313 | "asynckit": "^0.4.0", 314 | "combined-stream": "^1.0.6", 315 | "mime-types": "^2.1.12" 316 | } 317 | }, 318 | "generic-pool": { 319 | "version": "3.7.1", 320 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.7.1.tgz", 321 | "integrity": "sha512-ug6DAZoNgWm6q5KhPFA+hzXfBLFQu5sTXxPpv44DmE0A2g+CiHoq9LTVdkXpZMkYVMoGw83F6W+WT0h0MFMK/w==" 322 | }, 323 | "get-caller-file": { 324 | "version": "2.0.5", 325 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 326 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 327 | }, 328 | "getpass": { 329 | "version": "0.1.7", 330 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 331 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 332 | "requires": { 333 | "assert-plus": "^1.0.0" 334 | } 335 | }, 336 | "har-schema": { 337 | "version": "2.0.0", 338 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 339 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 340 | }, 341 | "har-validator": { 342 | "version": "5.1.3", 343 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 344 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 345 | "requires": { 346 | "ajv": "^6.5.5", 347 | "har-schema": "^2.0.0" 348 | } 349 | }, 350 | "http-signature": { 351 | "version": "1.2.0", 352 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 353 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 354 | "requires": { 355 | "assert-plus": "^1.0.0", 356 | "jsprim": "^1.2.2", 357 | "sshpk": "^1.7.0" 358 | } 359 | }, 360 | "inherits": { 361 | "version": "2.0.3", 362 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 363 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 364 | }, 365 | "is-typedarray": { 366 | "version": "1.0.0", 367 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 368 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 369 | }, 370 | "isarray": { 371 | "version": "1.0.0", 372 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 373 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 374 | }, 375 | "isstream": { 376 | "version": "0.1.2", 377 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 378 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 379 | }, 380 | "jsbn": { 381 | "version": "0.1.1", 382 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 383 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 384 | }, 385 | "json-schema": { 386 | "version": "0.2.3", 387 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 388 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 389 | }, 390 | "json-schema-traverse": { 391 | "version": "0.4.1", 392 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 393 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 394 | }, 395 | "json-stringify-safe": { 396 | "version": "5.0.1", 397 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 398 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 399 | }, 400 | "jsprim": { 401 | "version": "1.4.1", 402 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 403 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 404 | "requires": { 405 | "assert-plus": "1.0.0", 406 | "extsprintf": "1.3.0", 407 | "json-schema": "0.2.3", 408 | "verror": "1.10.0" 409 | } 410 | }, 411 | "jwa": { 412 | "version": "1.4.1", 413 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 414 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 415 | "requires": { 416 | "buffer-equal-constant-time": "1.0.1", 417 | "ecdsa-sig-formatter": "1.0.11", 418 | "safe-buffer": "^5.0.1" 419 | } 420 | }, 421 | "jws": { 422 | "version": "3.2.2", 423 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 424 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 425 | "requires": { 426 | "jwa": "^1.4.1", 427 | "safe-buffer": "^5.0.1" 428 | } 429 | }, 430 | "locate-path": { 431 | "version": "3.0.0", 432 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 433 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 434 | "requires": { 435 | "p-locate": "^3.0.0", 436 | "path-exists": "^3.0.0" 437 | } 438 | }, 439 | "mime-db": { 440 | "version": "1.40.0", 441 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 442 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" 443 | }, 444 | "mime-types": { 445 | "version": "2.1.24", 446 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 447 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 448 | "requires": { 449 | "mime-db": "1.40.0" 450 | } 451 | }, 452 | "ms": { 453 | "version": "2.1.2", 454 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 455 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 456 | }, 457 | "mssql": { 458 | "version": "5.1.0", 459 | "resolved": "https://registry.npmjs.org/mssql/-/mssql-5.1.0.tgz", 460 | "integrity": "sha512-eHrqRWCEBaXo48y2ZBaDleFvrWm2vYm6dNm1ci0XLYxm6kUb4KRsvjl74iKFhfYyuF9z6qzmTe/QmoQk+YVcVw==", 461 | "requires": { 462 | "debug": "^3.2.6", 463 | "generic-pool": "^3.6.1", 464 | "tedious": "^4.2.0" 465 | } 466 | }, 467 | "native-duplexpair": { 468 | "version": "1.0.0", 469 | "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", 470 | "integrity": "sha1-eJkHjmS/PIo9cyYBs9QP8F21j6A=" 471 | }, 472 | "oauth-sign": { 473 | "version": "0.9.0", 474 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 475 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 476 | }, 477 | "p-limit": { 478 | "version": "2.2.0", 479 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", 480 | "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", 481 | "requires": { 482 | "p-try": "^2.0.0" 483 | } 484 | }, 485 | "p-locate": { 486 | "version": "3.0.0", 487 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 488 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 489 | "requires": { 490 | "p-limit": "^2.0.0" 491 | } 492 | }, 493 | "p-try": { 494 | "version": "2.2.0", 495 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 496 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" 497 | }, 498 | "parse-duration": { 499 | "version": "0.1.1", 500 | "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.1.tgz", 501 | "integrity": "sha1-ExFN3JiRwezSgANiRFVN5DZHoiY=" 502 | }, 503 | "path-exists": { 504 | "version": "3.0.0", 505 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 506 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" 507 | }, 508 | "performance-now": { 509 | "version": "2.1.0", 510 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 511 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 512 | }, 513 | "psl": { 514 | "version": "1.2.0", 515 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", 516 | "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" 517 | }, 518 | "punycode": { 519 | "version": "2.1.1", 520 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 521 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 522 | }, 523 | "qs": { 524 | "version": "6.5.2", 525 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 526 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 527 | }, 528 | "request": { 529 | "version": "2.88.0", 530 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 531 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 532 | "requires": { 533 | "aws-sign2": "~0.7.0", 534 | "aws4": "^1.8.0", 535 | "caseless": "~0.12.0", 536 | "combined-stream": "~1.0.6", 537 | "extend": "~3.0.2", 538 | "forever-agent": "~0.6.1", 539 | "form-data": "~2.3.2", 540 | "har-validator": "~5.1.0", 541 | "http-signature": "~1.2.0", 542 | "is-typedarray": "~1.0.0", 543 | "isstream": "~0.1.2", 544 | "json-stringify-safe": "~5.0.1", 545 | "mime-types": "~2.1.19", 546 | "oauth-sign": "~0.9.0", 547 | "performance-now": "^2.1.0", 548 | "qs": "~6.5.2", 549 | "safe-buffer": "^5.1.2", 550 | "tough-cookie": "~2.4.3", 551 | "tunnel-agent": "^0.6.0", 552 | "uuid": "^3.3.2" 553 | }, 554 | "dependencies": { 555 | "safe-buffer": { 556 | "version": "5.2.0", 557 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 558 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 559 | } 560 | } 561 | }, 562 | "require-directory": { 563 | "version": "2.1.1", 564 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 565 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 566 | }, 567 | "require-main-filename": { 568 | "version": "2.0.0", 569 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 570 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" 571 | }, 572 | "safe-buffer": { 573 | "version": "5.1.1", 574 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 575 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 576 | }, 577 | "safer-buffer": { 578 | "version": "2.1.2", 579 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 580 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 581 | }, 582 | "set-blocking": { 583 | "version": "2.0.0", 584 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 585 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 586 | }, 587 | "sprintf-js": { 588 | "version": "1.1.2", 589 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", 590 | "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" 591 | }, 592 | "sshpk": { 593 | "version": "1.16.1", 594 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 595 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 596 | "requires": { 597 | "asn1": "~0.2.3", 598 | "assert-plus": "^1.0.0", 599 | "bcrypt-pbkdf": "^1.0.0", 600 | "dashdash": "^1.12.0", 601 | "ecc-jsbn": "~0.1.1", 602 | "getpass": "^0.1.1", 603 | "jsbn": "~0.1.0", 604 | "safer-buffer": "^2.0.2", 605 | "tweetnacl": "~0.14.0" 606 | } 607 | }, 608 | "tedious": { 609 | "version": "4.2.0", 610 | "resolved": "https://registry.npmjs.org/tedious/-/tedious-4.2.0.tgz", 611 | "integrity": "sha512-Py59XmvMcYWdjc1qyXDsbBwQE3yM8CJzuDnagjRpwjgndaBQXBULDI3D6OxKClbTNxA3qaLBFd9DjfV+is3AYA==", 612 | "requires": { 613 | "adal-node": "^0.1.22", 614 | "big-number": "1.0.0", 615 | "bl": "^2.0.1", 616 | "depd": "^1.1.2", 617 | "iconv-lite": "^0.4.23", 618 | "native-duplexpair": "^1.0.0", 619 | "punycode": "^2.1.0", 620 | "readable-stream": "^3.0.3", 621 | "sprintf-js": "^1.1.1" 622 | }, 623 | "dependencies": { 624 | "iconv-lite": { 625 | "version": "0.4.24", 626 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 627 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 628 | "requires": { 629 | "safer-buffer": ">= 2.1.2 < 3" 630 | } 631 | }, 632 | "readable-stream": { 633 | "version": "3.4.0", 634 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", 635 | "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", 636 | "requires": { 637 | "inherits": "^2.0.3", 638 | "string_decoder": "^1.1.1", 639 | "util-deprecate": "^1.0.1" 640 | } 641 | }, 642 | "string_decoder": { 643 | "version": "1.2.0", 644 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", 645 | "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", 646 | "requires": { 647 | "safe-buffer": "~5.1.0" 648 | } 649 | } 650 | } 651 | }, 652 | "tough-cookie": { 653 | "version": "2.4.3", 654 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 655 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 656 | "requires": { 657 | "psl": "^1.1.24", 658 | "punycode": "^1.4.1" 659 | }, 660 | "dependencies": { 661 | "punycode": { 662 | "version": "1.4.1", 663 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 664 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 665 | } 666 | } 667 | }, 668 | "tunnel-agent": { 669 | "version": "0.6.0", 670 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 671 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 672 | "requires": { 673 | "safe-buffer": "^5.0.1" 674 | } 675 | }, 676 | "tweetnacl": { 677 | "version": "0.14.5", 678 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 679 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 680 | }, 681 | "underscore": { 682 | "version": "1.9.1", 683 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", 684 | "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" 685 | }, 686 | "uri-js": { 687 | "version": "4.2.2", 688 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 689 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 690 | "requires": { 691 | "punycode": "^2.1.0" 692 | } 693 | }, 694 | "util-deprecate": { 695 | "version": "1.0.2", 696 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 697 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 698 | }, 699 | "uuid": { 700 | "version": "3.3.2", 701 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 702 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 703 | }, 704 | "verror": { 705 | "version": "1.10.0", 706 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 707 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 708 | "requires": { 709 | "assert-plus": "^1.0.0", 710 | "core-util-is": "1.0.2", 711 | "extsprintf": "^1.2.0" 712 | } 713 | }, 714 | "which-module": { 715 | "version": "2.0.0", 716 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 717 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" 718 | }, 719 | "wrap-ansi": { 720 | "version": "5.1.0", 721 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", 722 | "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", 723 | "requires": { 724 | "ansi-styles": "^3.2.0", 725 | "string-width": "^3.0.0", 726 | "strip-ansi": "^5.0.0" 727 | }, 728 | "dependencies": { 729 | "ansi-regex": { 730 | "version": "4.1.0", 731 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 732 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" 733 | }, 734 | "is-fullwidth-code-point": { 735 | "version": "2.0.0", 736 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 737 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 738 | }, 739 | "string-width": { 740 | "version": "3.1.0", 741 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 742 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 743 | "requires": { 744 | "emoji-regex": "^7.0.1", 745 | "is-fullwidth-code-point": "^2.0.0", 746 | "strip-ansi": "^5.1.0" 747 | } 748 | }, 749 | "strip-ansi": { 750 | "version": "5.2.0", 751 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 752 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 753 | "requires": { 754 | "ansi-regex": "^4.1.0" 755 | } 756 | } 757 | } 758 | }, 759 | "xmldom": { 760 | "version": "0.1.27", 761 | "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", 762 | "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" 763 | }, 764 | "xpath.js": { 765 | "version": "1.1.0", 766 | "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz", 767 | "integrity": "sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==" 768 | }, 769 | "xxhashjs": { 770 | "version": "0.2.2", 771 | "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", 772 | "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", 773 | "requires": { 774 | "cuint": "^0.2.2" 775 | } 776 | }, 777 | "y18n": { 778 | "version": "4.0.0", 779 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", 780 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" 781 | }, 782 | "yargs": { 783 | "version": "13.3.0", 784 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", 785 | "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", 786 | "requires": { 787 | "cliui": "^5.0.0", 788 | "find-up": "^3.0.0", 789 | "get-caller-file": "^2.0.1", 790 | "require-directory": "^2.1.1", 791 | "require-main-filename": "^2.0.0", 792 | "set-blocking": "^2.0.0", 793 | "string-width": "^3.0.0", 794 | "which-module": "^2.0.0", 795 | "y18n": "^4.0.0", 796 | "yargs-parser": "^13.1.1" 797 | }, 798 | "dependencies": { 799 | "ansi-regex": { 800 | "version": "4.1.0", 801 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 802 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" 803 | }, 804 | "is-fullwidth-code-point": { 805 | "version": "2.0.0", 806 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 807 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 808 | }, 809 | "string-width": { 810 | "version": "3.1.0", 811 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 812 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 813 | "requires": { 814 | "emoji-regex": "^7.0.1", 815 | "is-fullwidth-code-point": "^2.0.0", 816 | "strip-ansi": "^5.1.0" 817 | } 818 | }, 819 | "strip-ansi": { 820 | "version": "5.2.0", 821 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 822 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 823 | "requires": { 824 | "ansi-regex": "^4.1.0" 825 | } 826 | } 827 | } 828 | }, 829 | "yargs-parser": { 830 | "version": "13.1.1", 831 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", 832 | "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", 833 | "requires": { 834 | "camelcase": "^5.0.0", 835 | "decamelize": "^1.2.0" 836 | } 837 | } 838 | } 839 | } 840 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sqlmon", 3 | "version": "1.5.0", 4 | "description": "Collects events from SQL Server and saves them to Elasticseach for further analysis.", 5 | "main": "sqlmon.js", 6 | "dependencies": { 7 | "mssql": "5.1.0", 8 | "parse-duration": "0.1.1", 9 | "underscore": "1.9.1", 10 | "xxhashjs": "0.2.2", 11 | "yargs": "13.3.0" 12 | }, 13 | "preferGlobal": true, 14 | "bin": { 15 | "sqlmon": "./sqlmon.js" 16 | }, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/soheilpro/sqlmon" 23 | }, 24 | "keywords": [ 25 | "sql", 26 | "sqlserver", 27 | "elasticsearch", 28 | "kibana" 29 | ], 30 | "author": "Soheil Rashidi", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/soheilpro/sqlmon/issues" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pad.js: -------------------------------------------------------------------------------- 1 | function padLeft(nr, n, str){ 2 | return Array(n - String(nr).length + 1).join(str || ' ') + nr; 3 | } 4 | 5 | module.exports = { 6 | padLeft, 7 | }; 8 | -------------------------------------------------------------------------------- /processor.js: -------------------------------------------------------------------------------- 1 | class Processor { 2 | config; 3 | 4 | constructor(config) { 5 | this.config = config; 6 | } 7 | 8 | async process(events) { 9 | const documents = events.map(event => { 10 | const document = {}; 11 | 12 | for (const field of this.config.fields) 13 | document[field.alias || field.name] = field.transform ? field.transform(event[field.name]) : event[field.name]; 14 | 15 | return document; 16 | }); 17 | 18 | await this.config.elasticsearch.insertDocuments(documents); 19 | } 20 | } 21 | 22 | module.exports = Processor; 23 | -------------------------------------------------------------------------------- /reader.js: -------------------------------------------------------------------------------- 1 | const _ = require('underscore'); 2 | 3 | class Reader { 4 | config; 5 | onEvents; 6 | onEnd; 7 | 8 | constructor(config) { 9 | this.config = config; 10 | } 11 | 12 | async count() { 13 | const query = ` 14 | SELECT COUNT(*) as Count 15 | FROM fn_trace_gettable(N'${this.config.traceFilePath}', default) 16 | WHERE EventClass NOT IN ( 17 | 65528, -- Profiler Start 18 | 65534, -- ? 19 | 65533 -- Profiler End 20 | ) 21 | `; 22 | 23 | const result = await this.config.connection.execute(query); 24 | 25 | return result[0]['Count']; 26 | } 27 | 28 | async start() { 29 | const columns = _.uniq(this.config.fields.map(field => `[${field.name}]`)).join(', '); 30 | const query = ` 31 | SELECT ${columns} 32 | FROM fn_trace_gettable(N'${this.config.traceFilePath}', default) 33 | WHERE EventClass NOT IN ( 34 | 65528, -- Profiler Start 35 | 65534, -- ? 36 | 65533 -- Profiler End 37 | ) 38 | `; 39 | 40 | this.config.connection.stream(query, this.config.batchSize, (events) => this.onEvents(events), () => this.onEnd()); 41 | } 42 | } 43 | 44 | module.exports = Reader; 45 | -------------------------------------------------------------------------------- /sqlmon.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const child_process = require('child_process'); 4 | const parseDuration = require('parse-duration') 5 | const Connection = require('./connection'); 6 | const Elasticsearch = require('./elasticsearch'); 7 | const Events = require('./events'); 8 | const Fields = require('./fields'); 9 | const Processor = require('./processor'); 10 | const Reader = require('./reader'); 11 | const Trace = require('./trace'); 12 | const wait = require('./wait'); 13 | const padLeft = require('./pad').padLeft; 14 | 15 | let yargs = require('yargs') 16 | .option('ss-address', { type: 'string', demandOption: true, describe: 'SQL Server address.' }) 17 | .option('ss-port', { type: 'numbe', default: 1433, describe: 'SQL Server port.' }) 18 | .option('ss-user', { type: 'string', demandOption: true,describe: 'SQL Server user.' }) 19 | .option('ss-password', { type: 'string', demandOption: true, describe: 'SQL server password.' }) 20 | .option('ss-timeout', { type: 'number', default: 60, describe: 'SQL Server timeout (in seconds).' }) 21 | .option('events', { type: 'array', default: ['RPCCompleted', 'SQLBatchCompleted'], describe: 'Events to capture.' }) 22 | .option('fields', { type: 'array', default: ['EventClass', 'TextData', 'TextDataHash', 'QueryHash', 'HostName', 'ClientProcessID', 'LoginName', 'SPID', 'Duration', 'StartTime', 'EndTime', 'Reads', 'Writes', 'CPU', 'ServerName', 'DatabaseName', 'RowCounts'], describe: 'Fields to include.' }) 23 | .option('delay', { type: 'string', describe: 'Time to wait before starting.' }) 24 | .option('collect-only', { type: 'boolean', conflicts: 'import', describe: 'Only collect events and do not save them to Elasticsearch.' }) 25 | .option('import', { type: 'string', conflicts: 'collect-only', describe: 'Trace file to import.' }) 26 | .option('schedule-hook', { type: 'string', describe: 'Script to run when program is scheduled to run at a later time.' }) 27 | .option('collection-start-hook', { type: 'string', describe: 'Script to run when collection starts.' }) 28 | .option('collection-end-hook', { type: 'string', describe: 'Script to run when collection ends.' }) 29 | .option('save-start-hook', { type: 'string', describe: 'Script to run when saving starts.' }) 30 | .option('save-end-hook', { type: 'string', describe: 'Script to run when saving ends.' }) 31 | .option('error-hook', { type: 'string', describe: 'Script to run when an error occurs.' }) 32 | .option('interrupt-hook', { type: 'string', describe: 'Script to run when the program is interrupted.' }); 33 | 34 | if (!yargs.argv['import']) { 35 | yargs = yargs 36 | .option('trace-directory', { type: 'string', demandOption: true, describe: 'Directory to create the trace file.' }) 37 | .option('duration', { type: 'string', demandOption: true, alias: 'd', describe: 'Duration to run the profiler.' }) 38 | .option('max-size', { type: 'number', default: 1024, describe: 'Maximum size of the trace file (in megabytes).' }); 39 | } 40 | 41 | if (!yargs.argv['collect-only']) { 42 | yargs = yargs 43 | .option('es-address', { type: 'string', demandOption: true, describe: 'Elasticsearch address.' }) 44 | .option('es-port', { type: 'number', default: 9200, describe: 'Elasticsearch port.' }) 45 | .option('es-username', { type: 'string', describe: 'Elasticsearch username.' }) 46 | .option('es-password', { type: 'string', describe: 'Elasticsearch password.' }) 47 | .option('es-timeout', { type: 'number', default: 60, describe: 'Elasticsearch timeout (in seconds).' }) 48 | .option('batch-size', { type: 'number', default: 1000, describe: 'Number of events to save in each batch.' }) 49 | .option('index-prefix', { type: 'string', default: 'sql-', describe: 'Elasticsearch index prefix.' }); 50 | } 51 | 52 | const argv = yargs 53 | .strict(true) 54 | .argv; 55 | 56 | let env = { 57 | ...process.env, 58 | SQLMON_SQLSERVER_ADDRESS: argv['ss-address'], 59 | SQLMON_SQLSERVER_PORT: argv['ss-port'], 60 | SQLMON_SQLSERVER_USER: argv['ss-user'], 61 | SQLMON_SQLSERVER_PASSWORD: argv['ss-password'], 62 | SQLMON_SQLSERVER_TIMEOUT: argv['ss-timeout'], 63 | SQLMON_ELASTICSEARCH_ADDRESS: argv['es-address'], 64 | SQLMON_ELASTICSEARCH_USERNAME: argv['es-username'], 65 | SQLMON_ELASTICSEARCH_PASSWORD: argv['es-password'], 66 | SQLMON_ELASTICSEARCH_PORT: argv['es-port'], 67 | SQLMON_ELASTICSEARCH_TIMEOUT: argv['es-timeout'], 68 | SQLMON_TRACE_DIRECTORY: argv['trace-directory'], 69 | SQLMON_INDEX_PREFIX: argv['index-prefix'], 70 | SQLMON_EVENTS: argv['events'], 71 | SQLMON_FIELDS: argv['fields'], 72 | SQLMON_DURATION: argv['duration'], 73 | SQLMON_MAX_SIZE: argv['max-size'], 74 | SQLMON_BATCH_SIZE: argv['batch-size'], 75 | SQLMON_DELAY: argv['delay'], 76 | SQLMON_COLLECT_ONLY: !!argv['collect-only'], 77 | SQLMON_IMPORT: argv['import'], 78 | }; 79 | 80 | const cleanupHandlers = []; 81 | 82 | async function cleanup() { 83 | for (cleanupHandler of cleanupHandlers.reverse()) 84 | await cleanupHandler(); 85 | }; 86 | 87 | async function onError(error) { 88 | console.error(error); 89 | 90 | await cleanup(); 91 | 92 | if (argv['error-hook']) { 93 | child_process.execFile(argv['error-hook'], { 94 | env: { 95 | ...env, 96 | SQLMON_ERROR: error.toString(), 97 | } 98 | }); 99 | } 100 | } 101 | 102 | async function onInterrupt() { 103 | await cleanup(); 104 | 105 | if (argv['interrupt-hook']) { 106 | child_process.execFile(argv['interrupt-hook'], { 107 | env: env, 108 | }); 109 | } 110 | 111 | process.exit(); 112 | } 113 | 114 | process.on('SIGTERM', onInterrupt); 115 | process.on('SIGINT', onInterrupt); 116 | process.on('uncaughtException', onError); 117 | process.on('unhandledRejection', onError); 118 | 119 | async function main() { 120 | const events = argv['events'].map(event => Events[event]); 121 | const fields = argv['fields'].map(field => Fields[field]); 122 | let traceFilePath; 123 | 124 | // StartTime must always be included 125 | if (argv['fields'].indexOf('StartTime') === -1); 126 | fields.push(Fields.StartTime); 127 | 128 | const connection = new Connection({ 129 | server: argv['ss-address'], 130 | port: argv['ss-port'], 131 | user: argv['ss-user'], 132 | password: argv['ss-password'], 133 | requestTimeout: argv['ss-timeout'] * 1000, 134 | }); 135 | 136 | cleanupHandlers.push(async () => { 137 | await connection.close(); 138 | }); 139 | 140 | if (argv['delay']) { 141 | const delay = parseDuration(argv['delay']); 142 | const startDateTime = new Date(Date.now() + delay); 143 | const startTime = `${padLeft(startDateTime.getHours(), 2, '0')}:${padLeft(startDateTime.getMinutes(), 2, '0')}:${padLeft(startDateTime.getSeconds(), 2, '0')}`; 144 | 145 | if (argv['schedule-hook']) { 146 | child_process.execFile(argv['schedule-hook'], { 147 | env: { 148 | ...env, 149 | SQLMON_START_DATETIME: startDateTime.toISOString(), 150 | SQLMON_START_TIME: startTime, 151 | } 152 | }); 153 | } 154 | 155 | await wait(delay, `Waiting ${argv['delay']} until ${startTime}... (Press [s] to start now)`, 's'); 156 | } 157 | 158 | console.log(`Connecting to ${argv['ss-address']}...`); 159 | 160 | await connection.open(); 161 | 162 | if (!argv['import']) { 163 | const trace = new Trace({ 164 | connection: connection, 165 | events: events, 166 | fields: fields, 167 | traceDirectory: argv['trace-directory'], 168 | maxSize: argv['max-size'], 169 | }); 170 | 171 | cleanupHandlers.push(async () => { 172 | await trace.stop(); 173 | }); 174 | 175 | console.log("Creating trace..."); 176 | 177 | await trace.create(); 178 | 179 | traceFilePath = trace.traceFilePath; 180 | 181 | env = { 182 | ...env, 183 | SQLMON_TRACE_FILE_PATH: traceFilePath, 184 | }; 185 | 186 | console.log(`Trace file path: ${traceFilePath}`); 187 | console.log("Starting trace..."); 188 | 189 | const duration = parseDuration(argv['duration']); 190 | const stopDateTime = new Date(Date.now() + duration); 191 | const stopTime = `${padLeft(stopDateTime.getHours(), 2, '0')}:${padLeft(stopDateTime.getMinutes(), 2, '0')}:${padLeft(stopDateTime.getSeconds(), 2, '0')}`; 192 | 193 | if (argv['collection-start-hook']) { 194 | child_process.execFile(argv['collection-start-hook'], { 195 | env: { 196 | ...env, 197 | SQLMON_STOP_DATETIME: stopDateTime.toISOString(), 198 | SQLMON_STOP_TIME: stopTime, 199 | } 200 | }); 201 | } 202 | 203 | await trace.start(); 204 | 205 | await wait(duration, `Collecting events for ${argv['duration']} until ${stopTime}... (Press [c] to stop now)`, 'c'); 206 | 207 | console.log("Stopping trace..."); 208 | 209 | if (argv['collection-end-hook']) { 210 | child_process.execFile(argv['collection-end-hook'], { 211 | env: env, 212 | }); 213 | } 214 | 215 | await trace.stop(); 216 | } 217 | else { 218 | console.log(`Importing ${argv['import']}...`); 219 | 220 | traceFilePath = argv['import']; 221 | 222 | env = { 223 | ...env, 224 | SQLMON_TRACE_FILE_PATH: traceFilePath, 225 | }; 226 | } 227 | 228 | if (!argv['collect-only']) { 229 | const reader = new Reader({ 230 | connection: connection, 231 | traceFilePath: traceFilePath, 232 | fields: fields, 233 | batchSize: argv['batch-size'], 234 | }); 235 | 236 | const elasticsearch = new Elasticsearch({ 237 | address: argv['es-address'], 238 | username: argv['es-username'], 239 | password: argv['es-password'], 240 | port: argv['es-port'], 241 | timeout: argv['es-timeout'] * 1000, 242 | indexPrefix: argv['index-prefix'], 243 | }); 244 | 245 | const processor = new Processor({ 246 | fields: fields, 247 | elasticsearch: elasticsearch, 248 | }); 249 | 250 | if (argv['save-start-hook']) { 251 | child_process.execFile(argv['save-start-hook'], { 252 | env: env, 253 | }); 254 | } 255 | 256 | console.log(`Reading trace...`); 257 | 258 | const totalEvents = await reader.count(); 259 | let savedEvents = 0; 260 | 261 | console.log(`Saving ${totalEvents} events to ${argv['es-address']}...`); 262 | 263 | reader.start(); 264 | 265 | reader.onEvents = async (events) => { 266 | await processor.process(events); 267 | 268 | savedEvents += events.length; 269 | const progress = (savedEvents * 100 / totalEvents).toFixed(2); 270 | process.stdout.write('\033[999D'); // Move cursor to the first column 271 | process.stdout.write(padLeft(progress, 6) + '% ' + padLeft(savedEvents, totalEvents.toString().length) + '/' + totalEvents); 272 | }; 273 | 274 | reader.onEnd = async () => { 275 | if (totalEvents > 0) 276 | console.log(); 277 | 278 | console.log("Disconnecting..."); 279 | 280 | if (argv['save-end-hook']) { 281 | child_process.execFile(argv['save-end-hook'], { 282 | env: env, 283 | }); 284 | } 285 | 286 | await connection.close(); 287 | }; 288 | } 289 | else { 290 | console.log("Disconnecting..."); 291 | 292 | await connection.close(); 293 | } 294 | } 295 | 296 | main(); 297 | -------------------------------------------------------------------------------- /trace.js: -------------------------------------------------------------------------------- 1 | class Trace { 2 | config; 3 | traceFilePath; 4 | traceId; 5 | 6 | constructor(config) { 7 | this.config = config; 8 | } 9 | 10 | async createTrace(connection, filename, maxSize) { 11 | const query = ` 12 | DECLARE @traceid INT; 13 | DECLARE @maxfilesize BIGINT = ${maxSize}; 14 | EXEC sp_trace_create @traceid OUTPUT, 0, N'${filename}', @maxfilesize; 15 | SELECT @traceid AS TraceId; 16 | `; 17 | 18 | const result = await connection.execute(query); 19 | 20 | return result[0]['TraceId']; 21 | } 22 | 23 | async configureTrace(connection, traceId, events, fields) { 24 | let query = ''; 25 | 26 | for (const event of events) 27 | for (const field of fields) 28 | query += `EXEC sp_trace_setevent ${traceId}, ${event.id}, ${field.id}, 1;`; 29 | 30 | query += `EXEC sp_trace_setfilter ${traceId}, 1, 0, 7, N'exec sp_reset_connection' -- TextData NOT LIKE`; 31 | 32 | await connection.execute(query); 33 | } 34 | 35 | async startTrace(connection, traceId) { 36 | const query = ` 37 | EXEC sp_trace_setstatus ${traceId}, 1; -- start 38 | `; 39 | 40 | await connection.execute(query); 41 | } 42 | 43 | async stopTrace(connection, traceId) { 44 | const query = ` 45 | EXEC sp_trace_setstatus ${traceId}, 0; -- stop 46 | EXEC sp_trace_setstatus ${traceId}, 2; -- close 47 | `; 48 | 49 | await connection.execute(query); 50 | } 51 | 52 | async create() { 53 | this.traceFilePath = `${this.config.traceDirectory}\\${new Date().toISOString().replace(/\-/g, '').replace('T', '').replace(/:/g, '').replace('.', '').replace('Z', '')}`; 54 | this.traceId = await this.createTrace(this.config.connection, this.traceFilePath, this.config.maxSize); 55 | this.traceFilePath += '.trc'; // SQL Server automatically adds this 56 | 57 | await this.configureTrace(this.config.connection, this.traceId, this.config.events, this.config.fields); 58 | } 59 | 60 | async start() { 61 | await this.startTrace(this.config.connection, this.traceId); 62 | } 63 | 64 | async stop() { 65 | if (this.traceId) { 66 | try { 67 | await this.stopTrace(this.config.connection, this.traceId); 68 | } 69 | catch { 70 | } 71 | 72 | this.traceId = null; 73 | } 74 | } 75 | } 76 | 77 | module.exports = Trace; 78 | -------------------------------------------------------------------------------- /wait.js: -------------------------------------------------------------------------------- 1 | const readline = require('readline'); 2 | 3 | async function wait(milliseconds, message, cancelKey) { 4 | return new Promise(resolve => { 5 | readline.emitKeypressEvents(process.stdin); 6 | process.stdin.setRawMode(true); 7 | process.stdin.resume(); 8 | process.stdin.on('keypress', (_, key) => { 9 | if (key.name === cancelKey && !key.ctrl && !key.meta && !key.shift) 10 | end(); 11 | }); 12 | 13 | console.log(message); 14 | 15 | const timer = setTimeout(end, milliseconds); 16 | 17 | function end() { 18 | clearTimeout(timer); 19 | process.stdin.pause(); 20 | process.stdin.setRawMode(false); 21 | resolve(); 22 | } 23 | }); 24 | } 25 | 26 | module.exports = wait; 27 | --------------------------------------------------------------------------------