├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── go-zabbix-mssql.config.yaml └── go-zabbix-mssql.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | .DS_Store 3 | 4 | .idea/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.8 4 | - master 5 | env: 6 | global: 7 | - GOARCH=amd64 8 | - GO_FOR_RELEASE=1.8 9 | before_install: 10 | - go get github.com/AlekSi/zabbix-sender 11 | - go get github.com/denisenkom/go-mssqldb 12 | - go get gopkg.in/yaml.v2 13 | notifications: 14 | email: false 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 Pavel Fiskovich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/khannz/go-zabbix-mssql/master/LICENSE) [![Build Status](https://travis-ci.org/khannz/go-zabbix-mssql.svg?branch=master)](https://travis-ci.org/khannz/go-zabbix-mssql) [![Go Report Card](https://goreportcard.com/badge/github.com/khannz/go-zabbix-mssql)](https://goreportcard.com/report/github.com/khannz/go-zabbix-mssql) 2 | 3 | # go-zabbix-mssql 4 | 5 | ## Intro 6 | 7 | First of all, this is the utility to collect tricky metrics from `MS SQL Server` instance and send it to `Zabbix trapper` items using [Go implementation of zabbix-sender](https://github.com/AlekSi/zabbix-sender). By 'tricky' I mean things that must be collected from inside of Instance with proper queries. 8 | 9 | > Note: you can use go-zabbix-mssql to grab any data from DBs of Instance with key:value query result. Just check out queries from YAML, to get get exact idea. 10 | 11 | In case there would be developed any universal templates that can be easily imported into Zabbix and/or Grafana, it would be also added to repository. Anyway, I got plans to describe everything later in some kind of guide. 12 | 13 | One of the goals is to achieve something like following view with `zabbix` + `windows performance counters` + `go-zabbix-mssql` + `grafana` 14 | ![telegraf-sqlserver-full](https://cloud.githubusercontent.com/assets/16494280/12591426/fa2b17b4-c467-11e5-9c00-929f4c4aea57.png) 15 | 16 | ## How to cook 17 | 18 | With just one config YAML file, go-zabbix-mssql allows you to send (almost?) endless list of queries that is joining with `UNION ALL` before SQL engine get it. So technically any number of lines in YAML still produces just one query, amirite? Be careful about response time for final big query! 19 | 20 | ### Running 21 | 22 | You **must** use command line flags to tune few details for zabbix-sender part: 23 | * **-U** -- login for MS SQL connection 24 | * **-P** -- password for MS SQL connection 25 | * **-S** -- connection string in `server_name[\instance_name]` format 26 | * **-Z** -- zabbix-server FQDN where proper trapper items live 27 | * **-H** -- MS SQL Instance hostname, as it named in zabbix-server 28 | * **-F** -- full path to config YAML file, like `go-zabbix-mssql.config.yaml` 29 | 30 | ### Setting up Zabbix Agent 31 | 32 | go-zabbix-mssql can (and intended to) be easily used as part of zabbix-agent: 33 | 34 | * add compiled binary to your `${zabbix-agent\scripts}` or whatever path you use to store extensions in your zabbix-agent distributive 35 | * make sure, you have `UnsafeUserParameters=1` in `zabbix_agentd.conf` 36 | * also add something like `UserParameter=mssql.metrics[*],${zabbix-agent\scripts}\go-zabbix-mssql.exe -U=$1 -P=$2 -S=$3 -Z=$4 -H=$5 -F="${full path to YAML config}"` 37 | 38 | ### Setting up Zabbix Server 39 | 40 | * configure proper Macros details (in Zabbix console) to every monitored host (read official guide for `UnsafeUserParameters`) which gonna replace all those `$1`, `$2` and so on... In my case, part of Macroses belongs to 'MS SQL 2012 Template' template and part of it defined manually for each Instance. 41 | * add items to your template/host with type `Zabbix trapper`. Keys must be equal to metric name you await to receive 42 | * add and use item of `Zabbix agent` type with key `mssql.metrics` on each MS SQL Instance, configured as `Host` or in general template for MS SQL Instances 43 | * that's how you will control frequency of metrics gathering 44 | * this configuration also sends values of Macroses for every host, so you get most control of how your monitoring configured on zabbix-server side 45 | * execution of binary returns decimal number -- it shows how much of all collected metrics was not received correctly by zabbix-server. So perfect return must be `0` and that what would be self metric for go-zabbix-mssql (in case of troubles, answer will become text of error) 46 | 47 | ### Getting metrics for exact DBs 48 | 49 | TL;DR: You can notice that some part of metrics (last one monstrous query for now) from examples got also dynamic parts that returns metrics for each DB, not for whole Instance. If it is a goal for you (it is for me), it can be used with help of [popular method of MS SQL monitoring](https://share.zabbix.com/databases/microsoft-sql-server/template-app-ms-sql-default-installation-lld) by [Stephen Fritz](https://share.zabbix.com/owner/g_111769865974589121086). 50 | 51 | This solution collects data by LLD via WMI with PowerShell script. You receive an array `{#DBS}` with names of DBs as result. **Important to notice** - attached setup guide suggests to cut off system DBs from that LLD with *regular expression filter*. In case you planning to use go-zabbix-mssql for metrics of every table, do not implement this step. 52 | 53 | ## Notes 54 | 55 | I love [Prometheus](https://github.com/prometheus/prometheus) project and its ideas so that's why metrics from example YAML named so. 56 | 57 | ## TODOs 58 | 59 | All ideas respectively placed in [Projects](https://github.com/khannz/go-zabbix-mssql/projects) and [Milestones](https://github.com/khannz/go-zabbix-mssql/milestones) 60 | 61 | ## Thanks 62 | 63 | - Whole [Golang Project team](https://golang.org/project/), involved developers & engineers of [Google](https://google.com) as well as [Project Contributors](https://golang.org/CONTRIBUTORS) 64 | - [JetBrains s.r.o.](https://www.jetbrains.com) for [Gogland IDE](https://www.jetbrains.com/go/) which is damn awesome and feature-rich (for my very tiny needs) right from start of EAP program 65 | - Some guys from [Telegraf](https://github.com/influxdata/telegraf) contributors for R&D of [detailed and sophisticated metrics collection queries](https://github.com/influxdata/telegraf/blob/master/plugins/inputs/sqlserver/sqlserver.go#L191), which was perfectly combined for Grafana dashboard (shown at illustration above) 66 | - [AlekSi](https://github.com/AlekSi) for [Go implementation of zabbix-sender](https://github.com/AlekSi/zabbix-sender) 67 | - [denisenkom](https://github.com/denisenkom) for [Microsoft SQL driver written in Go](https://github.com/denisenkom/go-mssqldb) 68 | - Team of contributors, who developed [YAML.v2](https://github.com/go-yaml/yaml/tree/v2) Go package 69 | - [@mmcgrana](https://twitter.com/mmcgrana) for [Go by Example](https://gobyexample.com) project which helped me to dive into Golang with minimal doubts 70 | 71 | Hope it all is not too pretentious for 123 lines of code :trollface: 72 | -------------------------------------------------------------------------------- /go-zabbix-mssql.config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | sql_metrics: [ 3 | "SELECT 'db_state_ONLINE_total', Sum(CASE WHEN d.state = 0 THEN 1 ELSE 0 END) FROM sys.databases d", 4 | "SELECT 'db_state_RESTORING_total', Sum(CASE WHEN d.state = 1 THEN 1 ELSE 0 END) FROM sys.databases d", 5 | "SELECT 'db_state_RECOVERING_total', Sum(CASE WHEN d.state = 2 THEN 1 ELSE 0 END) FROM sys.databases d", 6 | "SELECT 'db_state_RECOVERY_PENDING_total', Sum(CASE WHEN d.state = 3 THEN 1 ELSE 0 END) FROM sys.databases d", 7 | "SELECT 'db_state_SUSPECT_total', Sum(CASE WHEN d.state = 4 THEN 1 ELSE 0 END) FROM sys.databases d", 8 | "SELECT 'db_state_EMERGENCY_total', Sum(CASE WHEN d.state = 5 THEN 1 ELSE 0 END) FROM sys.databases d", 9 | "SELECT 'db_state_OFFLINE_total', Sum(CASE WHEN d.state = 6 THEN 1 ELSE 0 END) FROM sys.databases d", 10 | "SELECT 'db_recovery_model_FULL_total', Sum(CASE WHEN d.recovery_model = 1 THEN 1 ELSE 0 END) FROM sys.databases d", 11 | "SELECT 'db_recovery_model_BULK_LOGGED_total', Sum(CASE WHEN d.recovery_model = 2 THEN 1 ELSE 0 END) FROM sys.databases d", 12 | "SELECT 'db_recovery_model_SIMPLE_total', Sum(CASE WHEN d.recovery_model = 3 THEN 1 ELSE 0 END) FROM sys.databases d", 13 | "SELECT 'db_perf_point_in_time_recovery', CASE WHEN 1 > 1.0 * COUNT(*) / NULLIF((SELECT COUNT(*) FROM sys.databases d WHERE database_id > 4), 0) THEN 0 ELSE 1 END FROM sys.databases d WHERE database_id > 4 AND recovery_model IN (1)", 14 | "SELECT 'db_perf_page_file_usage_percent', CAST(100 * (1 - available_page_file_kb * 1. / total_page_file_kb) as decimal(9,2)) FROM sys.dm_os_sys_memory", 15 | "SELECT 'db_perf_connection_memory_per_connection_bytes', CAST((cntr_value / (SELECT 1.0 * cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'User Connections')) * 1024 as int) FROM sys.dm_os_performance_counters WHERE counter_name = 'Connection Memory (KB)'", 16 | "SELECT 'db_perf_available_physical_memory_bytes', available_physical_memory_kb * 1024 FROM sys.dm_os_sys_memory", 17 | "SELECT 'db_perf_signal_wait_percent', CAST(100.0 * SUM(signal_wait_time_ms) / SUM (wait_time_ms) AS NUMERIC(20,2)) FROM sys.dm_os_wait_stats", 18 | "SELECT 'db_perf_sql_compilation_per_batch_request', 100.0 * cntr_value / (SELECT 1.0*cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Batch Requests/sec') FROM sys.dm_os_performance_counters WHERE counter_name = 'SQL Compilations/sec'", 19 | "SELECT 'db_perf_sql_recompilation_per_batch_request', 100.0 *cntr_value / (SELECT 1.0*cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Batch Requests/sec') FROM sys.dm_os_performance_counters WHERE counter_name = 'SQL Re-Compilations/sec'", 20 | "SELECT 'db_perf_page_lookup_per_batch_request', 100.0 * cntr_value / (SELECT 1.0*cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Batch Requests/sec') FROM sys.dm_os_performance_counters WHERE counter_name = 'Page lookups/sec'", 21 | "SELECT 'db_perf_page_split_per_batch_request', 100.0 * cntr_value / (SELECT 1.0*cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Batch Requests/sec') FROM sys.dm_os_performance_counters WHERE counter_name = 'Page splits/sec'", 22 | "SELECT 'db_perf_average_tasks', (SELECT AVG(current_tasks_count) FROM sys.dm_os_schedulers WITH (NOLOCK) WHERE scheduler_id < 255)", 23 | "SELECT 'db_perf_average_runnable_tasks', (SELECT AVG(runnable_tasks_count) FROM sys.dm_os_schedulers WITH (NOLOCK) WHERE scheduler_id < 255)", 24 | "SELECT 'db_perf_average_pending_disk_io', (SELECT AVG(pending_disk_io_count) FROM sys.dm_os_schedulers WITH (NOLOCK) WHERE scheduler_id < 255)", 25 | "SELECT 'db_perf_buffer_pool_rate_bytes_per_sec', (1.0*cntr_value * 8 * 1024) / (SELECT 1.0*cntr_value FROM sys.dm_os_performance_counters WHERE object_name like '%Buffer Manager%' AND lower(counter_name) = 'Page life expectancy') FROM sys.dm_os_performance_counters WHERE object_name like '%Buffer Manager%' AND counter_name = 'database pages'", 26 | "SELECT 'db_perf_memory_grants_pending', cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Memory Grants Pending'", 27 | "SELECT 'db_perf_readahead_per_page_read', 100.0 *cntr_value / (SELECT 1.0*cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Page Reads/sec') FROM sys.dm_os_performance_counters WHERE counter_name = 'Readahead pages/sec'", 28 | "SELECT 'db_perf_total_target_memory_ratio', 100.0 * cntr_value / (SELECT 1.0*cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Target Server Memory (KB)') FROM sys.dm_os_performance_counters WHERE counter_name = 'Total Server Memory (KB)'", 29 | "SET NOCOUNT ON 30 | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 31 | DECLARE @secondsBetween tinyint = 5 32 | DECLARE @delayInterval char(8) = CONVERT(Char(8), DATEADD(SECOND, 5, '00:00:00'), 108) 33 | IF OBJECT_ID('tempdb..#baseline') IS NOT NULL 34 | DROP TABLE #baseline; 35 | IF OBJECT_ID('tempdb..#baselinewritten') IS NOT NULL 36 | DROP TABLE #baselinewritten; 37 | SELECT DB_NAME(mf.database_id) AS databaseName, 38 | mf.physical_name, 39 | divfs.num_of_bytes_read, 40 | divfs.num_of_bytes_written, 41 | divfs.num_of_reads, 42 | divfs.num_of_writes, 43 | GETDATE() AS baselinedate 44 | INTO #baseline 45 | FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS divfs 46 | INNER JOIN sys.master_files AS mf ON mf.database_id = divfs.database_id 47 | AND mf.file_id = divfs.file_id 48 | WAITFOR DELAY @delayInterval; 49 | WITH currentLine AS ( 50 | SELECT 51 | DB_NAME(mf.database_id) AS databaseName 52 | , type_desc 53 | , mf.physical_name 54 | , divfs.num_of_bytes_read 55 | , divfs.num_of_bytes_written 56 | , divfs.num_of_reads 57 | , divfs.num_of_writes 58 | , GETDATE() AS currentlinedate 59 | FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS divfs 60 | INNER JOIN sys.master_files AS mf ON mf.database_id = divfs.database_id AND mf.file_id = divfs.file_id 61 | ) 62 | SELECT database_name 63 | , datafile_type 64 | , num_of_bytes_read_persec = SUM(num_of_bytes_read_persec) 65 | , num_of_bytes_written_persec = SUM(num_of_bytes_written_persec) 66 | , num_of_reads_persec = SUM(num_of_reads_persec) 67 | , num_of_writes_persec = SUM(num_of_writes_persec) 68 | INTO #baselinewritten 69 | FROM ( 70 | SELECT 71 | database_name = currentLine.databaseName 72 | , datafile_type = type_desc 73 | , num_of_bytes_read_persec = (currentLine.num_of_bytes_read - T1.num_of_bytes_read) / (DATEDIFF(SECOND,baselinedate,currentlinedate)) 74 | , num_of_bytes_written_persec = (currentLine.num_of_bytes_written - T1.num_of_bytes_written) / (DATEDIFF(SECOND,baselinedate,currentlinedate)) 75 | , num_of_reads_persec = (currentLine.num_of_reads - T1.num_of_reads) / (DATEDIFF(SECOND,baselinedate,currentlinedate)) 76 | , num_of_writes_persec = (currentLine.num_of_writes - T1.num_of_writes) / (DATEDIFF(SECOND,baselinedate,currentlinedate)) 77 | FROM currentLine 78 | INNER JOIN #baseline T1 ON T1.databaseName = currentLine.databaseName AND T1.physical_name = currentLine.physical_name 79 | ) as T 80 | GROUP BY database_name, datafile_type 81 | 82 | SELECT 'db_io_log_writes_bytes_per_sec_' + V.database_name, 0 + V.num_of_bytes_written_persec FROM ( SELECT database_name, num_of_bytes_written_persec FROM #baselinewritten WHERE datafile_type = 'LOG' ) as V 83 | UNION ALL 84 | SELECT 'db_io_log_writes_bytes_per_sec_total', SUM(V.num_of_bytes_written_persec) FROM ( SELECT num_of_bytes_written_persec FROM #baselinewritten WHERE datafile_type = 'LOG' ) as V 85 | UNION ALL 86 | SELECT 'db_io_rows_writes_bytes_per_sec_' + V.database_name, 0 + V.num_of_bytes_written_persec FROM ( SELECT database_name, num_of_bytes_written_persec FROM #baselinewritten WHERE datafile_type = 'ROWS' ) as V 87 | UNION ALL 88 | SELECT 'db_io_rows_writes_bytes_per_sec_total', SUM(V.num_of_bytes_written_persec) FROM ( SELECT num_of_bytes_written_persec FROM #baselinewritten WHERE datafile_type = 'ROWS' ) as V 89 | UNION ALL 90 | SELECT 'db_io_log_reads_bytes_per_sec_' + V.database_name, 0 + V.num_of_bytes_read_persec FROM ( SELECT database_name, num_of_bytes_read_persec FROM #baselinewritten WHERE datafile_type = 'LOG' ) as V 91 | UNION ALL 92 | SELECT 'db_io_log_reads_bytes_per_sec_total', SUM(V.num_of_bytes_read_persec) FROM ( SELECT num_of_bytes_read_persec FROM #baselinewritten WHERE datafile_type = 'LOG' ) as V 93 | UNION ALL 94 | SELECT 'db_io_rows_reads_bytes_per_sec_' + V.database_name, 0 + V.num_of_bytes_read_persec FROM ( SELECT database_name, num_of_bytes_read_persec FROM #baselinewritten WHERE datafile_type = 'ROWS' ) as V 95 | UNION ALL 96 | SELECT 'db_io_rows_reads_bytes_per_sec_total', SUM(V.num_of_bytes_read_persec) FROM ( SELECT num_of_bytes_read_persec FROM #baselinewritten WHERE datafile_type = 'ROWS' ) as V 97 | UNION ALL 98 | SELECT 'db_io_log_writes_per_sec_' + V.database_name, 0 + V.num_of_writes_persec FROM ( SELECT database_name, num_of_writes_persec FROM #baselinewritten WHERE datafile_type = 'LOG' ) as V 99 | UNION ALL 100 | SELECT 'db_io_log_writes_per_sec_total', SUM(V.num_of_writes_persec) FROM ( SELECT num_of_writes_persec FROM #baselinewritten WHERE datafile_type = 'LOG' ) as V 101 | UNION ALL 102 | SELECT 'db_io_rows_writes_per_sec_' + V.database_name, 0 + V.num_of_writes_persec FROM ( SELECT database_name, num_of_writes_persec FROM #baselinewritten WHERE datafile_type = 'ROWS' ) as V 103 | UNION ALL 104 | SELECT 'db_io_rows_writes_per_sec_total', SUM(V.num_of_writes_persec) FROM ( SELECT num_of_writes_persec FROM #baselinewritten WHERE datafile_type = 'ROWS' ) as V 105 | UNION ALL 106 | SELECT 'db_io_log_reads_per_sec_' + V.database_name, 0 + V.num_of_reads_persec FROM ( SELECT database_name, num_of_reads_persec FROM #baselinewritten WHERE datafile_type = 'LOG' ) as V 107 | UNION ALL 108 | SELECT 'db_io_log_reads_per_sec_total', SUM(V.num_of_reads_persec) FROM ( SELECT num_of_reads_persec FROM #baselinewritten WHERE datafile_type = 'LOG' ) as V 109 | UNION ALL 110 | SELECT 'db_io_rows_reads_per_sec_' + V.database_name, 0 + V.num_of_reads_persec FROM ( SELECT database_name, num_of_reads_persec FROM #baselinewritten WHERE datafile_type = 'ROWS' ) as V 111 | UNION ALL 112 | SELECT 'db_io_rows_reads_per_sec_total', SUM(V.num_of_reads_persec) FROM ( SELECT num_of_reads_persec FROM #baselinewritten WHERE datafile_type = 'ROWS' ) as V" 113 | ] 114 | -------------------------------------------------------------------------------- /go-zabbix-mssql.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "flag" 6 | "fmt" 7 | "github.com/AlekSi/zabbix-sender" 8 | _ "github.com/denisenkom/go-mssqldb" 9 | "gopkg.in/yaml.v2" 10 | "io/ioutil" 11 | "log" 12 | "net" 13 | "path/filepath" 14 | "regexp" 15 | ) 16 | 17 | func main() { 18 | var ( 19 | userid = flag.String("U", "", "login for MS SQL connection") 20 | password = flag.String("P", "", "password for MS SQL connection") 21 | server = flag.String("S", "", "server_name[\\instance_name]") 22 | zServer = flag.String("Z", "", "zabbix-server name with proper trapper items configured") 23 | zHost = flag.String("H", "", "hostname as it presented in Zabbix") 24 | confFile = flag.String("F", "", "full path to config YAML file") 25 | ) 26 | flag.Parse() 27 | 28 | dsn := "server=" + *server + ";user id=" + *userid + ";password=" + *password 29 | 30 | db, err := sql.Open("mssql", dsn) 31 | if err != nil { 32 | fmt.Println("Cannot connect: ", err.Error()) 33 | return 34 | } 35 | 36 | err = db.Ping() 37 | if err != nil { 38 | fmt.Println("Cannot connect: ", err.Error()) 39 | return 40 | } 41 | 42 | defer db.Close() 43 | 44 | // Reading config from YAML 45 | type Config struct { 46 | Metrics []string `yaml:"sql_metrics"` 47 | } 48 | 49 | filename, _ := filepath.Abs(*confFile) 50 | yamlFile, err := ioutil.ReadFile(filename) 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | var config Config 56 | 57 | err = yaml.Unmarshal(yamlFile, &config) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | concatQuery := `` 63 | longQueriesResult := make(map[string]interface{}) 64 | for i := 0; i < len(config.Metrics); i++ { 65 | if isML(config.Metrics[i]) == false { 66 | if concatQuery != `` { 67 | concatQuery += "\nUNION ALL\n" 68 | } 69 | concatQuery += config.Metrics[i] 70 | } else { 71 | for k, v := range dbQuery(db, config.Metrics[i]) { 72 | longQueriesResult[k] = v 73 | } 74 | } 75 | } 76 | 77 | resultQuery := dbQuery(db, concatQuery) 78 | for k, v := range longQueriesResult { 79 | resultQuery[k] = v 80 | } 81 | 82 | data := resultQuery 83 | di := zabbix_sender.MakeDataItems(data, *zHost) 84 | addr, _ := net.ResolveTCPAddr("tcp", *zServer+":10051") 85 | res, _ := zabbix_sender.Send(addr, di) 86 | fmt.Println(res.Failed) // 87 | } 88 | 89 | func dbQuery(db *sql.DB, cmd string) map[string]interface{} { 90 | var ( 91 | name string 92 | val float64 93 | ) 94 | 95 | rows, err := db.Query(cmd) 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | 100 | defer rows.Close() 101 | 102 | pipisa := make(map[string]interface{}) 103 | 104 | for rows.Next() { 105 | err = rows.Scan(&name, &val) 106 | if err != nil { 107 | log.Fatal(err) 108 | } 109 | pipisa[name] = val 110 | } 111 | 112 | err = rows.Err() 113 | if err != nil { 114 | log.Fatal(err) 115 | } 116 | 117 | return pipisa 118 | } 119 | 120 | func isML(val string) bool { // returns TRUE if *val (supposed to be query string from config) contains multiple lines 121 | match, _ := regexp.MatchString("[dD][eE][cC][lL][aA][rR][eE]\\s", val) // used regex pattern for DECLARE 122 | return match 123 | } 124 | --------------------------------------------------------------------------------