diff --git a/README.md b/README.md index 0702d6b4d..b579cdd81 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,7 @@ For documentation on the latest development code see the [documentation index][d * [snmp_trap](./plugins/inputs/snmp_trap) * [socket_listener](./plugins/inputs/socket_listener) * [solr](./plugins/inputs/solr) +* [sql](./plugins/inputs/sql) (generic SQL query plugin) * [sql server](./plugins/inputs/sqlserver) (microsoft) * [stackdriver](./plugins/inputs/stackdriver) (Google Cloud Monitoring) * [sql](./plugins/outputs/sql) (SQL generic output) diff --git a/docs/SQL_DRIVERS_INPUT.md b/docs/SQL_DRIVERS_INPUT.md new file mode 100644 index 000000000..81049fcee --- /dev/null +++ b/docs/SQL_DRIVERS_INPUT.md @@ -0,0 +1,43 @@ +# Available SQL drivers for the SQL input plugin + +This is a list of available drivers for the SQL input plugin. The data-source-name (DSN) is driver specific and +might change between versions. Please check the driver documentation for available options and the format. + +database | driver | aliases | example DSN | comment +---------------------| ------------------------------------------------------| --------------- | -------------------------------------------------------------------------------------- | ------- +CockroachDB | [cockroach](https://github.com/jackc/pgx) | postgres
pgx | see _postgres_ driver | uses PostgresQL driver +MariaDB | [maria](https://github.com/go-sql-driver/mysql) | mysql | see _mysql_ driver | uses MySQL driver +Microsoft SQL Server | [sqlserver](https://github.com/denisenkom/go-mssqldb) | mssql | `username:password@host/instance?param1=value¶m2=value` | uses newer _sqlserver_ driver +MySQL | [mysql](https://github.com/go-sql-driver/mysql) | | `[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]` | see [driver docs](https://github.com/go-sql-driver/mysql) for more information +PostgreSQL | [postgres](https://github.com/jackc/pgx) | pgx | `[user[:password]@][netloc][:port][,...][/dbname][?param1=value1&...]` | see [postgres docs](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING) for more information +SQLite | [sqlite](https://gitlab.com/cznic/sqlite) | | `filename` | see [driver docu](https://pkg.go.dev/modernc.org/sqlite) for more information +TiDB | [tidb](https://github.com/go-sql-driver/mysql) | mysql | see _mysql_ driver | uses MySQL driver + +## Comments + +### Driver aliases +Some database drivers are supported though another driver (e.g. CockroachDB). For other databases we provide a more +obvious name (e.g. postgres) compared to the driver name. For all of those drivers you might use an _alias_ name +during configuration. + +### Example data-source-name DSN +The given examples are just that, so please check the driver documentation for the exact format +and available options and parameters. Please note that the format of a DSN might also change +between driver version. + +### Type conversions +Telegraf relies on type conversion of the database driver and/or the golang sql framework. In case you find +any problem, please open an issue! + +## Help +If nothing seems to work, you might find help in the telegraf forum or in the chat. + +### The documentation is wrong +Please open an issue or even better send a pull-request! + +### I found a bug +Please open an issue or even better send a pull-request! + +### My database is not supported +We currently cannot support CGO drivers in telegraf! Please check if a **pure Go** driver for the [golang sql framework](https://golang.org/pkg/database/sql/) exists. +If you found such a driver, please let us know by opening an issue or even better by sending a pull-request! diff --git a/filter/filter.go b/filter/filter.go index 29fcb8c4f..984fa3ed0 100644 --- a/filter/filter.go +++ b/filter/filter.go @@ -79,13 +79,24 @@ func compileFilterNoGlob(filters []string) Filter { } type IncludeExcludeFilter struct { - include Filter - exclude Filter + include Filter + exclude Filter + includeDefault bool + excludeDefault bool } func NewIncludeExcludeFilter( include []string, exclude []string, +) (Filter, error) { + return NewIncludeExcludeFilterDefaults(include, exclude, true, false) +} + +func NewIncludeExcludeFilterDefaults( + include []string, + exclude []string, + includeDefault bool, + excludeDefault bool, ) (Filter, error) { in, err := Compile(include) if err != nil { @@ -97,7 +108,7 @@ func NewIncludeExcludeFilter( return nil, err } - return &IncludeExcludeFilter{in, ex}, nil + return &IncludeExcludeFilter{in, ex, includeDefault, excludeDefault}, nil } func (f *IncludeExcludeFilter) Match(s string) bool { @@ -105,12 +116,17 @@ func (f *IncludeExcludeFilter) Match(s string) bool { if !f.include.Match(s) { return false } + } else if !f.includeDefault { + return false } if f.exclude != nil { if f.exclude.Match(s) { return false } + } else if f.excludeDefault { + return false } + return true } diff --git a/go.sum b/go.sum index f4184b3ce..bd20f2842 100644 --- a/go.sum +++ b/go.sum @@ -110,23 +110,39 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go github.com/Mellanox/rdmamap v0.0.0-20191106181932-7c3c4763a6ee h1:atI/FFjXh6hIVlPE1Jup9m8N4B9q/OSbMUe2EBahs+w= github.com/Mellanox/rdmamap v0.0.0-20191106181932-7c3c4763a6ee/go.mod h1:jDA6v0TUYrFEIAE5uGJ29LQOeONIgMdP4Rkqb8HUnPM= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3 h1:mw6pDQqv38/WGF1cO/jF5t/jyAJ2yi7CmtFLLO5tGFI= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3 h1:mw6pDQqv38/WGF1cO/jF5t/jyAJ2yi7CmtFLLO5tGFI= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16 h1:8/auA4LFIZFTGrqfKhGBSXwM6/4X1fHa/xniyEHu8ac= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -246,8 +262,11 @@ github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= @@ -257,6 +276,8 @@ github.com/caio/go-tdigest v3.1.0+incompatible h1:uoVMJ3Q5lXmVLCCqaMGHLBWnbGoN6L github.com/caio/go-tdigest v3.1.0+incompatible/go.mod h1:sHQM/ubZStBUmF1WbB8FAm8q9GjDajLC5T7ydxE3JHI= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -287,12 +308,14 @@ github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABA github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68 h1:hkGVFjz+plgr5UfxZUTPFbUFIF/Km6/s+RVRIRHLrrY= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= @@ -309,14 +332,17 @@ github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go. github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4 h1:zjz4MOAOFgdBlwid2nNUlJ3YLpVi/97L36lfMYJex60= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e h1:6JKvHHt396/qabvMhnhUZvWaHZzfVfldxE60TK8YLhg= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= @@ -330,9 +356,11 @@ github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= @@ -405,16 +433,23 @@ github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/ github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v17.12.0-ce-rc1.0.20200706150819-a40b877fbb9e+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.6+incompatible h1:oXI3Vas8TI8Eu/EjH4srKHJBVqraSzJybhxY7Om9faQ= github.com/docker/docker v20.10.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= @@ -427,6 +462,7 @@ github.com/dropbox/godropbox v0.0.0-20180512210157-31879d3884b9/go.mod h1:glr97h github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dynatrace-oss/dynatrace-metric-utils-go v0.1.0 h1:ldKn47mFgWCoiJRXA32psdEACPKffX9O1Msh1K8M+f0= github.com/dynatrace-oss/dynatrace-metric-utils-go v0.1.0/go.mod h1:qw0E9EJ0PnSlhWawDNuqE0zhc1hqOBUCFIAj3dd9DNw= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -614,6 +650,7 @@ github.com/gogo/googleapis v1.3.1/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9 github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -885,6 +922,7 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -1045,11 +1083,16 @@ github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hx github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ= github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ= github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM= +github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM= github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM= +github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= @@ -1107,6 +1150,7 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1131,17 +1175,30 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc93 h1:x2UMpOOVf3kQ8arv/EsDGwim8PTNqzL1/EYDr/+scOM= github.com/opencontainers/runc v1.0.0-rc93 h1:x2UMpOOVf3kQ8arv/EsDGwim8PTNqzL1/EYDr/+scOM= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= @@ -1230,6 +1287,7 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/prometheus v1.8.2-0.20200911110723-e83ef207b6c2 h1:IB/5RJRcJiR/YzKs4Aou86s/RaMepZOZVCArYNHJHWc= @@ -1296,6 +1354,7 @@ github.com/signalfx/sapm-proto v0.4.0/go.mod h1:x3gtwJ1GRejtkghB4nYpwixh2zqJrLbP github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -1307,6 +1366,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -1330,6 +1391,7 @@ github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bd github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= @@ -1586,6 +1648,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1650,6 +1713,8 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1678,6 +1743,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200821140526-fda516888d29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200821140526-fda516888d29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1689,11 +1755,15 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/internal.go b/internal/internal.go index 055ea361c..4441e9acf 100644 --- a/internal/internal.go +++ b/internal/internal.go @@ -297,8 +297,25 @@ func parseComponents(timestamp interface{}) (int64, int64, error) { return 0, 0, err } return integer, 0, nil + case int8: + return int64(ts), 0, nil + case int16: + return int64(ts), 0, nil + case int32: + return int64(ts), 0, nil case int64: return ts, 0, nil + case uint8: + return int64(ts), 0, nil + case uint16: + return int64(ts), 0, nil + case uint32: + return int64(ts), 0, nil + case uint64: + return int64(ts), 0, nil + case float32: + integer, fractional := math.Modf(float64(ts)) + return int64(integer), int64(fractional * 1e9), nil case float64: integer, fractional := math.Modf(ts) return int64(integer), int64(fractional * 1e9), nil diff --git a/internal/type_conversions.go b/internal/type_conversions.go new file mode 100644 index 000000000..ed4ed374a --- /dev/null +++ b/internal/type_conversions.go @@ -0,0 +1,198 @@ +package internal + +import ( + "fmt" + "strconv" +) + +func ToString(value interface{}) (string, error) { + switch v := value.(type) { + case string: + return v, nil + case []byte: + return string(v), nil + case int: + return strconv.FormatInt(int64(v), 10), nil + case int8: + return strconv.FormatInt(int64(v), 10), nil + case int16: + return strconv.FormatInt(int64(v), 10), nil + case int32: + return strconv.FormatInt(int64(v), 10), nil + case int64: + return strconv.FormatInt(v, 10), nil + case uint: + return strconv.FormatUint(uint64(v), 10), nil + case uint8: + return strconv.FormatUint(uint64(v), 10), nil + case uint16: + return strconv.FormatUint(uint64(v), 10), nil + case uint32: + return strconv.FormatUint(uint64(v), 10), nil + case uint64: + return strconv.FormatUint(v, 10), nil + case float32: + return strconv.FormatFloat(float64(v), 'f', -1, 32), nil + case float64: + return strconv.FormatFloat(v, 'f', -1, 64), nil + case bool: + return strconv.FormatBool(v), nil + case fmt.Stringer: + return v.String(), nil + case nil: + return "", nil + } + return "", fmt.Errorf("type \"%T\" unsupported", value) +} + +func ToFloat64(value interface{}) (float64, error) { + switch v := value.(type) { + case string: + return strconv.ParseFloat(v, 64) + case []byte: + return strconv.ParseFloat(string(v), 64) + case fmt.Stringer: + return strconv.ParseFloat(v.String(), 64) + case int: + return float64(v), nil + case int8: + return float64(v), nil + case int16: + return float64(v), nil + case int32: + return float64(v), nil + case int64: + return float64(v), nil + case uint: + return float64(v), nil + case uint8: + return float64(v), nil + case uint16: + return float64(v), nil + case uint32: + return float64(v), nil + case uint64: + return float64(v), nil + case float32: + return float64(v), nil + case float64: + return v, nil + case nil: + return 0, nil + } + return 0, fmt.Errorf("type \"%T\" unsupported", value) +} + +func ToInt64(value interface{}) (int64, error) { + switch v := value.(type) { + case string: + return strconv.ParseInt(v, 10, 64) + case []byte: + return strconv.ParseInt(string(v), 10, 64) + case fmt.Stringer: + return strconv.ParseInt(v.String(), 10, 64) + case int: + return int64(v), nil + case int8: + return int64(v), nil + case int16: + return int64(v), nil + case int32: + return int64(v), nil + case int64: + return v, nil + case uint: + return int64(v), nil + case uint8: + return int64(v), nil + case uint16: + return int64(v), nil + case uint32: + return int64(v), nil + case uint64: + return int64(v), nil + case float32: + return int64(v), nil + case float64: + return int64(v), nil + case nil: + return 0, nil + } + return 0, fmt.Errorf("type \"%T\" unsupported", value) +} + +func ToUint64(value interface{}) (uint64, error) { + switch v := value.(type) { + case string: + return strconv.ParseUint(v, 10, 64) + case []byte: + return strconv.ParseUint(string(v), 10, 64) + case fmt.Stringer: + return strconv.ParseUint(v.String(), 10, 64) + case int: + return uint64(v), nil + case int8: + return uint64(v), nil + case int16: + return uint64(v), nil + case int32: + return uint64(v), nil + case int64: + return uint64(v), nil + case uint: + return uint64(v), nil + case uint8: + return uint64(v), nil + case uint16: + return uint64(v), nil + case uint32: + return uint64(v), nil + case uint64: + return v, nil + case float32: + return uint64(v), nil + case float64: + return uint64(v), nil + case nil: + return 0, nil + } + return 0, fmt.Errorf("type \"%T\" unsupported", value) +} + +func ToBool(value interface{}) (bool, error) { + switch v := value.(type) { + case string: + return strconv.ParseBool(v) + case []byte: + return strconv.ParseBool(string(v)) + case fmt.Stringer: + return strconv.ParseBool(v.String()) + case int: + return v > 0, nil + case int8: + return v > 0, nil + case int16: + return v > 0, nil + case int32: + return v > 0, nil + case int64: + return v > 0, nil + case uint: + return v > 0, nil + case uint8: + return v > 0, nil + case uint16: + return v > 0, nil + case uint32: + return v > 0, nil + case uint64: + return v > 0, nil + case float32: + return v > 0, nil + case float64: + return v > 0, nil + case nil: + return false, nil + } + return false, fmt.Errorf("type \"%T\" unsupported", value) +} diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index aa273a4aa..7c4e0bcf4 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -167,6 +167,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/snmp_trap" _ "github.com/influxdata/telegraf/plugins/inputs/socket_listener" _ "github.com/influxdata/telegraf/plugins/inputs/solr" + _ "github.com/influxdata/telegraf/plugins/inputs/sql" _ "github.com/influxdata/telegraf/plugins/inputs/sqlserver" _ "github.com/influxdata/telegraf/plugins/inputs/stackdriver" _ "github.com/influxdata/telegraf/plugins/inputs/statsd" diff --git a/plugins/inputs/sql/README.md b/plugins/inputs/sql/README.md new file mode 100644 index 000000000..9c002df18 --- /dev/null +++ b/plugins/inputs/sql/README.md @@ -0,0 +1,153 @@ +# SQL Input Plugin + +This plugin reads metrics from performing SQL queries against a SQL server. Different server +types are supported and their settings might differ (especially the connection parameters). +Please check the list of [supported SQL drivers](../../../docs/SQL_DRIVERS_INPUT.md) for the +`driver` name and options for the data-source-name (`dsn`) options. + +### Configuration + +This section contains the default TOML to configure the plugin. You can +generate it using `telegraf --usage `. + +```toml +[[inputs.sql]] + ## Database Driver + ## See https://github.com/influxdata/telegraf/blob/master/docs/SQL_DRIVERS_INPUT.md for + ## a list of supported drivers. + driver = "mysql" + + ## Data source name for connecting + ## The syntax and supported options depends on selected driver. + dsn = "username:password@mysqlserver:3307/dbname?param=value" + + ## Timeout for any operation + # timeout = "5s" + + ## Connection time limits + ## By default the maximum idle time and maximum lifetime of a connection is unlimited, i.e. the connections + ## will not be closed automatically. If you specify a positive time, the connections will be closed after + ## idleing or existing for at least that amount of time, respectively. + # connection_max_idle_time = "0s" + # connection_max_life_time = "0s" + + ## Connection count limits + ## By default the number of open connections is not limited and the number of maximum idle connections + ## will be inferred from the number of queries specified. If you specify a positive number for any of the + ## two options, connections will be closed when reaching the specified limit. The number of idle connections + ## will be clipped to the maximum number of connections limit if any. + # connection_max_open = 0 + # connection_max_idle = auto + + [[inputs.sql.query]] + ## Query to perform on the server + query="SELECT user,state,latency,score FROM Scoreboard WHERE application > 0" + ## Alternatively to specifying the query directly you can select a file here containing the SQL query. + ## Only one of 'query' and 'query_script' can be specified! + # query_script = "/path/to/sql/script.sql" + + ## Name of the measurement + ## In case both measurement and 'measurement_col' are given, the latter takes precedence. + # measurement = "sql" + + ## Column name containing the name of the measurement + ## If given, this will take precedence over the 'measurement' setting. In case a query result + ## does not contain the specified column, we fall-back to the 'measurement' setting. + # measurement_column = "" + + ## Column name containing the time of the measurement + ## If ommited, the time of the query will be used. + # time_column = "" + + ## Format of the time contained in 'time_col' + ## The time must be 'unix', 'unix_ms', 'unix_us', 'unix_ns', or a golang time format. + ## See https://golang.org/pkg/time/#Time.Format for details. + # time_format = "unix" + + ## Column names containing tags + ## An empty include list will reject all columns and an empty exclude list will not exclude any column. + ## I.e. by default no columns will be returned as tag and the tags are empty. + # tag_columns_include = [] + # tag_columns_exclude = [] + + ## Column names containing fields (explicit types) + ## Convert the given columns to the corresponding type. Explicit type conversions take precedence over + ## the automatic (driver-based) conversion below. + ## NOTE: Columns should not be specified for multiple types or the resulting type is undefined. + # field_columns_float = [] + # field_columns_int = [] + # field_columns_uint = [] + # field_columns_bool = [] + # field_columns_string = [] + + ## Column names containing fields (automatic types) + ## An empty include list is equivalent to '[*]' and all returned columns will be accepted. An empty + ## exclude list will not exclude any column. I.e. by default all columns will be returned as fields. + ## NOTE: We rely on the database driver to perform automatic datatype conversion. + # field_columns_include = [] + # field_columns_exclude = [] +``` + +### Options +#### Driver +The `driver` and `dsn` options specify how to connect to the database. As especially the `dsn` format and +values vary with the `driver` refer to the list of [supported SQL drivers](../../../docs/SQL_DRIVERS_INPUT.md) for possible values and more details. + +#### Connection limits +With these options you can limit the number of connections kept open by this plugin. Details about the exact +workings can be found in the [golang sql documentation](https://golang.org/pkg/database/sql/#DB.SetConnMaxIdleTime). + +#### Query sections +Multiple `query` sections can be specified for this plugin. Each specified query will first be prepared on the server +and then executed in every interval using the column mappings specified. Please note that `tag` and `field` columns +are not exclusive, i.e. a column can be added to both. When using both `include` and `exclude` lists, the `exclude` +list takes precedence over the `include` list. I.e. given you specify `foo` in both lists, `foo` will _never_ pass +the filter. In case any the columns specified in `measurement_col` or `time_col` are _not_ returned by the query, +the plugin falls-back to the documented defaults. Fields or tags specified in the includes of the options but missing +in the returned query are silently ignored. + +### Types +This plugin relies on the driver to do the type conversion. For the different properties of the metric the following +types are accepted. + +#### Measurement +Only columns of type `string` are accepted. + +#### Time +For the metric time columns of type `time` are accepted directly. For numeric columns, `time_format` should be set +to any of `unix`, `unix_ms`, `unix_ns` or `unix_us` accordingly. By default the a timestamp in `unix` format is +expected. For string columns, please specify the `time_format` accordingly. +See the [golang time documentation](https://golang.org/pkg/time/#Time.Format) for details. + +#### Tags +For tags columns with textual values (`string` and `bytes`), signed and unsigned integers (8, 16, 32 and 64 bit), +floating-point (32 and 64 bit), `boolean` and `time` values are accepted. Those values will be converted to string. + +#### Fields +For fields columns with textual values (`string` and `bytes`), signed and unsigned integers (8, 16, 32 and 64 bit), +floating-point (32 and 64 bit), `boolean` and `time` values are accepted. Here `bytes` will be converted to `string`, +signed and unsigned integer values will be converted to `int64` or `uint64` respectively. Floating-point values are converted to `float64` and `time` is converted to a nanosecond timestamp of type `int64`. + +### Example Output +Using the [MariaDB sample database](https://www.mariadbtutorial.com/getting-started/mariadb-sample-database) and the +configuration +```toml +[[inputs.sql]] + driver = "mysql" + dsn = "root:password@/nation" + + [[inputs.sql.query]] + query="SELECT * FROM guests" + measurement = "nation" + tag_cols_include = ["name"] + field_cols_exclude = ["name"] +``` + +Telegraf will output the following metrics +``` +nation,host=Hugin,name=John guest_id=1i 1611332164000000000 +nation,host=Hugin,name=Jane guest_id=2i 1611332164000000000 +nation,host=Hugin,name=Jean guest_id=3i 1611332164000000000 +nation,host=Hugin,name=Storm guest_id=4i 1611332164000000000 +nation,host=Hugin,name=Beast guest_id=5i 1611332164000000000 +``` diff --git a/plugins/inputs/sql/drivers.go b/plugins/inputs/sql/drivers.go new file mode 100644 index 000000000..09af9bfc8 --- /dev/null +++ b/plugins/inputs/sql/drivers.go @@ -0,0 +1,8 @@ +package sql + +import ( + // Blank imports to register the drivers + _ "github.com/denisenkom/go-mssqldb" + _ "github.com/go-sql-driver/mysql" + _ "github.com/jackc/pgx/v4/stdlib" +) diff --git a/plugins/inputs/sql/drivers_sqlite.go b/plugins/inputs/sql/drivers_sqlite.go new file mode 100644 index 000000000..4c9e56a8c --- /dev/null +++ b/plugins/inputs/sql/drivers_sqlite.go @@ -0,0 +1,8 @@ +// +build linux,freebsd +// +build !mips !mips64 + +package sql + +import ( + _ "modernc.org/sqlite" +) diff --git a/plugins/inputs/sql/sql.go b/plugins/inputs/sql/sql.go new file mode 100644 index 000000000..383f04c40 --- /dev/null +++ b/plugins/inputs/sql/sql.go @@ -0,0 +1,542 @@ +package sql + +import ( + "context" + dbsql "database/sql" + "errors" + "fmt" + "io/ioutil" + "sort" + "strings" + "sync" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/config" + "github.com/influxdata/telegraf/filter" + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/internal/choice" + "github.com/influxdata/telegraf/plugins/inputs" +) + +const sampleConfig = ` + ## Database Driver + ## See https://github.com/influxdata/telegraf/blob/master/docs/SQL_DRIVERS_INPUT.md for + ## a list of supported drivers. + driver = "mysql" + + ## Data source name for connecting + ## The syntax and supported options depends on selected driver. + dsn = "username:password@mysqlserver:3307/dbname?param=value" + + ## Timeout for any operation + # timeout = "5s" + + ## Connection time limits + ## By default the maximum idle time and maximum lifetime of a connection is unlimited, i.e. the connections + ## will not be closed automatically. If you specify a positive time, the connections will be closed after + ## idleing or existing for at least that amount of time, respectively. + # connection_max_idle_time = "0s" + # connection_max_life_time = "0s" + + ## Connection count limits + ## By default the number of open connections is not limited and the number of maximum idle connections + ## will be inferred from the number of queries specified. If you specify a positive number for any of the + ## two options, connections will be closed when reaching the specified limit. The number of idle connections + ## will be clipped to the maximum number of connections limit if any. + # connection_max_open = 0 + # connection_max_idle = auto + + [[inputs.sql.query]] + ## Query to perform on the server + query="SELECT user,state,latency,score FROM Scoreboard WHERE application > 0" + ## Alternatively to specifying the query directly you can select a file here containing the SQL query. + ## Only one of 'query' and 'query_script' can be specified! + # query_script = "/path/to/sql/script.sql" + + ## Name of the measurement + ## In case both measurement and 'measurement_col' are given, the latter takes precedence. + # measurement = "sql" + + ## Column name containing the name of the measurement + ## If given, this will take precedence over the 'measurement' setting. In case a query result + ## does not contain the specified column, we fall-back to the 'measurement' setting. + # measurement_column = "" + + ## Column name containing the time of the measurement + ## If ommited, the time of the query will be used. + # time_column = "" + + ## Format of the time contained in 'time_col' + ## The time must be 'unix', 'unix_ms', 'unix_us', 'unix_ns', or a golang time format. + ## See https://golang.org/pkg/time/#Time.Format for details. + # time_format = "unix" + + ## Column names containing tags + ## An empty include list will reject all columns and an empty exclude list will not exclude any column. + ## I.e. by default no columns will be returned as tag and the tags are empty. + # tag_columns_include = [] + # tag_columns_exclude = [] + + ## Column names containing fields (explicit types) + ## Convert the given columns to the corresponding type. Explicit type conversions take precedence over + ## the automatic (driver-based) conversion below. + ## NOTE: Columns should not be specified for multiple types or the resulting type is undefined. + # field_columns_float = [] + # field_columns_int = [] + # field_columns_uint = [] + # field_columns_bool = [] + # field_columns_string = [] + + ## Column names containing fields (automatic types) + ## An empty include list is equivalent to '[*]' and all returned columns will be accepted. An empty + ## exclude list will not exclude any column. I.e. by default all columns will be returned as fields. + ## NOTE: We rely on the database driver to perform automatic datatype conversion. + # field_columns_include = [] + # field_columns_exclude = [] +` + +const magicIdleCount int = (-int(^uint(0) >> 1)) + +type Query struct { + Query string `toml:"query"` + Script string `toml:"query_script"` + Measurement string `toml:"measurement"` + MeasurementColumn string `toml:"measurement_column"` + TimeColumn string `toml:"time_column"` + TimeFormat string `toml:"time_format"` + TagColumnsInclude []string `toml:"tag_columns_include"` + TagColumnsExclude []string `toml:"tag_columns_exclude"` + FieldColumnsInclude []string `toml:"field_columns_include"` + FieldColumnsExclude []string `toml:"field_columns_exclude"` + FieldColumnsFloat []string `toml:"field_columns_float"` + FieldColumnsInt []string `toml:"field_columns_int"` + FieldColumnsUint []string `toml:"field_columns_uint"` + FieldColumnsBool []string `toml:"field_columns_bool"` + FieldColumnsString []string `toml:"field_columns_string"` + + statement *dbsql.Stmt + tagFilter filter.Filter + fieldFilter filter.Filter + fieldFilterFloat filter.Filter + fieldFilterInt filter.Filter + fieldFilterUint filter.Filter + fieldFilterBool filter.Filter + fieldFilterString filter.Filter +} + +func (q *Query) parse(ctx context.Context, acc telegraf.Accumulator, rows *dbsql.Rows, t time.Time) (int, error) { + columnNames, err := rows.Columns() + if err != nil { + return 0, err + } + + // Prepare the list of datapoints according to the received row + columnData := make([]interface{}, len(columnNames)) + columnDataPtr := make([]interface{}, len(columnNames)) + + for i := range columnData { + columnDataPtr[i] = &columnData[i] + } + + rowCount := 0 + for rows.Next() { + measurement := q.Measurement + timestamp := t + tags := make(map[string]string) + fields := make(map[string]interface{}, len(columnNames)) + + // Do the parsing with (hopefully) automatic type conversion + if err := rows.Scan(columnDataPtr...); err != nil { + return 0, err + } + + for i, name := range columnNames { + if q.MeasurementColumn != "" && name == q.MeasurementColumn { + var ok bool + if measurement, ok = columnData[i].(string); !ok { + return 0, fmt.Errorf("measurement column type \"%T\" unsupported", columnData[i]) + } + } + + if q.TimeColumn != "" && name == q.TimeColumn { + var fieldvalue interface{} + var skipParsing bool + + switch v := columnData[i].(type) { + case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: + fieldvalue = v + case []byte: + fieldvalue = string(v) + case time.Time: + timestamp = v + skipParsing = true + case fmt.Stringer: + fieldvalue = v.String() + default: + return 0, fmt.Errorf("time column %q of type \"%T\" unsupported", name, columnData[i]) + } + if !skipParsing { + if timestamp, err = internal.ParseTimestamp(q.TimeFormat, fieldvalue, ""); err != nil { + return 0, fmt.Errorf("parsing time failed: %v", err) + } + } + } + + if q.tagFilter.Match(name) { + tagvalue, err := internal.ToString(columnData[i]) + if err != nil { + return 0, fmt.Errorf("converting tag column %q failed: %v", name, err) + } + if v := strings.TrimSpace(tagvalue); v != "" { + tags[name] = v + } + } + + // Explicit type conversions take precedence + if q.fieldFilterFloat.Match(name) { + v, err := internal.ToFloat64(columnData[i]) + if err != nil { + return 0, fmt.Errorf("converting field column %q to float failed: %v", name, err) + } + fields[name] = v + continue + } + + if q.fieldFilterInt.Match(name) { + v, err := internal.ToInt64(columnData[i]) + if err != nil { + return 0, fmt.Errorf("converting field column %q to int failed: %v", name, err) + } + fields[name] = v + continue + } + + if q.fieldFilterUint.Match(name) { + v, err := internal.ToUint64(columnData[i]) + if err != nil { + return 0, fmt.Errorf("converting field column %q to uint failed: %v", name, err) + } + fields[name] = v + continue + } + + if q.fieldFilterBool.Match(name) { + v, err := internal.ToBool(columnData[i]) + if err != nil { + return 0, fmt.Errorf("converting field column %q to bool failed: %v", name, err) + } + fields[name] = v + continue + } + + if q.fieldFilterString.Match(name) { + v, err := internal.ToString(columnData[i]) + if err != nil { + return 0, fmt.Errorf("converting field column %q to string failed: %v", name, err) + } + fields[name] = v + continue + } + + // Try automatic conversion for all remaining fields + if q.fieldFilter.Match(name) { + var fieldvalue interface{} + switch v := columnData[i].(type) { + case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool: + fieldvalue = v + case []byte: + fieldvalue = string(v) + case time.Time: + fieldvalue = v.UnixNano() + case nil: + fieldvalue = nil + case fmt.Stringer: + fieldvalue = v.String() + default: + return 0, fmt.Errorf("field column %q of type \"%T\" unsupported", name, columnData[i]) + } + if fieldvalue != nil { + fields[name] = fieldvalue + } + } + } + acc.AddFields(measurement, fields, tags, timestamp) + rowCount++ + } + + if err := rows.Err(); err != nil { + return rowCount, err + } + + return rowCount, nil +} + +type SQL struct { + Driver string `toml:"driver"` + Dsn string `toml:"dsn"` + Timeout config.Duration `toml:"timeout"` + MaxIdleTime config.Duration `toml:"connection_max_idle_time"` + MaxLifetime config.Duration `toml:"connection_max_life_time"` + MaxOpenConnections int `toml:"connection_max_open"` + MaxIdleConnections int `toml:"connection_max_idle"` + Queries []Query `toml:"query"` + Log telegraf.Logger `toml:"-"` + + driverName string + db *dbsql.DB +} + +func (s *SQL) Description() string { + return `Read metrics from SQL queries` +} + +func (s *SQL) SampleConfig() string { + return sampleConfig +} + +func (s *SQL) Init() error { + // Option handling + if s.Driver == "" { + return errors.New("missing SQL driver option") + } + + if s.Dsn == "" { + return errors.New("missing data source name (DSN) option") + } + + if s.Timeout <= 0 { + s.Timeout = config.Duration(5 * time.Second) + } + + if s.MaxIdleConnections == magicIdleCount { + // Determine the number by the number of queries + the golang default value + s.MaxIdleConnections = len(s.Queries) + 2 + } + + for i, q := range s.Queries { + if q.Query == "" && q.Script == "" { + return errors.New("neither 'query' nor 'query_script' specified") + } + + if q.Query != "" && q.Script != "" { + return errors.New("only one of 'query' and 'query_script' can be specified") + } + + // In case we got a script, we should read the query now. + if q.Script != "" { + query, err := ioutil.ReadFile(q.Script) + if err != nil { + return fmt.Errorf("reading script %q failed: %v", q.Script, err) + } + s.Queries[i].Query = string(query) + } + + // Time format + if q.TimeFormat == "" { + s.Queries[i].TimeFormat = "unix" + } + + // Compile the tag-filter + tagfilter, err := filter.NewIncludeExcludeFilterDefaults(q.TagColumnsInclude, q.TagColumnsExclude, false, false) + if err != nil { + return fmt.Errorf("creating tag filter failed: %v", err) + } + s.Queries[i].tagFilter = tagfilter + + // Compile the explicit type field-filter + fieldfilterFloat, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsFloat, nil, false, false) + if err != nil { + return fmt.Errorf("creating field filter for float failed: %v", err) + } + s.Queries[i].fieldFilterFloat = fieldfilterFloat + + fieldfilterInt, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsInt, nil, false, false) + if err != nil { + return fmt.Errorf("creating field filter for int failed: %v", err) + } + s.Queries[i].fieldFilterInt = fieldfilterInt + + fieldfilterUint, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsUint, nil, false, false) + if err != nil { + return fmt.Errorf("creating field filter for uint failed: %v", err) + } + s.Queries[i].fieldFilterUint = fieldfilterUint + + fieldfilterBool, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsBool, nil, false, false) + if err != nil { + return fmt.Errorf("creating field filter for bool failed: %v", err) + } + s.Queries[i].fieldFilterBool = fieldfilterBool + + fieldfilterString, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsString, nil, false, false) + if err != nil { + return fmt.Errorf("creating field filter for string failed: %v", err) + } + s.Queries[i].fieldFilterString = fieldfilterString + + // Compile the field-filter + fieldfilter, err := filter.NewIncludeExcludeFilter(q.FieldColumnsInclude, q.FieldColumnsExclude) + if err != nil { + return fmt.Errorf("creating field filter failed: %v", err) + } + s.Queries[i].fieldFilter = fieldfilter + + if q.Measurement == "" { + s.Queries[i].Measurement = "sql" + } + } + + // Derive the sql-framework driver name from our config name. This abstracts the actual driver + // from the database-type the user wants. + aliases := map[string]string{ + "cockroach": "pgx", + "tidb": "mysql", + "mssql": "sqlserver", + "maria": "mysql", + "postgres": "pgx", + } + s.driverName = s.Driver + if driver, ok := aliases[s.Driver]; ok { + s.driverName = driver + } + + availDrivers := dbsql.Drivers() + if !choice.Contains(s.driverName, availDrivers) { + for d, r := range aliases { + if choice.Contains(r, availDrivers) { + availDrivers = append(availDrivers, d) + } + } + + // Sort the list of drivers and make them unique + sort.Strings(availDrivers) + last := 0 + for _, d := range availDrivers { + if d != availDrivers[last] { + last++ + availDrivers[last] = d + } + } + availDrivers = availDrivers[:last+1] + + return fmt.Errorf("driver %q not supported use one of %v", s.Driver, availDrivers) + } + + return nil +} + +func (s *SQL) Start(_ telegraf.Accumulator) error { + var err error + + // Connect to the database server + s.Log.Debugf("Connecting to %q...", s.Dsn) + s.db, err = dbsql.Open(s.driverName, s.Dsn) + if err != nil { + return err + } + + // Set the connection limits + // s.db.SetConnMaxIdleTime(time.Duration(s.MaxIdleTime)) // Requires go >= 1.15 + s.db.SetConnMaxLifetime(time.Duration(s.MaxLifetime)) + s.db.SetMaxOpenConns(s.MaxOpenConnections) + s.db.SetMaxIdleConns(s.MaxIdleConnections) + + // Test if the connection can be established + s.Log.Debugf("Testing connectivity...") + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout)) + err = s.db.PingContext(ctx) + cancel() + if err != nil { + return fmt.Errorf("connecting to database failed: %v", err) + } + + // Prepare the statements + for i, q := range s.Queries { + s.Log.Debugf("Preparing statement %q...", q.Query) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout)) + stmt, err := s.db.PrepareContext(ctx, q.Query) //nolint:sqlclosecheck // Closed in Stop() + cancel() + if err != nil { + return fmt.Errorf("preparing query %q failed: %v", q.Query, err) + } + s.Queries[i].statement = stmt + } + + return nil +} + +func (s *SQL) Stop() { + // Free the statements + for _, q := range s.Queries { + if q.statement != nil { + if err := q.statement.Close(); err != nil { + s.Log.Errorf("closing statement for query %q failed: %v", q.Query, err) + } + } + } + + // Close the connection to the server + if s.db != nil { + if err := s.db.Close(); err != nil { + s.Log.Errorf("closing database connection failed: %v", err) + } + } +} + +func (s *SQL) Gather(acc telegraf.Accumulator) error { + var wg sync.WaitGroup + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout)) + defer cancel() + + tstart := time.Now() + for _, query := range s.Queries { + wg.Add(1) + + go func(q Query) { + defer wg.Done() + if err := s.executeQuery(ctx, acc, q, tstart); err != nil { + acc.AddError(err) + } + }(query) + } + wg.Wait() + s.Log.Debugf("Executed %d queries in %s", len(s.Queries), time.Since(tstart).String()) + + return nil +} + +func init() { + inputs.Add("sql", func() telegraf.Input { + return &SQL{ + MaxIdleTime: config.Duration(0), // unlimited + MaxLifetime: config.Duration(0), // unlimited + MaxOpenConnections: 0, // unlimited + MaxIdleConnections: magicIdleCount, // will trigger auto calculation + } + }) +} + +func (s *SQL) executeQuery(ctx context.Context, acc telegraf.Accumulator, q Query, tquery time.Time) error { + if q.statement == nil { + return fmt.Errorf("statement is nil for query %q", q.Query) + } + + // Execute the query + rows, err := q.statement.QueryContext(ctx) + if err != nil { + return err + } + defer rows.Close() + + // Handle the rows + columnNames, err := rows.Columns() + if err != nil { + return err + } + rowCount, err := q.parse(ctx, acc, rows, tquery) + s.Log.Debugf("Received %d rows and %d columns for query %q", rowCount, len(columnNames), q.Query) + + return err +} diff --git a/plugins/inputs/sql/sql_test.go b/plugins/inputs/sql/sql_test.go new file mode 100644 index 000000000..35010eeb5 --- /dev/null +++ b/plugins/inputs/sql/sql_test.go @@ -0,0 +1,272 @@ +package sql + +import ( + "context" + "flag" + "fmt" + "testing" + "time" + + "math/rand" + "path/filepath" + + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/testutil" +) + +func pwgen(n int) string { + charset := []byte("abcdedfghijklmnopqrstABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + + nchars := len(charset) + buffer := make([]byte, n) + + for i := range buffer { + buffer[i] = charset[rand.Intn(nchars)] + } + + return string(buffer) +} + +var spinup = flag.Bool("spinup", false, "Spin-up the required test containers") + +func TestMariaDB(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + logger := testutil.Logger{} + + addr := "127.0.0.1" + port := "3306" + passwd := "" + database := "foo" + + if *spinup { + logger.Infof("Spinning up container...") + + // Generate a random password + passwd = pwgen(32) + + // Determine the test-data mountpoint + testdata, err := filepath.Abs("testdata/mariadb") + require.NoError(t, err, "determining absolute path of test-data failed") + + // Spin-up the container + ctx := context.Background() + req := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "mariadb", + Env: map[string]string{ + "MYSQL_ROOT_PASSWORD": passwd, + "MYSQL_DATABASE": database, + }, + BindMounts: map[string]string{ + testdata: "/docker-entrypoint-initdb.d", + }, + ExposedPorts: []string{"3306/tcp"}, + WaitingFor: wait.ForListeningPort("3306/tcp"), + }, + Started: true, + } + container, err := testcontainers.GenericContainer(ctx, req) + require.NoError(t, err, "starting container failed") + defer func() { + require.NoError(t, container.Terminate(ctx), "terminating container failed") + }() + + // Get the connection details from the container + addr, err = container.Host(ctx) + require.NoError(t, err, "getting container host address failed") + p, err := container.MappedPort(ctx, "3306/tcp") + require.NoError(t, err, "getting container host port failed") + port = p.Port() + } + + // Define the testset + var testset = []struct { + name string + queries []Query + expected []telegraf.Metric + }{ + { + name: "metric_one", + queries: []Query{ + { + Query: "SELECT * FROM metric_one", + TagColumnsInclude: []string{"tag_*"}, + FieldColumnsExclude: []string{"tag_*", "timestamp"}, + TimeColumn: "timestamp", + TimeFormat: "2006-01-02 15:04:05", + }, + }, + expected: []telegraf.Metric{ + testutil.MustMetric( + "sql", + map[string]string{ + "tag_one": "tag1", + "tag_two": "tag2", + }, + map[string]interface{}{ + "int64_one": int64(1234), + "int64_two": int64(2345), + }, + time.Date(2021, 5, 17, 22, 4, 45, 0, time.UTC), + ), + }, + }, + } + + for _, tt := range testset { + t.Run(tt.name, func(t *testing.T) { + // Setup the plugin-under-test + plugin := &SQL{ + Driver: "maria", + Dsn: fmt.Sprintf("root:%s@tcp(%s:%s)/%s", passwd, addr, port, database), + Queries: tt.queries, + Log: logger, + } + + var acc testutil.Accumulator + + // Startup the plugin + err := plugin.Init() + require.NoError(t, err) + err = plugin.Start(&acc) + require.NoError(t, err) + + // Gather + err = plugin.Gather(&acc) + require.NoError(t, err) + require.Len(t, acc.Errors, 0) + + // Stopping the plugin + plugin.Stop() + + // Do the comparison + testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics()) + }) + } +} + +func TestPostgreSQL(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + logger := testutil.Logger{} + + addr := "127.0.0.1" + port := "5432" + passwd := "" + database := "foo" + + if *spinup { + logger.Infof("Spinning up container...") + + // Generate a random password + passwd = pwgen(32) + + // Determine the test-data mountpoint + testdata, err := filepath.Abs("testdata/postgres") + require.NoError(t, err, "determining absolute path of test-data failed") + + // Spin-up the container + ctx := context.Background() + req := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "postgres", + Env: map[string]string{ + "POSTGRES_PASSWORD": passwd, + "POSTGRES_DB": database, + }, + BindMounts: map[string]string{ + testdata: "/docker-entrypoint-initdb.d", + }, + ExposedPorts: []string{"5432/tcp"}, + WaitingFor: wait.ForListeningPort("5432/tcp"), + }, + Started: true, + } + container, err := testcontainers.GenericContainer(ctx, req) + require.NoError(t, err, "starting container failed") + defer func() { + require.NoError(t, container.Terminate(ctx), "terminating container failed") + }() + + // Get the connection details from the container + addr, err = container.Host(ctx) + require.NoError(t, err, "getting container host address failed") + p, err := container.MappedPort(ctx, "5432/tcp") + require.NoError(t, err, "getting container host port failed") + port = p.Port() + } + + // Define the testset + var testset = []struct { + name string + queries []Query + expected []telegraf.Metric + }{ + { + name: "metric_one", + queries: []Query{ + { + Query: "SELECT * FROM metric_one", + TagColumnsInclude: []string{"tag_*"}, + FieldColumnsExclude: []string{"tag_*", "timestamp"}, + TimeColumn: "timestamp", + TimeFormat: "2006-01-02 15:04:05", + }, + }, + expected: []telegraf.Metric{ + testutil.MustMetric( + "sql", + map[string]string{ + "tag_one": "tag1", + "tag_two": "tag2", + }, + map[string]interface{}{ + "int64_one": int64(1234), + "int64_two": int64(2345), + }, + time.Date(2021, 5, 17, 22, 4, 45, 0, time.UTC), + ), + }, + }, + } + + for _, tt := range testset { + t.Run(tt.name, func(t *testing.T) { + // Setup the plugin-under-test + plugin := &SQL{ + Driver: "pgx", + Dsn: fmt.Sprintf("postgres://postgres:%v@%v:%v/%v", passwd, addr, port, database), + Queries: tt.queries, + Log: logger, + } + + var acc testutil.Accumulator + + // Startup the plugin + err := plugin.Init() + require.NoError(t, err) + err = plugin.Start(&acc) + require.NoError(t, err) + + // Gather + err = plugin.Gather(&acc) + require.NoError(t, err) + require.Len(t, acc.Errors, 0) + + // Stopping the plugin + plugin.Stop() + + // Do the comparison + testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics()) + }) + } +} diff --git a/plugins/inputs/sql/testdata/mariadb/expected.sql b/plugins/inputs/sql/testdata/mariadb/expected.sql new file mode 100644 index 000000000..49a3095db --- /dev/null +++ b/plugins/inputs/sql/testdata/mariadb/expected.sql @@ -0,0 +1,36 @@ +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bar` ( + `baz` int(11) DEFAULT NULL +); +/*!40101 SET character_set_client = @saved_cs_client */; +INSERT INTO `bar` VALUES (1); +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `metric three` ( + `timestamp` timestamp NOT NULL DEFAULT current_timestamp(), + `tag four` text DEFAULT NULL, + `string two` text DEFAULT NULL +); +/*!40101 SET character_set_client = @saved_cs_client */; +INSERT INTO `metric three` VALUES ('2021-05-17 22:04:45','tag4','string2'); +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `metric_one` ( + `timestamp` timestamp NOT NULL DEFAULT current_timestamp(), + `tag_one` text DEFAULT NULL, + `tag_two` text DEFAULT NULL, + `int64_one` int(11) DEFAULT NULL, + `int64_two` int(11) DEFAULT NULL +); +/*!40101 SET character_set_client = @saved_cs_client */; +INSERT INTO `metric_one` VALUES ('2021-05-17 22:04:45','tag1','tag2',1234,2345); +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `metric_two` ( + `timestamp` timestamp NOT NULL DEFAULT current_timestamp(), + `tag_three` text DEFAULT NULL, + `string_one` text DEFAULT NULL +); +/*!40101 SET character_set_client = @saved_cs_client */; +INSERT INTO `metric_two` VALUES ('2021-05-17 22:04:45','tag3','string1'); diff --git a/plugins/inputs/sql/testdata/postgres/expected.sql b/plugins/inputs/sql/testdata/postgres/expected.sql new file mode 100644 index 000000000..8bc2b2fc8 --- /dev/null +++ b/plugins/inputs/sql/testdata/postgres/expected.sql @@ -0,0 +1,41 @@ +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; +SET default_tablespace = ''; +SET default_table_access_method = heap; +CREATE TABLE public."metric three" ( + "timestamp" timestamp without time zone, + "tag four" text, + "string two" text +); +ALTER TABLE public."metric three" OWNER TO postgres; +CREATE TABLE public.metric_one ( + "timestamp" timestamp without time zone, + tag_one text, + tag_two text, + int64_one integer, + int64_two integer +); +ALTER TABLE public.metric_one OWNER TO postgres; +CREATE TABLE public.metric_two ( + "timestamp" timestamp without time zone, + tag_three text, + string_one text +); +ALTER TABLE public.metric_two OWNER TO postgres; +COPY public."metric three" ("timestamp", "tag four", "string two") FROM stdin; +2021-05-17 22:04:45 tag4 string2 +\. +COPY public.metric_one ("timestamp", tag_one, tag_two, int64_one, int64_two) FROM stdin; +2021-05-17 22:04:45 tag1 tag2 1234 2345 +\. +COPY public.metric_two ("timestamp", tag_three, string_one) FROM stdin; +2021-05-17 22:04:45 tag3 string1 +\.