From 956db51d730a2c88b5259b82214656e1138bf3bd Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Mon, 9 Apr 2018 08:48:08 +0200 Subject: [PATCH 01/27] Alertlog added --- README.md | 10 +- alertlog.go | 41 ++++ main.go | 526 ++++++++++++++++++++++++-------------------- misc.go | 33 +++ oracle.conf.example | 54 +++++ 5 files changed, 417 insertions(+), 247 deletions(-) create mode 100644 alertlog.go create mode 100644 misc.go create mode 100644 oracle.conf.example diff --git a/README.md b/README.md index a768451..d6cab1b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Prometheus Oracle Exporter -A [Prometheus](https://prometheus.io/) exporter for Oracle. Insipred from (https://github.com/iamseth/oracledb_exporter). I'm a DBA , PRs welcomed. +A [Prometheus](https://prometheus.io/) exporter for Oracle. +Insipred from (https://github.com/iamseth/oracledb_exporter). The following metrics are exposed currently. Support for RAC (databasename and instancename added via lables) @@ -9,8 +10,9 @@ The following metrics are exposed currently. Support for RAC (databasename and i - oracledb_exporter_scrapes_total - oracledb_uptime (days) - oracledb_session (view v$session system/user active/passive) -- oracledb_sysmetric (view v$sysmetric (Physical Read Total IO Requests Per Sec / Physical Write Total IO Requests Per Sec - Physical Read Total Bytes Per Sec / Physical Write Total Bytes Per Sec) +- oracledb_sysmetric (view v$sysmetric + (Physical Read Total IO Requests Per Sec / Physical Write Total IO Requests Per Sec + Physical Read Total Bytes Per Sec / Physical Write Total Bytes Per Sec)) - oracledb_sysstat (view v$sysstat (parse count (total) / execute count / user commits / user rollbacks)) - oracledb_waitclass (view v$waitclass) - oracledb_tablespace (tablespace total/free) @@ -34,6 +36,8 @@ export NLS_LANG=AMERICAN_AMERICA.UTF8 ```bash Usage of ./prometheus_oracle_exporter: + -configfile string + ConfigurationFile in YAML format. (default "oracle.conf") -web.listen-address string Address to listen on for web interface and telemetry. (default ":9161") -web.telemetry-path string diff --git a/alertlog.go b/alertlog.go new file mode 100644 index 0000000..5a8b585 --- /dev/null +++ b/alertlog.go @@ -0,0 +1,41 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "time" + "regexp" + "github.com/prometheus/common/log" +) + +func processAlertlog(fn string) { + var lastTime time.Time + + layout := "Mon Jan 02 15:04:05 2006" + loc := time.Now().Location() + + file, err := os.Open(fn) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + t, err := time.ParseInLocation(layout, scanner.Text(),loc) + if err == nil { + lastTime = t + } else { + match, _ := regexp.MatchString("^ORA-[0-9]+", scanner.Text()) + if match { + fmt.Println(lastTime) + fmt.Println(scanner.Text()) + } + } + } + + if err := scanner.Err(); err != nil { + log.Fatal(err) + } +} diff --git a/main.go b/main.go index db6efd5..aa19970 100644 --- a/main.go +++ b/main.go @@ -1,26 +1,14 @@ package main import ( - "database/sql" - "flag" - "net/http" - "os" - "strings" - "time" - + "database/sql" + "flag" + _ "net/http" + "time" + _ "fmt" _ "github.com/mattn/go-oci8" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/common/log" -) - - -var ( - // Version will be set at build time. - Version = "0.0.4" - listenAddress = flag.String("web.listen-address", ":9161", "Address to listen on for web interface and telemetry.") - metricPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") - landingPage = []byte("Prometheus Oracle exporter

Prometheus Oracle exporter

Metrics

") + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/log" ) // Metric name parts. @@ -29,36 +17,51 @@ const ( exporter = "exporter" ) +type Config struct { + Connection string `yaml:"connection"` + Database string `yaml:"database"` + Instance string `yaml:"instance"` + Alertlog string `yaml:"alertlog"` + Ignoreora []string `yaml:"ignoreora"` + db *sql.DB +} -type dbConn struct { - database, instance string - db *sql.DB +type Configs struct { + Cfgs []Config `yaml:"connections"` } // Exporter collects Oracle DB metrics. It implements prometheus.Collector. type Exporter struct { - dsn string duration, error prometheus.Gauge totalScrapes prometheus.Counter scrapeErrors *prometheus.CounterVec - session *prometheus.GaugeVec - sysstat *prometheus.GaugeVec - waitclass *prometheus.GaugeVec - sysmetric *prometheus.GaugeVec - interconnect *prometheus.GaugeVec + session *prometheus.GaugeVec + sysstat *prometheus.GaugeVec + waitclass *prometheus.GaugeVec + sysmetric *prometheus.GaugeVec + interconnect *prometheus.GaugeVec uptime *prometheus.GaugeVec + up *prometheus.GaugeVec tablespace *prometheus.GaugeVec recovery *prometheus.GaugeVec redo *prometheus.GaugeVec cache *prometheus.GaugeVec - conns []dbConn } +var ( + // Version will be set at build time. + Version = "1.0.0" + listenAddress = flag.String("web.listen-address", ":9161", "Address to listen on for web interface and telemetry.") + metricPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") + configFile = flag.String("configfile", "oracle.conf", "ConfigurationFile in YAML format.") + landingPage = []byte("Prometheus Oracle exporter

Prometheus Oracle exporter

Metrics

") + config Configs +) + // NewExporter returns a new Oracle DB exporter for the provided DSN. -func NewExporter(dsn string) *Exporter { +func NewExporter() *Exporter { return &Exporter{ - dsn: dsn, duration: prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: namespace, Subsystem: exporter, @@ -87,22 +90,22 @@ func NewExporter(dsn string) *Exporter { Namespace: namespace, Name: "sysmetric", Help: "Gauge metric with read/write pysical IOPs/bytes (v$sysmetric).", - }, []string{"type","database","dbinstance"}), + }, []string{"database","dbinstance","type"}), waitclass: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "waitclass", Help: "Gauge metric with Waitevents (v$waitclassmetric).", - }, []string{"type","database","dbinstance"}), + }, []string{"database","dbinstance","type"}), sysstat: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "sysstat", Help: "Gauge metric with commits/rollbacks/parses (v$sysstat).", - }, []string{"type","database","dbinstance"}), + }, []string{"database","dbinstance","type"}), session: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "session", Help: "Gauge metric user/system active/passive sessions (v$session).", - }, []string{"type","state","database","dbinstance"}), + }, []string{"database","dbinstance","type","state"}), uptime: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "uptime", @@ -112,17 +115,17 @@ func NewExporter(dsn string) *Exporter { Namespace: namespace, Name: "tablespace", Help: "Gauge metric with total/free size of the Tablespaces.", - }, []string{"name","type","contents","database","dbinstance"}), + }, []string{"database","dbinstance","type","name","contents"}), interconnect: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "interconnect", Help: "Gauge metric with interconnect block transfers (v$sysstat).", - }, []string{"type","database","dbinstance"}), + }, []string{"database","dbinstance","type"}), recovery: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "recovery", Help: "Gauge metric with percentage usage of FRA (v$recovery_file_dest).", - }, []string{"database","dbinstance"}), + }, []string{"database","dbinstance","type"}), redo: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "redo", @@ -132,7 +135,12 @@ func NewExporter(dsn string) *Exporter { Namespace: namespace, Name: "cachehitratio", Help: "Gauge metric witch Cache hit ratios (v$sysmetric).", - }, []string{"type","database","dbinstance"}), + }, []string{"database","dbinstance","type"}), + up: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "up", + Help: "Whether the Oracle server is up.", + }, []string{"database","dbinstance"}), } } @@ -142,26 +150,29 @@ func (e *Exporter) ScrapeCache() { rows *sql.Rows err error ) - for _, conn := range e.conns { + for _, conn := range config.Cfgs { //metric_id metric_name //2000 Buffer Cache Hit Ratio //2050 Cursor Cache Hit Ratio //2112 Library Cache Hit Ratio //2110 Row Cache Hit Ratio - - rows, err = conn.db.Query("select metric_name,value from v$sysmetric where metric_id in (2000,2050,2112,2110)") - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name, &value); err != nil { + if conn.db != nil { + rows, err = conn.db.Query(`select metric_name,value + from v$sysmetric + where group_id=2 and metric_id in (2000,2050,2112,2110)`) + if err != nil { break } - name = cleanName(name) - e.cache.WithLabelValues(name,conn.database,conn.instance).Set(value) + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name, &value); err != nil { + break + } + name = cleanName(name) + e.cache.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } } } } @@ -173,18 +184,20 @@ func (e *Exporter) ScrapeRedo() { rows *sql.Rows err error ) - for _, conn := range e.conns { - rows, err = conn.db.Query(`select count(*) from v$log_history where first_time > sysdate - 1/24/12`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var value float64 - if err := rows.Scan(&value); err != nil { + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`select count(*) from v$log_history where first_time > sysdate - 1/24/12`) + if err != nil { break } - e.redo.WithLabelValues(conn.database,conn.instance).Set(value) + defer rows.Close() + for rows.Next() { + var value float64 + if err := rows.Scan(&value); err != nil { + break + } + e.redo.WithLabelValues(conn.Database,conn.Instance).Set(value) + } } } } @@ -195,19 +208,23 @@ func (e *Exporter) ScrapeRecovery() { rows *sql.Rows err error ) - for _, conn := range e.conns { - rows, err = conn.db.Query(`SELECT sum(percent_space_used)-sum(percent_space_reclaimable) percent_used - from V$FLASH_RECOVERY_AREA_USAGE`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var value float64 - if err := rows.Scan(&value); err != nil { + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT sum(percent_space_used) , sum(percent_space_reclaimable) + from V$FLASH_RECOVERY_AREA_USAGE`) + if err != nil { break } - e.recovery.WithLabelValues(conn.database,conn.instance).Set(value) + defer rows.Close() + for rows.Next() { + var used float64 + var recl float64 + if err := rows.Scan(&used, &recl); err != nil { + break + } + e.recovery.WithLabelValues(conn.Database,conn.Instance,"percent_space_used").Set(used) + e.recovery.WithLabelValues(conn.Database,conn.Instance,"percent_space_reclaimable").Set(recl) + } } } } @@ -218,22 +235,24 @@ func (e *Exporter) ScrapeInterconnect() { rows *sql.Rows err error ) - for _, conn := range e.conns { - rows, err = conn.db.Query(`SELECT name, value - FROM V$SYSSTAT - WHERE name in ('gc cr blocks served','gc cr blocks flushed','gc cr blocks received')`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name, &value); err != nil { + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT name, value + FROM V$SYSSTAT + WHERE name in ('gc cr blocks served','gc cr blocks flushed','gc cr blocks received')`) + if err != nil { break } - name = cleanName(name) - e.interconnect.WithLabelValues(name,conn.database,conn.instance).Set(value) + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name, &value); err != nil { + break + } + name = cleanName(name) + e.interconnect.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } } } } @@ -244,36 +263,38 @@ func (e *Exporter) ScrapeTablespace() { rows *sql.Rows err error ) - for _, conn := range e.conns { - rows, err = conn.db.Query(`WITH - getsize AS (SELECT tablespace_name, SUM(bytes) tsize - FROM dba_data_files GROUP BY tablespace_name), - getfree as (SELECT tablespace_name, contents, SUM(blocks*block_size) tfree - FROM DBA_LMT_FREE_SPACE a, v$tablespace b, dba_tablespaces c - WHERE a.TABLESPACE_ID= b.ts# and b.name=c.tablespace_name - GROUP BY tablespace_name,contents) - SELECT a.tablespace_name, b.contents, a.tsize, b.tfree - FROM GETSIZE a, GETFREE b - WHERE a.tablespace_name = b.tablespace_name - UNION - SELECT tablespace_name, 'TEMPORARY', sum(tablespace_size), sum(free_space) - FROM dba_temp_free_space - GROUP BY tablespace_name`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var contents string - var tsize float64 - var tfree float64 - if err := rows.Scan(&name, &contents, &tsize, &tfree); err != nil { + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`WITH + getsize AS (SELECT tablespace_name, SUM(bytes) tsize + FROM dba_data_files GROUP BY tablespace_name), + getfree as (SELECT tablespace_name, contents, SUM(blocks*block_size) tfree + FROM DBA_LMT_FREE_SPACE a, v$tablespace b, dba_tablespaces c + WHERE a.TABLESPACE_ID= b.ts# and b.name=c.tablespace_name + GROUP BY tablespace_name,contents) + SELECT a.tablespace_name, b.contents, a.tsize, b.tfree + FROM GETSIZE a, GETFREE b + WHERE a.tablespace_name = b.tablespace_name + UNION + SELECT tablespace_name, 'TEMPORARY', sum(tablespace_size), sum(free_space) + FROM dba_temp_free_space + GROUP BY tablespace_name`) + if err != nil { break } - e.tablespace.WithLabelValues(name,"total",contents,conn.database,conn.instance).Set(tsize) - e.tablespace.WithLabelValues(name,"free",contents,conn.database,conn.instance).Set(tfree) - e.tablespace.WithLabelValues(name,"used",contents,conn.database,conn.instance).Set(tsize-tfree) + defer rows.Close() + for rows.Next() { + var name string + var contents string + var tsize float64 + var tfree float64 + if err := rows.Scan(&name, &contents, &tsize, &tfree); err != nil { + break + } + e.tablespace.WithLabelValues(conn.Database,conn.Instance,name,"total",contents).Set(tsize) + e.tablespace.WithLabelValues(conn.Database,conn.Instance,name,"free",contents).Set(tfree) + e.tablespace.WithLabelValues(conn.Database,conn.Instance,name,"used",contents).Set(tsize-tfree) + } } } } @@ -284,22 +305,24 @@ func (e *Exporter) ScrapeSession() { rows *sql.Rows err error ) - for _, conn := range e.conns { - rows, err = conn.db.Query(`SELECT decode(username,NULL,'SYSTEM','SYS','SYSTEM','USER'), status,count(*) - FROM v$session - GROUP BY decode(username,NULL,'SYSTEM','SYS','SYSTEM','USER'),status`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var user string - var status string - var value float64 - if err := rows.Scan(&user, &status, &value); err != nil { + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT decode(username,NULL,'SYSTEM','SYS','SYSTEM','USER'), status,count(*) + FROM v$session + GROUP BY decode(username,NULL,'SYSTEM','SYS','SYSTEM','USER'),status`) + if err != nil { break } - e.session.WithLabelValues(user,status,conn.database,conn.instance).Set(value) + defer rows.Close() + for rows.Next() { + var user string + var status string + var value float64 + if err := rows.Scan(&user, &status, &value); err != nil { + break + } + e.session.WithLabelValues(conn.Database,conn.Instance,user,status).Set(value) + } } } } @@ -308,12 +331,14 @@ func (e *Exporter) ScrapeSession() { // ScrapeUptime Instance uptime func (e *Exporter) ScrapeUptime() { var uptime float64 - for _, conn := range e.conns { - err := conn.db.QueryRow("select sysdate-startup_time from v$instance").Scan(&uptime) - if err != nil { - return + for _, conn := range config.Cfgs { + if conn.db != nil { + err := conn.db.QueryRow("select sysdate-startup_time from v$instance").Scan(&uptime) + if err != nil { + return + } + e.uptime.WithLabelValues(conn.Database,conn.Instance).Set(uptime) } - e.uptime.WithLabelValues(conn.database,conn.instance).Set(uptime) } } @@ -323,21 +348,23 @@ func (e *Exporter) ScrapeSysstat() { rows *sql.Rows err error ) - for _, conn := range e.conns { - rows, err = conn.db.Query(`SELECT name, value FROM v$sysstat - WHERE name IN ('parse count (total)', 'execute count', 'user commits', 'user rollbacks')`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name, &value); err != nil { + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT name, value FROM v$sysstat + WHERE statistic# in (6,7,1084,1089)`) + if err != nil { break } - name = cleanName(name) - e.sysstat.WithLabelValues(name,conn.database,conn.instance).Set(value) + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name, &value); err != nil { + break + } + name = cleanName(name) + e.sysstat.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } } } } @@ -348,22 +375,24 @@ func (e *Exporter) ScrapeWaitclass() { rows *sql.Rows err error ) - for _, conn := range e.conns { - rows, err = conn.db.Query(`SELECT n.wait_class, round(m.time_waited/m.INTSIZE_CSEC,3) - FROM v$waitclassmetric m, v$system_wait_class n - WHERE m.wait_class_id=n.wait_class_id and n.wait_class != 'Idle'`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name, &value); err != nil { + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT n.wait_class, round(m.time_waited/m.INTSIZE_CSEC,3) + FROM v$waitclassmetric m, v$system_wait_class n + WHERE m.wait_class_id=n.wait_class_id and n.wait_class != 'Idle'`) + if err != nil { break } - name = cleanName(name) - e.waitclass.WithLabelValues(name,conn.database,conn.instance).Set(value) + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name, &value); err != nil { + break + } + name = cleanName(name) + e.waitclass.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } } } } @@ -374,25 +403,27 @@ func (e *Exporter) ScrapeSysmetric() { rows *sql.Rows err error ) - for _, conn := range e.conns { + for _, conn := range config.Cfgs { //metric_id metric_name //2092 Physical Read Total IO Requests Per Sec //2093 Physical Read Total Bytes Per Sec //2100 Physical Write Total IO Requests Per Sec //2124 Physical Write Total Bytes Per Sec - rows, err = conn.db.Query("select metric_name,value from v$sysmetric where metric_id in (2092,2093,2124,2100)") - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name, &value); err != nil { + if conn.db != nil { + rows, err = conn.db.Query("select metric_name,value from v$sysmetric where metric_id in (2092,2093,2124,2100)") + if err != nil { break } - name = cleanName(name) - e.sysmetric.WithLabelValues(name,conn.database,conn.instance).Set(value) + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name, &value); err != nil { + break + } + name = cleanName(name) + e.sysmetric.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } } } } @@ -403,57 +434,70 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { e.duration.Describe(ch) e.totalScrapes.Describe(ch) e.scrapeErrors.Describe(ch) - e.session.Describe(ch) - e.sysstat.Describe(ch) - e.waitclass.Describe(ch) + e.session.Describe(ch) + e.sysstat.Describe(ch) + e.waitclass.Describe(ch) e.sysmetric.Describe(ch) e.interconnect.Describe(ch) - e.tablespace.Describe(ch) - e.recovery.Describe(ch) - e.redo.Describe(ch) - e.cache.Describe(ch) + e.tablespace.Describe(ch) + e.recovery.Describe(ch) + e.redo.Describe(ch) + e.cache.Describe(ch) e.uptime.Describe(ch) + e.up.Describe(ch) } // Connect the DBs and gather Databasename and Instancename func (e *Exporter) Connect() { - var instance string - var database string - for _, dsn := range strings.Split(e.dsn,";") { - db , err := sql.Open("oci8", dsn) - if err != nil { - log.Errorln("Error opening connection to database:", err) - break - } - err = db.QueryRow("select db_unique_name from v$database").Scan(&database) - if err != nil { - log.Errorln("Error query the database name:", err) - break - } + var dbname string + var inname string - err = db.QueryRow("select instance_name from v$instance").Scan(&instance) - if err != nil { - log.Errorln("Error query the instance name:", err) - break + for i, conf := range config.Cfgs { + config.Cfgs[i].db = nil + db , err := sql.Open("oci8", conf.Connection) + if err == nil { + err = db.QueryRow("select db_unique_name,instance_name from v$database,v$instance").Scan(&dbname,&inname) + if err == nil { + if (conf.Database != dbname) || (conf.Instance != inname) { + config.Cfgs[i].Database = dbname + config.Cfgs[i].Instance = inname + } + config.Cfgs[i].db = db + e.up.WithLabelValues(conf.Database,conf.Instance).Set(1) + } else { + e.up.WithLabelValues(conf.Database,conf.Instance).Set(0) + } + } else { + e.up.WithLabelValues(conf.Database,conf.Instance).Set(0) } - conn := dbConn{database: database, instance: instance, db: db} - e.conns = append(e.conns, conn) } + e.session.Reset() + e.sysstat.Reset() + e.waitclass.Reset() + e.sysmetric.Reset() + e.interconnect.Reset() + e.tablespace.Reset() + e.recovery.Reset() + e.redo.Reset() + e.cache.Reset() + e.uptime.Reset() } // Close Connections func (e *Exporter) Close() { - for _, conn := range e.conns { - conn.db.Close() + for _, conn := range config.Cfgs { + if conn.db != nil { + conn.db.Close() + } } - e.conns = nil } // Collect implements prometheus.Collector. -func (e *Exporter) Collect(ch chan<- prometheus.Metric) { +func (e *Exporter) Collect(ch chan<- prometheus.Metric) { var err error - e.totalScrapes.Inc() + + e.totalScrapes.Inc() defer func(begun time.Time) { e.duration.Set(time.Since(begun).Seconds()) if err == nil { @@ -463,66 +507,60 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { } }(time.Now()) - e.Connect() + e.Connect() + e.up.Collect(ch) - e.ScrapeUptime() - e.uptime.Collect(ch) + e.ScrapeUptime() + e.uptime.Collect(ch) - e.ScrapeSession() - e.session.Collect(ch) + e.ScrapeSession() + e.session.Collect(ch) - e.ScrapeSysstat() - e.sysstat.Collect(ch) + e.ScrapeSysstat() + e.sysstat.Collect(ch) - e.ScrapeWaitclass() - e.waitclass.Collect(ch) + e.ScrapeWaitclass() + e.waitclass.Collect(ch) - e.ScrapeSysmetric() - e.sysmetric.Collect(ch) + e.ScrapeSysmetric() + e.sysmetric.Collect(ch) - e.ScrapeTablespace() - e.tablespace.Collect(ch) + e.ScrapeTablespace() + e.tablespace.Collect(ch) - e.ScrapeInterconnect() - e.interconnect.Collect(ch) + e.ScrapeInterconnect() + e.interconnect.Collect(ch) - e.ScrapeRecovery() - e.recovery.Collect(ch) + e.ScrapeRecovery() + e.recovery.Collect(ch) - e.ScrapeRedo() - e.redo.Collect(ch) + e.ScrapeRedo() + e.redo.Collect(ch) - e.ScrapeCache() - e.cache.Collect(ch) + e.ScrapeCache() + e.cache.Collect(ch) ch <- e.duration ch <- e.totalScrapes ch <- e.error e.scrapeErrors.Collect(ch) - e.Close() -} - -// Oracle gives us some ugly names back. This function cleans things up for Prometheus. -func cleanName(s string) string { - s = strings.Replace(s, " ", "_", -1) // Remove spaces - s = strings.Replace(s, "(", "", -1) // Remove open parenthesis - s = strings.Replace(s, ")", "", -1) // Remove close parenthesis - s = strings.Replace(s, "/", "", -1) // Remove forward slashes - s = strings.ToLower(s) - return s + e.Close() } func main() { flag.Parse() log.Infoln("Starting Prometheus Oracle exporter " + Version) - dsn := os.Getenv("DATA_SOURCE_NAME") - exporter := NewExporter(dsn) - prometheus.MustRegister(exporter) - http.Handle(*metricPath, prometheus.Handler()) - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if loadConfig() { +/* log.Infoln("Config loaded: ", *configFile) + exporter := NewExporter() + prometheus.MustRegister(exporter) + http.Handle(*metricPath, prometheus.Handler()) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write(landingPage) - }) - log.Infoln("Listening on", *listenAddress) - log.Fatal(http.ListenAndServe(*listenAddress, nil)) + }) + log.Infoln("Listening on", *listenAddress) + log.Fatal(http.ListenAndServe(*listenAddress, nil))*/ + } + processAlertlog(config.Cfgs[0].Alertlog) } diff --git a/misc.go b/misc.go new file mode 100644 index 0000000..1428230 --- /dev/null +++ b/misc.go @@ -0,0 +1,33 @@ +package main + +import ( + "strings" + "io/ioutil" + "gopkg.in/yaml.v2" + "github.com/prometheus/common/log" +) + +// Oracle gives us some ugly names back. This function cleans things up for Prometheus. +func cleanName(s string) string { + s = strings.Replace(s, " ", "_", -1) // Remove spaces + s = strings.Replace(s, "(", "", -1) // Remove open parenthesis + s = strings.Replace(s, ")", "", -1) // Remove close parenthesis + s = strings.Replace(s, "/", "", -1) // Remove forward slashes + s = strings.ToLower(s) + return s +} + +func loadConfig() bool { + content, err := ioutil.ReadFile(*configFile) + if err != nil { + log.Fatalf("error: %v", err) + return false + } else { + err := yaml.Unmarshal(content, &config) + if err != nil { + log.Fatalf("error: %v", err) + return false + } + return true + } +} diff --git a/oracle.conf.example b/oracle.conf.example new file mode 100644 index 0000000..8bd9d34 --- /dev/null +++ b/oracle.conf.example @@ -0,0 +1,54 @@ +connections: + - connection: /@ + database: DEVELOP + instance: DEVELOP + alertlog: /data/oracle/diag/rdbms/develop/DEVELOP/trace/alert_DEVELOP.log + ignoreora: + - ORA-01033 + - ORA-01041 + - ORA-01089 + - ORA-02050 + - ORA-02068 + - ORA-03113 + - ORA-03135 + - ORA-12537 + - ORA-16011 + - ORA-16040 + - ORA-16055 + - ORA-16058 + - ORA-16401 + - ORA-00054 + - ORA-01013 + - ORA-01555 + - ORA-28 + - ORA-235 + - ORA-609 + - ORA-3136 + - ABORT_REDEF_TABLE + + - connection: /@ + database: STAGE + instance: STAGE + alertlog: /data/oracle/diag/rdbms/stage/STAGE/trace/alert_STAGE.log + ignoreora: + - ORA-01033 + - ORA-01041 + - ORA-01089 + - ORA-02050 + - ORA-02068 + - ORA-03113 + - ORA-03135 + - ORA-12537 + - ORA-16011 + - ORA-16040 + - ORA-16055 + - ORA-16058 + - ORA-16401 + - ORA-00054 + - ORA-01013 + - ORA-01555 + - ORA-28 + - ORA-235 + - ORA-609 + - ORA-3136 + - ABORT_REDEF_TABLE From bbf315b43bcc2a51b0e3f752233bec2f84d2395b Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Mon, 9 Apr 2018 08:49:10 +0200 Subject: [PATCH 02/27] Alertlog added --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..44f422d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +oracle.conf +prometheus_oracle_exporter + + From 4738925b5dfa682d522b9c4ee4a704825d5dea76 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Mon, 9 Apr 2018 14:40:52 +0200 Subject: [PATCH 03/27] Alertlog added --- .gitignore | 2 -- alertlog.go | 78 ++++++++++++++++++++++++++++++++++++++++------------- main.go | 34 ++++++++++++++++------- 3 files changed, 83 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 44f422d..6793b16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ oracle.conf prometheus_oracle_exporter - - diff --git a/alertlog.go b/alertlog.go index 5a8b585..7ec553a 100644 --- a/alertlog.go +++ b/alertlog.go @@ -2,40 +2,80 @@ package main import ( "bufio" - "fmt" "os" "time" "regexp" + "strings" "github.com/prometheus/common/log" ) -func processAlertlog(fn string) { - var lastTime time.Time +type oraerr struct { + ora string + text string + ignore int + count int +} - layout := "Mon Jan 02 15:04:05 2006" - loc := time.Now().Location() +var Errors []oraerr - file, err := os.Open(fn) - if err != nil { - log.Fatal(err) +func addError(conf int, ora string, text string){ + var found bool = false + for i, _ := range Errors { + if Errors[i].ora == ora { + Errors[i].count ++ + found = true + } + } + if ! found { + ignore := 0 + for _ , e := range config.Cfgs[conf].Alertlog[0].Ignoreora { + if e == ora { + ignore = 1 + } + } + i := strings.Index(text, " ") + if i < 0{ + i = 0 + } + ora := oraerr{ora: ora, text: text[i+1:], ignore: ignore, count: 1} + Errors = append (Errors, ora) } - defer file.Close() +} + +func (e *Exporter) ScrapeOraerror() { + layout := "Mon Jan 02 15:04:05 2006" + loc := time.Now().Location() + re := regexp.MustCompile(`ORA-[0-9]+`) + + for conf, _ := range config.Cfgs { + var lastTime time.Time + Errors = nil - scanner := bufio.NewScanner(file) - for scanner.Scan() { + file, err := os.Open(config.Cfgs[conf].Alertlog[0].File) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { t, err := time.ParseInLocation(layout, scanner.Text(),loc) if err == nil { lastTime = t } else { - match, _ := regexp.MatchString("^ORA-[0-9]+", scanner.Text()) - if match { - fmt.Println(lastTime) - fmt.Println(scanner.Text()) + if int(time.Now().Sub(lastTime).Seconds()) < config.Cfgs[conf].Alertlog[0].Scantime { + if re.MatchString(scanner.Text()) { + ora := re.FindString(scanner.Text()) + addError(conf,ora, scanner.Text()) + } } } - } - - if err := scanner.Err(); err != nil { - log.Fatal(err) + } + for i, _ := range Errors { + e.oraerror.WithLabelValues(config.Cfgs[conf].Database, + config.Cfgs[conf].Instance, + Errors[i].ora, + Errors[i].text).Set(float64(Errors[i].count)) + } } } diff --git a/main.go b/main.go index aa19970..c71d48e 100644 --- a/main.go +++ b/main.go @@ -3,9 +3,8 @@ package main import ( "database/sql" "flag" - _ "net/http" + "net/http" "time" - _ "fmt" _ "github.com/mattn/go-oci8" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/log" @@ -17,12 +16,17 @@ const ( exporter = "exporter" ) +type Alert struct { + File string `yaml:"file"` + Scantime int `yaml:"scantime"` + Ignoreora []string `yaml:"ignoreora"` +} + type Config struct { Connection string `yaml:"connection"` Database string `yaml:"database"` Instance string `yaml:"instance"` - Alertlog string `yaml:"alertlog"` - Ignoreora []string `yaml:"ignoreora"` + Alertlog []Alert `yaml:"alertlog"` db *sql.DB } @@ -46,6 +50,7 @@ type Exporter struct { recovery *prometheus.GaugeVec redo *prometheus.GaugeVec cache *prometheus.GaugeVec + oraerror *prometheus.GaugeVec } var ( @@ -141,6 +146,11 @@ func NewExporter() *Exporter { Name: "up", Help: "Whether the Oracle server is up.", }, []string{"database","dbinstance"}), + oraerror: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "error", + Help: "Oracle Errors occured during configured interval.", + }, []string{"database","dbinstance","type","name"}), } } @@ -445,6 +455,7 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { e.cache.Describe(ch) e.uptime.Describe(ch) e.up.Describe(ch) + e.oraerror.Describe(ch) } // Connect the DBs and gather Databasename and Instancename @@ -481,6 +492,7 @@ func (e *Exporter) Connect() { e.redo.Reset() e.cache.Reset() e.uptime.Reset() + e.oraerror.Reset() } // Close Connections @@ -540,6 +552,9 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.ScrapeCache() e.cache.Collect(ch) + e.ScrapeOraerror() + e.oraerror.Collect(ch) + ch <- e.duration ch <- e.totalScrapes ch <- e.error @@ -552,15 +567,14 @@ func main() { flag.Parse() log.Infoln("Starting Prometheus Oracle exporter " + Version) if loadConfig() { -/* log.Infoln("Config loaded: ", *configFile) + + log.Infoln("Config loaded: ", *configFile) exporter := NewExporter() prometheus.MustRegister(exporter) http.Handle(*metricPath, prometheus.Handler()) - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.Write(landingPage) - }) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write(landingPage)}) + log.Infoln("Listening on", *listenAddress) - log.Fatal(http.ListenAndServe(*listenAddress, nil))*/ + log.Fatal(http.ListenAndServe(*listenAddress, nil)) } - processAlertlog(config.Cfgs[0].Alertlog) } From 25595c3dc4ae75620b7198ca7d5035584d419334 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Mon, 9 Apr 2018 14:44:03 +0200 Subject: [PATCH 04/27] Alertlog added --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 1f1500c..f08ea01 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Michael Neumann +Copyright (c) 2018 Freenet Digital GmbH Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From f754f1012ebf87e5aa9de523572ca5a71c79457f Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Mon, 9 Apr 2018 14:54:02 +0200 Subject: [PATCH 05/27] Alertlog added --- README.md | 5 ++- oracle.conf.example | 97 ++++++++++++++++++++++++--------------------- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index d6cab1b..51f2aaf 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,15 @@ The following metrics are exposed currently. Support for RAC (databasename and i - oracledb_recovery (percentage usage in FRA from V$RECOVERY_FILE_DEST) - oracledb_redo (Redo log switches over last 5 min from v$log_history) - oracledb_cachehitratio (Cache hit ratios (v$sysmetric) +- oracledb_error (Errors parsed from the alert.log) ... # Installation -Ensure that the environment variable DATA_SOURCE_NAME is set correctly before starting. You can add multiple instances, if you run more than one instance on a host. It is even possible to run one Exporter for all your Databases, but this is not recommended. We use it in our Company because on one host multiple Instances are running. +Ensure that the configfile (oracle.conf) is set correctly before starting. You can add multiple instances, if you run more than one instance on a host or. It is even possible to run one Exporter for all your Databases, but this is not recommended. We use it in our Company because on one host multiple Instances are running. +It is now possible to scan the alert.log File. The metrics are exposed as a gauge metric with a total occurence of the specific ORA in a definied timeframe (scantime). ```bash -export DATA_SOURCE_NAME="system/oracle@myhost1;system/oracle@myhost2;system/oracle@myhost3" export NLS_LANG=AMERICAN_AMERICA.UTF8 /path/to/binary -l log.level error -l web.listen-address 9161 ``` diff --git a/oracle.conf.example b/oracle.conf.example index 8bd9d34..b062f98 100644 --- a/oracle.conf.example +++ b/oracle.conf.example @@ -2,53 +2,58 @@ connections: - connection: /@ database: DEVELOP instance: DEVELOP - alertlog: /data/oracle/diag/rdbms/develop/DEVELOP/trace/alert_DEVELOP.log - ignoreora: - - ORA-01033 - - ORA-01041 - - ORA-01089 - - ORA-02050 - - ORA-02068 - - ORA-03113 - - ORA-03135 - - ORA-12537 - - ORA-16011 - - ORA-16040 - - ORA-16055 - - ORA-16058 - - ORA-16401 - - ORA-00054 - - ORA-01013 - - ORA-01555 - - ORA-28 - - ORA-235 - - ORA-609 - - ORA-3136 - - ABORT_REDEF_TABLE + alertlog: + - file: /data/oracle/diag/rdbms/develop/DEVELOP/trace/alert_DEVELOP.log + scantime: 300 + ignoreora: + - ORA-00001 + - ORA-01033 + - ORA-01041 + - ORA-01089 + - ORA-02050 + - ORA-02068 + - ORA-03113 + - ORA-03135 + - ORA-12537 + - ORA-16011 + - ORA-16040 + - ORA-16055 + - ORA-16058 + - ORA-16401 + - ORA-00054 + - ORA-01013 + - ORA-01555 + - ORA-28 + - ORA-235 + - ORA-609 + - ORA-3136 + - ABORT_REDEF_TABLE - connection: /@ database: STAGE instance: STAGE - alertlog: /data/oracle/diag/rdbms/stage/STAGE/trace/alert_STAGE.log - ignoreora: - - ORA-01033 - - ORA-01041 - - ORA-01089 - - ORA-02050 - - ORA-02068 - - ORA-03113 - - ORA-03135 - - ORA-12537 - - ORA-16011 - - ORA-16040 - - ORA-16055 - - ORA-16058 - - ORA-16401 - - ORA-00054 - - ORA-01013 - - ORA-01555 - - ORA-28 - - ORA-235 - - ORA-609 - - ORA-3136 - - ABORT_REDEF_TABLE + alertlog: + - file: /data/oracle/diag/rdbms/stage/STAGE/trace/alert_STAGE.log + scantime: 300 + ignoreora: + - ORA-01033 + - ORA-01041 + - ORA-01089 + - ORA-02050 + - ORA-02068 + - ORA-03113 + - ORA-03135 + - ORA-12537 + - ORA-16011 + - ORA-16040 + - ORA-16055 + - ORA-16058 + - ORA-16401 + - ORA-00054 + - ORA-01013 + - ORA-01555 + - ORA-28 + - ORA-235 + - ORA-609 + - ORA-3136 + - ABORT_REDEF_TABLE From 423b86b93610f40c55e7032bc4975ac3370ba5ae Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Mon, 9 Apr 2018 14:55:19 +0200 Subject: [PATCH 06/27] Alertlog added --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 51f2aaf..9c6b7d8 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The following metrics are exposed currently. Support for RAC (databasename and i # Installation Ensure that the configfile (oracle.conf) is set correctly before starting. You can add multiple instances, if you run more than one instance on a host or. It is even possible to run one Exporter for all your Databases, but this is not recommended. We use it in our Company because on one host multiple Instances are running. -It is now possible to scan the alert.log File. The metrics are exposed as a gauge metric with a total occurence of the specific ORA in a definied timeframe (scantime). +It is now possible to scan the alert.log File. The metrics are exposed as a gauge metric with a total occurence of the specific ORA in a definied timeframe (scantime). ```bash export NLS_LANG=AMERICAN_AMERICA.UTF8 @@ -37,8 +37,8 @@ export NLS_LANG=AMERICAN_AMERICA.UTF8 ```bash Usage of ./prometheus_oracle_exporter: - -configfile string - ConfigurationFile in YAML format. (default "oracle.conf") + -configfile string + ConfigurationFile in YAML format. (default "oracle.conf") -web.listen-address string Address to listen on for web interface and telemetry. (default ":9161") -web.telemetry-path string From 11a541c8d8f6617329e1ff0adca46632e522a9d1 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Mon, 9 Apr 2018 15:39:23 +0200 Subject: [PATCH 07/27] Alertlog added --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9c6b7d8..3f86e51 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ The following metrics are exposed currently. Support for RAC (databasename and i - oracledb_recovery (percentage usage in FRA from V$RECOVERY_FILE_DEST) - oracledb_redo (Redo log switches over last 5 min from v$log_history) - oracledb_cachehitratio (Cache hit ratios (v$sysmetric) +- oracledb_up (Whether the Oracle server is up) - oracledb_error (Errors parsed from the alert.log) ... From c168039c3f327e22b52bfcdee76d369bb99363e6 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Tue, 10 Apr 2018 13:20:59 +0200 Subject: [PATCH 08/27] Active Services added --- README.md | 3 ++- alertlog.go | 39 ++++++++++++++++++++------------------- main.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 3f86e51..42c6160 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,12 @@ The following metrics are exposed currently. Support for RAC (databasename and i - oracledb_cachehitratio (Cache hit ratios (v$sysmetric) - oracledb_up (Whether the Oracle server is up) - oracledb_error (Errors parsed from the alert.log) +- oracledb_services (Active Oracle Services (v$active_services)) ... # Installation -Ensure that the configfile (oracle.conf) is set correctly before starting. You can add multiple instances, if you run more than one instance on a host or. It is even possible to run one Exporter for all your Databases, but this is not recommended. We use it in our Company because on one host multiple Instances are running. +Ensure that the configfile (oracle.conf) is set correctly before starting. You can add multiple instances, e.g. the ASM instance. It is even possible to run one Exporter for all your Databases, but this is not recommended. We use it in our Company because on one host multiple Instances are running. It is now possible to scan the alert.log File. The metrics are exposed as a gauge metric with a total occurence of the specific ORA in a definied timeframe (scantime). ```bash diff --git a/alertlog.go b/alertlog.go index 7ec553a..24843a9 100644 --- a/alertlog.go +++ b/alertlog.go @@ -53,29 +53,30 @@ func (e *Exporter) ScrapeOraerror() { file, err := os.Open(config.Cfgs[conf].Alertlog[0].File) if err != nil { - log.Fatal(err) - } - defer file.Close() + log.Infoln(err) + } else{ + file.Close() - scanner := bufio.NewScanner(file) - for scanner.Scan() { - t, err := time.ParseInLocation(layout, scanner.Text(),loc) - if err == nil { - lastTime = t - } else { - if int(time.Now().Sub(lastTime).Seconds()) < config.Cfgs[conf].Alertlog[0].Scantime { - if re.MatchString(scanner.Text()) { - ora := re.FindString(scanner.Text()) - addError(conf,ora, scanner.Text()) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + t, err := time.ParseInLocation(layout, scanner.Text(),loc) + if err == nil { + lastTime = t + } else { + if int(time.Now().Sub(lastTime).Seconds()) < config.Cfgs[conf].Alertlog[0].Scantime { + if re.MatchString(scanner.Text()) { + ora := re.FindString(scanner.Text()) + addError(conf,ora, scanner.Text()) + } } } } - } - for i, _ := range Errors { - e.oraerror.WithLabelValues(config.Cfgs[conf].Database, - config.Cfgs[conf].Instance, - Errors[i].ora, - Errors[i].text).Set(float64(Errors[i].count)) + for i, _ := range Errors { + e.oraerror.WithLabelValues(config.Cfgs[conf].Database, + config.Cfgs[conf].Instance, + Errors[i].ora, + Errors[i].text).Set(float64(Errors[i].count)) + } } } } diff --git a/main.go b/main.go index c71d48e..0a976d5 100644 --- a/main.go +++ b/main.go @@ -51,6 +51,7 @@ type Exporter struct { redo *prometheus.GaugeVec cache *prometheus.GaugeVec oraerror *prometheus.GaugeVec + services *prometheus.GaugeVec } var ( @@ -151,9 +152,40 @@ func NewExporter() *Exporter { Name: "error", Help: "Oracle Errors occured during configured interval.", }, []string{"database","dbinstance","type","name"}), + services: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "services", + Help: "Active Oracle Services (v$active_services).", + }, []string{"database","dbinstance","name"}), } } +// ScrapeServices collects metrics from the v$active_services view. +func (e *Exporter) ScrapeServices() { + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`select name from v$active_services`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + break + } + name = cleanName(name) + e.services.WithLabelValues(conn.Database,conn.Instance,name).Set(1) + } + } + } +} + + // ScrapeCache collects session metrics from the v$sysmetrics view. func (e *Exporter) ScrapeCache() { var ( @@ -456,6 +488,7 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { e.uptime.Describe(ch) e.up.Describe(ch) e.oraerror.Describe(ch) + e.services.Describe(ch) } // Connect the DBs and gather Databasename and Instancename @@ -493,6 +526,7 @@ func (e *Exporter) Connect() { e.cache.Reset() e.uptime.Reset() e.oraerror.Reset() + e.services.Reset() } // Close Connections @@ -555,6 +589,9 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.ScrapeOraerror() e.oraerror.Collect(ch) + e.ScrapeServices() + e.services.Collect(ch) + ch <- e.duration ch <- e.totalScrapes ch <- e.error From 5df76f76f6bcd1ecd05a4e6d32a0966699698f85 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Tue, 10 Apr 2018 14:50:59 +0200 Subject: [PATCH 09/27] Parameters added --- README.md | 5 +++-- main.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 42c6160..70b57d9 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ The following metrics are exposed currently. Support for RAC (databasename and i - oracledb_up (Whether the Oracle server is up) - oracledb_error (Errors parsed from the alert.log) - oracledb_services (Active Oracle Services (v$active_services)) -... +- oracledb_parameter (Configuration Parameters (v$parameter)) + # Installation @@ -40,7 +41,7 @@ export NLS_LANG=AMERICAN_AMERICA.UTF8 ```bash Usage of ./prometheus_oracle_exporter: -configfile string - ConfigurationFile in YAML format. (default "oracle.conf") + ConfigurationFile in YAML format. (default "oracle.conf") -web.listen-address string Address to listen on for web interface and telemetry. (default ":9161") -web.telemetry-path string diff --git a/main.go b/main.go index 0a976d5..50751ef 100644 --- a/main.go +++ b/main.go @@ -52,6 +52,7 @@ type Exporter struct { cache *prometheus.GaugeVec oraerror *prometheus.GaugeVec services *prometheus.GaugeVec + parameter *prometheus.GaugeVec } var ( @@ -157,9 +158,43 @@ func NewExporter() *Exporter { Name: "services", Help: "Active Oracle Services (v$active_services).", }, []string{"database","dbinstance","name"}), + parameter: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "parameter", + Help: "oracle Configuration Parameters (v$parameter).", + }, []string{"database","dbinstance","name"}), } } +// ScrapeParameters collects metrics from the v$parameters view. +func (e *Exporter) ScrapeParameter() { + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + //num metric_name + //43 sessions + if conn.db != nil { + rows, err = conn.db.Query(`select name,value from v$parameter WHERE num=43`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name,&value); err != nil { + break + } + name = cleanName(name) + e.parameter.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } + } + } +} + + // ScrapeServices collects metrics from the v$active_services view. func (e *Exporter) ScrapeServices() { var ( @@ -489,6 +524,7 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { e.up.Describe(ch) e.oraerror.Describe(ch) e.services.Describe(ch) + e.parameter.Describe(ch) } // Connect the DBs and gather Databasename and Instancename @@ -527,6 +563,7 @@ func (e *Exporter) Connect() { e.uptime.Reset() e.oraerror.Reset() e.services.Reset() + e.parameter.Reset() } // Close Connections @@ -592,6 +629,9 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.ScrapeServices() e.services.Collect(ch) + e.ScrapeParameter() + e.parameter.Collect(ch) + ch <- e.duration ch <- e.totalScrapes ch <- e.error From 31b8639a8b58a2223df45f1dcfb210573577be2a Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Wed, 11 Apr 2018 10:38:41 +0200 Subject: [PATCH 10/27] Parameters added --- README.md | 4 ++-- main.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 70b57d9..ef6339d 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Usage of ./prometheus_oracle_exporter: -configfile string ConfigurationFile in YAML format. (default "oracle.conf") -web.listen-address string - Address to listen on for web interface and telemetry. (default ":9161") + Address to listen on for web interface and telemetry. (default ":9161") -web.telemetry-path string - Path under which to expose metrics. (default "/metrics") + Path under which to expose metrics. (default "/metrics") ``` diff --git a/main.go b/main.go index 50751ef..e6c5ea2 100644 --- a/main.go +++ b/main.go @@ -368,9 +368,9 @@ func (e *Exporter) ScrapeTablespace() { if err := rows.Scan(&name, &contents, &tsize, &tfree); err != nil { break } - e.tablespace.WithLabelValues(conn.Database,conn.Instance,name,"total",contents).Set(tsize) - e.tablespace.WithLabelValues(conn.Database,conn.Instance,name,"free",contents).Set(tfree) - e.tablespace.WithLabelValues(conn.Database,conn.Instance,name,"used",contents).Set(tsize-tfree) + e.tablespace.WithLabelValues(conn.Database,conn.Instance,"total",name,contents).Set(tsize) + e.tablespace.WithLabelValues(conn.Database,conn.Instance,"free",name,contents).Set(tfree) + e.tablespace.WithLabelValues(conn.Database,conn.Instance,"used",name,contents).Set(tsize-tfree) } } } From 9183a81a0f33694833ddbc4d68343605098a91de Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Wed, 11 Apr 2018 14:40:06 +0200 Subject: [PATCH 11/27] Buxfix --- alertlog.go | 13 +++++++------ main.go | 2 +- misc.go | 7 +++++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/alertlog.go b/alertlog.go index 24843a9..d3431fa 100644 --- a/alertlog.go +++ b/alertlog.go @@ -4,6 +4,7 @@ import ( "bufio" "os" "time" + _ "fmt" "regexp" "strings" "github.com/prometheus/common/log" @@ -12,7 +13,7 @@ import ( type oraerr struct { ora string text string - ignore int + ignore bool count int } @@ -27,10 +28,10 @@ func addError(conf int, ora string, text string){ } } if ! found { - ignore := 0 + ignore := false for _ , e := range config.Cfgs[conf].Alertlog[0].Ignoreora { if e == ora { - ignore = 1 + ignore = true } } i := strings.Index(text, " ") @@ -55,8 +56,6 @@ func (e *Exporter) ScrapeOraerror() { if err != nil { log.Infoln(err) } else{ - file.Close() - scanner := bufio.NewScanner(file) for scanner.Scan() { t, err := time.ParseInLocation(layout, scanner.Text(),loc) @@ -71,11 +70,13 @@ func (e *Exporter) ScrapeOraerror() { } } } + file.Close() for i, _ := range Errors { e.oraerror.WithLabelValues(config.Cfgs[conf].Database, config.Cfgs[conf].Instance, Errors[i].ora, - Errors[i].text).Set(float64(Errors[i].count)) + Errors[i].text, + FormatBool(Errors[i].ignore)).Set(float64(Errors[i].count)) } } } diff --git a/main.go b/main.go index e6c5ea2..ce76e7b 100644 --- a/main.go +++ b/main.go @@ -152,7 +152,7 @@ func NewExporter() *Exporter { Namespace: namespace, Name: "error", Help: "Oracle Errors occured during configured interval.", - }, []string{"database","dbinstance","type","name"}), + }, []string{"database","dbinstance","code","description","ignore"}), services: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "services", diff --git a/misc.go b/misc.go index 1428230..9f9a4cc 100644 --- a/misc.go +++ b/misc.go @@ -7,6 +7,13 @@ import ( "github.com/prometheus/common/log" ) +func FormatBool(b bool) string { + if b { + return "1" + } + return "0" +} + // Oracle gives us some ugly names back. This function cleans things up for Prometheus. func cleanName(s string) string { s = strings.Replace(s, " ", "_", -1) // Remove spaces From 72d85d9b9fb848ab0d044e74f096abdab5a06aba Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Thu, 12 Apr 2018 09:46:13 +0200 Subject: [PATCH 12/27] Alertlog change --- .gitignore | 1 + README.md | 1 - alertlog.go | 10 +++++++--- main.go | 4 +++- misc.go | 16 ++++++++++++++++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 6793b16..21b08da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ oracle.conf prometheus_oracle_exporter +prometheus_*.dat diff --git a/README.md b/README.md index ef6339d..41a1c27 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Prometheus Oracle Exporter A [Prometheus](https://prometheus.io/) exporter for Oracle. -Insipred from (https://github.com/iamseth/oracledb_exporter). The following metrics are exposed currently. Support for RAC (databasename and instancename added via lables) diff --git a/alertlog.go b/alertlog.go index d3431fa..e42be85 100644 --- a/alertlog.go +++ b/alertlog.go @@ -4,7 +4,6 @@ import ( "bufio" "os" "time" - _ "fmt" "regexp" "strings" "github.com/prometheus/common/log" @@ -44,7 +43,6 @@ func addError(conf int, ora string, text string){ } func (e *Exporter) ScrapeOraerror() { - layout := "Mon Jan 02 15:04:05 2006" loc := time.Now().Location() re := regexp.MustCompile(`ORA-[0-9]+`) @@ -62,7 +60,7 @@ func (e *Exporter) ScrapeOraerror() { if err == nil { lastTime = t } else { - if int(time.Now().Sub(lastTime).Seconds()) < config.Cfgs[conf].Alertlog[0].Scantime { + if lastTime.After(config.Cfgs[conf].Alertlog[0].lasttime) { if re.MatchString(scanner.Text()) { ora := re.FindString(scanner.Text()) addError(conf,ora, scanner.Text()) @@ -71,6 +69,12 @@ func (e *Exporter) ScrapeOraerror() { } } file.Close() + // Write last known date from alertlog + file, err := os.Create(config.Cfgs[conf].Alertlog[0].lastfile) + if err == nil { + file.WriteString(lastTime.String()) + file.Close() + } for i, _ := range Errors { e.oraerror.WithLabelValues(config.Cfgs[conf].Database, config.Cfgs[conf].Instance, diff --git a/main.go b/main.go index ce76e7b..92ccb8c 100644 --- a/main.go +++ b/main.go @@ -18,8 +18,9 @@ const ( type Alert struct { File string `yaml:"file"` - Scantime int `yaml:"scantime"` Ignoreora []string `yaml:"ignoreora"` + lastfile string + lasttime time.Time } type Config struct { @@ -63,6 +64,7 @@ var ( configFile = flag.String("configfile", "oracle.conf", "ConfigurationFile in YAML format.") landingPage = []byte("Prometheus Oracle exporter

Prometheus Oracle exporter

Metrics

") config Configs + layout = "Mon Jan 02 15:04:05 2006" ) diff --git a/misc.go b/misc.go index 9f9a4cc..b133d43 100644 --- a/misc.go +++ b/misc.go @@ -5,6 +5,9 @@ import ( "io/ioutil" "gopkg.in/yaml.v2" "github.com/prometheus/common/log" + "os" + "strconv" + "time" ) func FormatBool(b bool) string { @@ -25,6 +28,10 @@ func cleanName(s string) string { } func loadConfig() bool { + pwd, err := os.Getwd() + if err != nil { + log.Fatalf("error: %v", err) + } content, err := ioutil.ReadFile(*configFile) if err != nil { log.Fatalf("error: %v", err) @@ -35,6 +42,15 @@ func loadConfig() bool { log.Fatalf("error: %v", err) return false } + for conf, _ := range config.Cfgs { + file := pwd + "/prometheus_" + strconv.Itoa(conf) + ".dat" + config.Cfgs[conf].Alertlog[0].lastfile = file + content, err := ioutil.ReadFile(file) + if err == nil { + t, _ := time.Parse(layout,string(content)) + config.Cfgs[conf].Alertlog[0].lasttime = t + } + } return true } } From 5695aef2228108c0660abe339ce63d38fffc448f Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Thu, 12 Apr 2018 09:57:37 +0200 Subject: [PATCH 13/27] Alertlog change --- misc.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/misc.go b/misc.go index b133d43..92d6152 100644 --- a/misc.go +++ b/misc.go @@ -6,7 +6,6 @@ import ( "gopkg.in/yaml.v2" "github.com/prometheus/common/log" "os" - "strconv" "time" ) @@ -43,12 +42,14 @@ func loadConfig() bool { return false } for conf, _ := range config.Cfgs { - file := pwd + "/prometheus_" + strconv.Itoa(conf) + ".dat" + file := pwd + "/prometheus_" + config.Cfgs[conf].Instance + ".dat" config.Cfgs[conf].Alertlog[0].lastfile = file content, err := ioutil.ReadFile(file) if err == nil { t, _ := time.Parse(layout,string(content)) config.Cfgs[conf].Alertlog[0].lasttime = t + }else{ + config.Cfgs[conf].Alertlog[0].lasttime = time.Now() } } return true From c94d17a9e911592d8c70dca83919e60a6198da4a Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Thu, 12 Apr 2018 10:22:55 +0200 Subject: [PATCH 14/27] Alertlog change --- misc.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misc.go b/misc.go index 92d6152..d94d254 100644 --- a/misc.go +++ b/misc.go @@ -7,6 +7,7 @@ import ( "github.com/prometheus/common/log" "os" "time" + "path/filepath" ) func FormatBool(b bool) string { @@ -27,7 +28,7 @@ func cleanName(s string) string { } func loadConfig() bool { - pwd, err := os.Getwd() + pwd, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { log.Fatalf("error: %v", err) } From 3e605f04fae35a72d18f026675f851486286f9df Mon Sep 17 00:00:00 2001 From: Christoph Hoth Date: Thu, 12 Apr 2018 11:44:25 +0200 Subject: [PATCH 15/27] adds client ip gathering --- alertlog.go | 2 ++ main.go | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/alertlog.go b/alertlog.go index e42be85..5f0da0f 100644 --- a/alertlog.go +++ b/alertlog.go @@ -43,6 +43,8 @@ func addError(conf int, ora string, text string){ } func (e *Exporter) ScrapeOraerror() { + log.Infoln("Request from: " + e.lastIp) + loc := time.Now().Location() re := regexp.MustCompile(`ORA-[0-9]+`) diff --git a/main.go b/main.go index 92ccb8c..f021b26 100644 --- a/main.go +++ b/main.go @@ -3,9 +3,10 @@ package main import ( "database/sql" "flag" + "net" "net/http" "time" - _ "github.com/mattn/go-oci8" +// _ "github.com/mattn/go-oci8" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/log" ) @@ -54,6 +55,8 @@ type Exporter struct { oraerror *prometheus.GaugeVec services *prometheus.GaugeVec parameter *prometheus.GaugeVec + + lastIp string } var ( @@ -642,6 +645,13 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.Close() } +func (e *Exporter) Handler(w http.ResponseWriter, r *http.Request) { + ip, _, _ := net.SplitHostPort(r.RemoteAddr) + //TODO: error handling + e.lastIp = ip + prometheus.Handler().ServeHTTP(w, r) +} + func main() { flag.Parse() log.Infoln("Starting Prometheus Oracle exporter " + Version) @@ -650,7 +660,10 @@ func main() { log.Infoln("Config loaded: ", *configFile) exporter := NewExporter() prometheus.MustRegister(exporter) - http.Handle(*metricPath, prometheus.Handler()) + +// http.Handle(*metricPath, prometheus.Handler()) + http.HandleFunc(*metricPath, exporter.Handler) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write(landingPage)}) log.Infoln("Listening on", *listenAddress) From 0c081bc4741e497de4e1dcc1f1b0445056f01c2b Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Thu, 12 Apr 2018 14:09:28 +0200 Subject: [PATCH 16/27] Alertlog change --- README.md | 4 +- alertlog.go | 41 +- main.go | 1041 ++++++++++++++++++++++++++------------------------- misc.go | 67 ++-- 4 files changed, 583 insertions(+), 570 deletions(-) diff --git a/README.md b/README.md index 41a1c27..18bbc65 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ The following metrics are exposed currently. Support for RAC (databasename and i - oracledb_uptime (days) - oracledb_session (view v$session system/user active/passive) - oracledb_sysmetric (view v$sysmetric - (Physical Read Total IO Requests Per Sec / Physical Write Total IO Requests Per Sec - Physical Read Total Bytes Per Sec / Physical Write Total Bytes Per Sec)) + (Physical Read Total IO Requests Per Sec / Physical Write Total IO Requests Per Sec + Physical Read Total Bytes Per Sec / Physical Write Total Bytes Per Sec)) - oracledb_sysstat (view v$sysstat (parse count (total) / execute count / user commits / user rollbacks)) - oracledb_waitclass (view v$waitclass) - oracledb_tablespace (tablespace total/free) diff --git a/alertlog.go b/alertlog.go index 5f0da0f..53c95e8 100644 --- a/alertlog.go +++ b/alertlog.go @@ -6,18 +6,39 @@ import ( "time" "regexp" "strings" + "io/ioutil" "github.com/prometheus/common/log" ) type oraerr struct { - ora string - text string + ora string + text string ignore bool - count int + count int } var Errors []oraerr +func (e *Exporter) GetLastScrapeTime(conf int) time.Time { + file := pwd + "/prometheus_" + config.Cfgs[conf].Instance + "_" + cleanIp(e.lastIp) + ".dat" + content, err := ioutil.ReadFile(file) + if err == nil { + t, _ := time.Parse("2006-01-02 15:04:05 -0700 MST",string(content)) + return t + } + return time.Now() +} + +func (e *Exporter) SetLastScrapeTime(conf int,t time.Time) { + file := pwd + "/prometheus_" + config.Cfgs[conf].Instance + "_" + cleanIp(e.lastIp) + ".dat" + fh, err := os.Create(file) + if err == nil { + fh.WriteString(t.String()) + fh.Close() + } +} + + func addError(conf int, ora string, text string){ var found bool = false for i, _ := range Errors { @@ -43,14 +64,13 @@ func addError(conf int, ora string, text string){ } func (e *Exporter) ScrapeOraerror() { - log.Infoln("Request from: " + e.lastIp) - loc := time.Now().Location() re := regexp.MustCompile(`ORA-[0-9]+`) for conf, _ := range config.Cfgs { var lastTime time.Time Errors = nil + lastScrapeTime := e.GetLastScrapeTime(conf) file, err := os.Open(config.Cfgs[conf].Alertlog[0].File) if err != nil { @@ -58,11 +78,11 @@ func (e *Exporter) ScrapeOraerror() { } else{ scanner := bufio.NewScanner(file) for scanner.Scan() { - t, err := time.ParseInLocation(layout, scanner.Text(),loc) + t, err := time.ParseInLocation(oralayout, scanner.Text(),loc) if err == nil { lastTime = t } else { - if lastTime.After(config.Cfgs[conf].Alertlog[0].lasttime) { + if lastTime.After(lastScrapeTime) { if re.MatchString(scanner.Text()) { ora := re.FindString(scanner.Text()) addError(conf,ora, scanner.Text()) @@ -71,12 +91,7 @@ func (e *Exporter) ScrapeOraerror() { } } file.Close() - // Write last known date from alertlog - file, err := os.Create(config.Cfgs[conf].Alertlog[0].lastfile) - if err == nil { - file.WriteString(lastTime.String()) - file.Close() - } + e.SetLastScrapeTime(conf,lastTime) for i, _ := range Errors { e.oraerror.WithLabelValues(config.Cfgs[conf].Database, config.Cfgs[conf].Instance, diff --git a/main.go b/main.go index f021b26..004104d 100644 --- a/main.go +++ b/main.go @@ -1,602 +1,602 @@ package main import ( - "database/sql" - "flag" - "net" - "net/http" - "time" -// _ "github.com/mattn/go-oci8" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/common/log" + "database/sql" + "flag" + "net" + "net/http" + "time" + _ "github.com/mattn/go-oci8" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/log" ) // Metric name parts. const ( - namespace = "oracledb" - exporter = "exporter" + namespace = "oracledb" + exporter = "exporter" ) type Alert struct { - File string `yaml:"file"` - Ignoreora []string `yaml:"ignoreora"` - lastfile string - lasttime time.Time + File string `yaml:"file"` + Ignoreora []string `yaml:"ignoreora"` + lastfile string + lasttime time.Time } type Config struct { - Connection string `yaml:"connection"` - Database string `yaml:"database"` - Instance string `yaml:"instance"` - Alertlog []Alert `yaml:"alertlog"` - db *sql.DB + Connection string `yaml:"connection"` + Database string `yaml:"database"` + Instance string `yaml:"instance"` + Alertlog []Alert `yaml:"alertlog"` + db *sql.DB } type Configs struct { - Cfgs []Config `yaml:"connections"` + Cfgs []Config `yaml:"connections"` } // Exporter collects Oracle DB metrics. It implements prometheus.Collector. type Exporter struct { - duration, error prometheus.Gauge - totalScrapes prometheus.Counter - scrapeErrors *prometheus.CounterVec + duration, error prometheus.Gauge + totalScrapes prometheus.Counter + scrapeErrors *prometheus.CounterVec session *prometheus.GaugeVec sysstat *prometheus.GaugeVec waitclass *prometheus.GaugeVec - sysmetric *prometheus.GaugeVec - interconnect *prometheus.GaugeVec - uptime *prometheus.GaugeVec - up *prometheus.GaugeVec - tablespace *prometheus.GaugeVec - recovery *prometheus.GaugeVec - redo *prometheus.GaugeVec - cache *prometheus.GaugeVec - oraerror *prometheus.GaugeVec + sysmetric *prometheus.GaugeVec + interconnect *prometheus.GaugeVec + uptime *prometheus.GaugeVec + up *prometheus.GaugeVec + tablespace *prometheus.GaugeVec + recovery *prometheus.GaugeVec + redo *prometheus.GaugeVec + cache *prometheus.GaugeVec + oraerror *prometheus.GaugeVec services *prometheus.GaugeVec - parameter *prometheus.GaugeVec - - lastIp string + parameter *prometheus.GaugeVec + lastIp string } var ( - // Version will be set at build time. - Version = "1.0.0" - listenAddress = flag.String("web.listen-address", ":9161", "Address to listen on for web interface and telemetry.") - metricPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") - configFile = flag.String("configfile", "oracle.conf", "ConfigurationFile in YAML format.") - landingPage = []byte("Prometheus Oracle exporter

Prometheus Oracle exporter

Metrics

") - config Configs - layout = "Mon Jan 02 15:04:05 2006" + // Version will be set at build time. + Version = "1.0.0" + listenAddress = flag.String("web.listen-address", ":9161", "Address to listen on for web interface and telemetry.") + metricPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") + configFile = flag.String("configfile", "oracle.conf", "ConfigurationFile in YAML format.") + landingPage = []byte("Prometheus Oracle exporter

Prometheus Oracle exporter

Metrics

") + config Configs + oralayout = "Mon Jan 02 15:04:05 2006" + pwd string ) // NewExporter returns a new Oracle DB exporter for the provided DSN. func NewExporter() *Exporter { - return &Exporter{ - duration: prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: exporter, - Name: "last_scrape_duration_seconds", - Help: "Duration of the last scrape of metrics from Oracle DB.", - }), - totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: namespace, - Subsystem: exporter, - Name: "scrapes_total", - Help: "Total number of times Oracle DB was scraped for metrics.", - }), - scrapeErrors: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: namespace, - Subsystem: exporter, - Name: "scrape_errors_total", - Help: "Total number of times an error occured scraping a Oracle database.", - }, []string{"collector"}), - error: prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: exporter, - Name: "last_scrape_error", - Help: "Whether the last scrape of metrics from Oracle DB resulted in an error (1 for error, 0 for success).", - }), - sysmetric: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "sysmetric", - Help: "Gauge metric with read/write pysical IOPs/bytes (v$sysmetric).", - }, []string{"database","dbinstance","type"}), - waitclass: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "waitclass", - Help: "Gauge metric with Waitevents (v$waitclassmetric).", - }, []string{"database","dbinstance","type"}), - sysstat: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "sysstat", - Help: "Gauge metric with commits/rollbacks/parses (v$sysstat).", - }, []string{"database","dbinstance","type"}), - session: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "session", - Help: "Gauge metric user/system active/passive sessions (v$session).", - }, []string{"database","dbinstance","type","state"}), - uptime: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "uptime", - Help: "Gauge metric with uptime in days of the Instance.", - }, []string{"database","dbinstance"}), - tablespace: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "tablespace", - Help: "Gauge metric with total/free size of the Tablespaces.", - }, []string{"database","dbinstance","type","name","contents"}), - interconnect: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "interconnect", - Help: "Gauge metric with interconnect block transfers (v$sysstat).", - }, []string{"database","dbinstance","type"}), - recovery: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "recovery", - Help: "Gauge metric with percentage usage of FRA (v$recovery_file_dest).", - }, []string{"database","dbinstance","type"}), - redo: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "redo", - Help: "Gauge metric with Redo log switches over last 5 min (v$log_history).", - }, []string{"database","dbinstance"}), - cache: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "cachehitratio", - Help: "Gauge metric witch Cache hit ratios (v$sysmetric).", - }, []string{"database","dbinstance","type"}), - up: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "up", - Help: "Whether the Oracle server is up.", - }, []string{"database","dbinstance"}), - oraerror: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "error", - Help: "Oracle Errors occured during configured interval.", - }, []string{"database","dbinstance","code","description","ignore"}), - services: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "services", - Help: "Active Oracle Services (v$active_services).", - }, []string{"database","dbinstance","name"}), - parameter: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "parameter", - Help: "oracle Configuration Parameters (v$parameter).", - }, []string{"database","dbinstance","name"}), - } + return &Exporter{ + duration: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: exporter, + Name: "last_scrape_duration_seconds", + Help: "Duration of the last scrape of metrics from Oracle DB.", + }), + totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: exporter, + Name: "scrapes_total", + Help: "Total number of times Oracle DB was scraped for metrics.", + }), + scrapeErrors: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: exporter, + Name: "scrape_errors_total", + Help: "Total number of times an error occured scraping a Oracle database.", + }, []string{"collector"}), + error: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: exporter, + Name: "last_scrape_error", + Help: "Whether the last scrape of metrics from Oracle DB resulted in an error (1 for error, 0 for success).", + }), + sysmetric: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "sysmetric", + Help: "Gauge metric with read/write pysical IOPs/bytes (v$sysmetric).", + }, []string{"database","dbinstance","type"}), + waitclass: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "waitclass", + Help: "Gauge metric with Waitevents (v$waitclassmetric).", + }, []string{"database","dbinstance","type"}), + sysstat: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "sysstat", + Help: "Gauge metric with commits/rollbacks/parses (v$sysstat).", + }, []string{"database","dbinstance","type"}), + session: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "session", + Help: "Gauge metric user/system active/passive sessions (v$session).", + }, []string{"database","dbinstance","type","state"}), + uptime: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "uptime", + Help: "Gauge metric with uptime in days of the Instance.", + }, []string{"database","dbinstance"}), + tablespace: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "tablespace", + Help: "Gauge metric with total/free size of the Tablespaces.", + }, []string{"database","dbinstance","type","name","contents"}), + interconnect: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "interconnect", + Help: "Gauge metric with interconnect block transfers (v$sysstat).", + }, []string{"database","dbinstance","type"}), + recovery: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "recovery", + Help: "Gauge metric with percentage usage of FRA (v$recovery_file_dest).", + }, []string{"database","dbinstance","type"}), + redo: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "redo", + Help: "Gauge metric with Redo log switches over last 5 min (v$log_history).", + }, []string{"database","dbinstance"}), + cache: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "cachehitratio", + Help: "Gauge metric witch Cache hit ratios (v$sysmetric).", + }, []string{"database","dbinstance","type"}), + up: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "up", + Help: "Whether the Oracle server is up.", + }, []string{"database","dbinstance"}), + oraerror: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "error", + Help: "Oracle Errors occured during configured interval.", + }, []string{"database","dbinstance","code","description","ignore"}), + services: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "services", + Help: "Active Oracle Services (v$active_services).", + }, []string{"database","dbinstance","name"}), + parameter: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "parameter", + Help: "oracle Configuration Parameters (v$parameter).", + }, []string{"database","dbinstance","name"}), + } } // ScrapeParameters collects metrics from the v$parameters view. func (e *Exporter) ScrapeParameter() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - //num metric_name - //43 sessions - if conn.db != nil { - rows, err = conn.db.Query(`select name,value from v$parameter WHERE num=43`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name,&value); err != nil { - break - } - name = cleanName(name) - e.parameter.WithLabelValues(conn.Database,conn.Instance,name).Set(value) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + //num metric_name + //43 sessions + if conn.db != nil { + rows, err = conn.db.Query(`select name,value from v$parameter WHERE num=43`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name,&value); err != nil { + break + } + name = cleanName(name) + e.parameter.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } + } + } } // ScrapeServices collects metrics from the v$active_services view. func (e *Exporter) ScrapeServices() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - if conn.db != nil { - rows, err = conn.db.Query(`select name from v$active_services`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - if err := rows.Scan(&name); err != nil { - break - } - name = cleanName(name) - e.services.WithLabelValues(conn.Database,conn.Instance,name).Set(1) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`select name from v$active_services`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + break + } + name = cleanName(name) + e.services.WithLabelValues(conn.Database,conn.Instance,name).Set(1) + } + } + } } // ScrapeCache collects session metrics from the v$sysmetrics view. func (e *Exporter) ScrapeCache() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - //metric_id metric_name - //2000 Buffer Cache Hit Ratio - //2050 Cursor Cache Hit Ratio - //2112 Library Cache Hit Ratio - //2110 Row Cache Hit Ratio - if conn.db != nil { - rows, err = conn.db.Query(`select metric_name,value - from v$sysmetric - where group_id=2 and metric_id in (2000,2050,2112,2110)`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name, &value); err != nil { - break - } - name = cleanName(name) - e.cache.WithLabelValues(conn.Database,conn.Instance,name).Set(value) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + //metric_id metric_name + //2000 Buffer Cache Hit Ratio + //2050 Cursor Cache Hit Ratio + //2112 Library Cache Hit Ratio + //2110 Row Cache Hit Ratio + if conn.db != nil { + rows, err = conn.db.Query(`select metric_name,value + from v$sysmetric + where group_id=2 and metric_id in (2000,2050,2112,2110)`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name, &value); err != nil { + break + } + name = cleanName(name) + e.cache.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } + } + } } // ScrapeRecovery collects tablespace metrics func (e *Exporter) ScrapeRedo() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - if conn.db != nil { - rows, err = conn.db.Query(`select count(*) from v$log_history where first_time > sysdate - 1/24/12`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var value float64 - if err := rows.Scan(&value); err != nil { - break - } - e.redo.WithLabelValues(conn.Database,conn.Instance).Set(value) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`select count(*) from v$log_history where first_time > sysdate - 1/24/12`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var value float64 + if err := rows.Scan(&value); err != nil { + break + } + e.redo.WithLabelValues(conn.Database,conn.Instance).Set(value) + } + } + } } // ScrapeRecovery collects tablespace metrics func (e *Exporter) ScrapeRecovery() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - if conn.db != nil { - rows, err = conn.db.Query(`SELECT sum(percent_space_used) , sum(percent_space_reclaimable) - from V$FLASH_RECOVERY_AREA_USAGE`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var used float64 - var recl float64 - if err := rows.Scan(&used, &recl); err != nil { - break - } - e.recovery.WithLabelValues(conn.Database,conn.Instance,"percent_space_used").Set(used) - e.recovery.WithLabelValues(conn.Database,conn.Instance,"percent_space_reclaimable").Set(recl) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT sum(percent_space_used) , sum(percent_space_reclaimable) + from V$FLASH_RECOVERY_AREA_USAGE`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var used float64 + var recl float64 + if err := rows.Scan(&used, &recl); err != nil { + break + } + e.recovery.WithLabelValues(conn.Database,conn.Instance,"percent_space_used").Set(used) + e.recovery.WithLabelValues(conn.Database,conn.Instance,"percent_space_reclaimable").Set(recl) + } + } + } } // ScrapeTablespaces collects tablespace metrics func (e *Exporter) ScrapeInterconnect() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - if conn.db != nil { - rows, err = conn.db.Query(`SELECT name, value - FROM V$SYSSTAT - WHERE name in ('gc cr blocks served','gc cr blocks flushed','gc cr blocks received')`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name, &value); err != nil { - break - } - name = cleanName(name) - e.interconnect.WithLabelValues(conn.Database,conn.Instance,name).Set(value) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT name, value + FROM V$SYSSTAT + WHERE name in ('gc cr blocks served','gc cr blocks flushed','gc cr blocks received')`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name, &value); err != nil { + break + } + name = cleanName(name) + e.interconnect.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } + } + } } // ScrapeTablespaces collects tablespace metrics func (e *Exporter) ScrapeTablespace() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - if conn.db != nil { - rows, err = conn.db.Query(`WITH - getsize AS (SELECT tablespace_name, SUM(bytes) tsize - FROM dba_data_files GROUP BY tablespace_name), - getfree as (SELECT tablespace_name, contents, SUM(blocks*block_size) tfree - FROM DBA_LMT_FREE_SPACE a, v$tablespace b, dba_tablespaces c - WHERE a.TABLESPACE_ID= b.ts# and b.name=c.tablespace_name - GROUP BY tablespace_name,contents) - SELECT a.tablespace_name, b.contents, a.tsize, b.tfree - FROM GETSIZE a, GETFREE b - WHERE a.tablespace_name = b.tablespace_name - UNION - SELECT tablespace_name, 'TEMPORARY', sum(tablespace_size), sum(free_space) - FROM dba_temp_free_space - GROUP BY tablespace_name`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var contents string - var tsize float64 - var tfree float64 - if err := rows.Scan(&name, &contents, &tsize, &tfree); err != nil { - break - } - e.tablespace.WithLabelValues(conn.Database,conn.Instance,"total",name,contents).Set(tsize) - e.tablespace.WithLabelValues(conn.Database,conn.Instance,"free",name,contents).Set(tfree) - e.tablespace.WithLabelValues(conn.Database,conn.Instance,"used",name,contents).Set(tsize-tfree) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`WITH + getsize AS (SELECT tablespace_name, SUM(bytes) tsize + FROM dba_data_files GROUP BY tablespace_name), + getfree as (SELECT tablespace_name, contents, SUM(blocks*block_size) tfree + FROM DBA_LMT_FREE_SPACE a, v$tablespace b, dba_tablespaces c + WHERE a.TABLESPACE_ID= b.ts# and b.name=c.tablespace_name + GROUP BY tablespace_name,contents) + SELECT a.tablespace_name, b.contents, a.tsize, b.tfree + FROM GETSIZE a, GETFREE b + WHERE a.tablespace_name = b.tablespace_name + UNION + SELECT tablespace_name, 'TEMPORARY', sum(tablespace_size), sum(free_space) + FROM dba_temp_free_space + GROUP BY tablespace_name`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + var contents string + var tsize float64 + var tfree float64 + if err := rows.Scan(&name, &contents, &tsize, &tfree); err != nil { + break + } + e.tablespace.WithLabelValues(conn.Database,conn.Instance,"total",name,contents).Set(tsize) + e.tablespace.WithLabelValues(conn.Database,conn.Instance,"free",name,contents).Set(tfree) + e.tablespace.WithLabelValues(conn.Database,conn.Instance,"used",name,contents).Set(tsize-tfree) + } + } + } } // ScrapeSessions collects session metrics from the v$session view. func (e *Exporter) ScrapeSession() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - if conn.db != nil { - rows, err = conn.db.Query(`SELECT decode(username,NULL,'SYSTEM','SYS','SYSTEM','USER'), status,count(*) - FROM v$session - GROUP BY decode(username,NULL,'SYSTEM','SYS','SYSTEM','USER'),status`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var user string - var status string - var value float64 - if err := rows.Scan(&user, &status, &value); err != nil { - break - } - e.session.WithLabelValues(conn.Database,conn.Instance,user,status).Set(value) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT decode(username,NULL,'SYSTEM','SYS','SYSTEM','USER'), status,count(*) + FROM v$session + GROUP BY decode(username,NULL,'SYSTEM','SYS','SYSTEM','USER'),status`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var user string + var status string + var value float64 + if err := rows.Scan(&user, &status, &value); err != nil { + break + } + e.session.WithLabelValues(conn.Database,conn.Instance,user,status).Set(value) + } + } + } } // ScrapeUptime Instance uptime func (e *Exporter) ScrapeUptime() { - var uptime float64 - for _, conn := range config.Cfgs { - if conn.db != nil { - err := conn.db.QueryRow("select sysdate-startup_time from v$instance").Scan(&uptime) - if err != nil { - return - } - e.uptime.WithLabelValues(conn.Database,conn.Instance).Set(uptime) - } - } + var uptime float64 + for _, conn := range config.Cfgs { + if conn.db != nil { + err := conn.db.QueryRow("select sysdate-startup_time from v$instance").Scan(&uptime) + if err != nil { + return + } + e.uptime.WithLabelValues(conn.Database,conn.Instance).Set(uptime) + } + } } // ScrapeSysstat collects activity metrics from the v$sysstat view. func (e *Exporter) ScrapeSysstat() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - if conn.db != nil { - rows, err = conn.db.Query(`SELECT name, value FROM v$sysstat - WHERE statistic# in (6,7,1084,1089)`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name, &value); err != nil { - break - } - name = cleanName(name) - e.sysstat.WithLabelValues(conn.Database,conn.Instance,name).Set(value) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT name, value FROM v$sysstat + WHERE statistic# in (6,7,1084,1089)`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name, &value); err != nil { + break + } + name = cleanName(name) + e.sysstat.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } + } + } } // ScrapeWaitTime collects wait time metrics from the v$waitclassmetric view. func (e *Exporter) ScrapeWaitclass() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - if conn.db != nil { - rows, err = conn.db.Query(`SELECT n.wait_class, round(m.time_waited/m.INTSIZE_CSEC,3) - FROM v$waitclassmetric m, v$system_wait_class n - WHERE m.wait_class_id=n.wait_class_id and n.wait_class != 'Idle'`) - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name, &value); err != nil { - break - } - name = cleanName(name) - e.waitclass.WithLabelValues(conn.Database,conn.Instance,name).Set(value) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT n.wait_class, round(m.time_waited/m.INTSIZE_CSEC,3) + FROM v$waitclassmetric m, v$system_wait_class n + WHERE m.wait_class_id=n.wait_class_id and n.wait_class != 'Idle'`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name, &value); err != nil { + break + } + name = cleanName(name) + e.waitclass.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } + } + } } // ScrapeSysmetrics collects session metrics from the v$sysmetrics view. func (e *Exporter) ScrapeSysmetric() { - var ( - rows *sql.Rows - err error - ) - for _, conn := range config.Cfgs { - //metric_id metric_name - //2092 Physical Read Total IO Requests Per Sec - //2093 Physical Read Total Bytes Per Sec - //2100 Physical Write Total IO Requests Per Sec - //2124 Physical Write Total Bytes Per Sec - if conn.db != nil { - rows, err = conn.db.Query("select metric_name,value from v$sysmetric where metric_id in (2092,2093,2124,2100)") - if err != nil { - break - } - defer rows.Close() - for rows.Next() { - var name string - var value float64 - if err := rows.Scan(&name, &value); err != nil { - break - } - name = cleanName(name) - e.sysmetric.WithLabelValues(conn.Database,conn.Instance,name).Set(value) - } - } - } + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + //metric_id metric_name + //2092 Physical Read Total IO Requests Per Sec + //2093 Physical Read Total Bytes Per Sec + //2100 Physical Write Total IO Requests Per Sec + //2124 Physical Write Total Bytes Per Sec + if conn.db != nil { + rows, err = conn.db.Query("select metric_name,value from v$sysmetric where metric_id in (2092,2093,2124,2100)") + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + var value float64 + if err := rows.Scan(&name, &value); err != nil { + break + } + name = cleanName(name) + e.sysmetric.WithLabelValues(conn.Database,conn.Instance,name).Set(value) + } + } + } } // Describe describes all the metrics exported by the Oracle exporter. func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { - e.duration.Describe(ch) - e.totalScrapes.Describe(ch) - e.scrapeErrors.Describe(ch) + e.duration.Describe(ch) + e.totalScrapes.Describe(ch) + e.scrapeErrors.Describe(ch) e.session.Describe(ch) e.sysstat.Describe(ch) e.waitclass.Describe(ch) - e.sysmetric.Describe(ch) - e.interconnect.Describe(ch) + e.sysmetric.Describe(ch) + e.interconnect.Describe(ch) e.tablespace.Describe(ch) e.recovery.Describe(ch) e.redo.Describe(ch) e.cache.Describe(ch) - e.uptime.Describe(ch) - e.up.Describe(ch) - e.oraerror.Describe(ch) - e.services.Describe(ch) - e.parameter.Describe(ch) + e.uptime.Describe(ch) + e.up.Describe(ch) + e.oraerror.Describe(ch) + e.services.Describe(ch) + e.parameter.Describe(ch) } // Connect the DBs and gather Databasename and Instancename func (e *Exporter) Connect() { - var dbname string - var inname string - - for i, conf := range config.Cfgs { - config.Cfgs[i].db = nil - db , err := sql.Open("oci8", conf.Connection) - if err == nil { - err = db.QueryRow("select db_unique_name,instance_name from v$database,v$instance").Scan(&dbname,&inname) - if err == nil { - if (conf.Database != dbname) || (conf.Instance != inname) { - config.Cfgs[i].Database = dbname - config.Cfgs[i].Instance = inname - } - config.Cfgs[i].db = db - e.up.WithLabelValues(conf.Database,conf.Instance).Set(1) - } else { - e.up.WithLabelValues(conf.Database,conf.Instance).Set(0) - } - } else { - e.up.WithLabelValues(conf.Database,conf.Instance).Set(0) - } - } + var dbname string + var inname string + + for i, conf := range config.Cfgs { + config.Cfgs[i].db = nil + db , err := sql.Open("oci8", conf.Connection) + if err == nil { + err = db.QueryRow("select db_unique_name,instance_name from v$database,v$instance").Scan(&dbname,&inname) + if err == nil { + if (conf.Database != dbname) || (conf.Instance != inname) { + config.Cfgs[i].Database = dbname + config.Cfgs[i].Instance = inname + } + config.Cfgs[i].db = db + e.up.WithLabelValues(conf.Database,conf.Instance).Set(1) + } else { + e.up.WithLabelValues(conf.Database,conf.Instance).Set(0) + } + } else { + e.up.WithLabelValues(conf.Database,conf.Instance).Set(0) + } + } e.session.Reset() e.sysstat.Reset() e.waitclass.Reset() - e.sysmetric.Reset() - e.interconnect.Reset() + e.sysmetric.Reset() + e.interconnect.Reset() e.tablespace.Reset() e.recovery.Reset() e.redo.Reset() e.cache.Reset() - e.uptime.Reset() - e.oraerror.Reset() - e.services.Reset() - e.parameter.Reset() + e.uptime.Reset() + e.oraerror.Reset() + e.services.Reset() + e.parameter.Reset() } // Close Connections func (e *Exporter) Close() { - for _, conn := range config.Cfgs { - if conn.db != nil { - conn.db.Close() - } - } + for _, conn := range config.Cfgs { + if conn.db != nil { + conn.db.Close() + } + } } // Collect implements prometheus.Collector. func (e *Exporter) Collect(ch chan<- prometheus.Metric) { - var err error + var err error - e.totalScrapes.Inc() - defer func(begun time.Time) { - e.duration.Set(time.Since(begun).Seconds()) - if err == nil { - e.error.Set(0) - } else { - e.error.Set(1) - } - }(time.Now()) + e.totalScrapes.Inc() + defer func(begun time.Time) { + e.duration.Set(time.Since(begun).Seconds()) + if err == nil { + e.error.Set(0) + } else { + e.error.Set(1) + } + }(time.Now()) - e.Connect() - e.up.Collect(ch) + e.Connect() + e.up.Collect(ch) e.ScrapeUptime() e.uptime.Collect(ch) @@ -607,66 +607,69 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.ScrapeSysstat() e.sysstat.Collect(ch) - e.ScrapeWaitclass() - e.waitclass.Collect(ch) + e.ScrapeWaitclass() + e.waitclass.Collect(ch) - e.ScrapeSysmetric() - e.sysmetric.Collect(ch) + e.ScrapeSysmetric() + e.sysmetric.Collect(ch) - e.ScrapeTablespace() - e.tablespace.Collect(ch) + e.ScrapeTablespace() + e.tablespace.Collect(ch) - e.ScrapeInterconnect() - e.interconnect.Collect(ch) + e.ScrapeInterconnect() + e.interconnect.Collect(ch) - e.ScrapeRecovery() - e.recovery.Collect(ch) + e.ScrapeRecovery() + e.recovery.Collect(ch) - e.ScrapeRedo() - e.redo.Collect(ch) + e.ScrapeRedo() + e.redo.Collect(ch) - e.ScrapeCache() - e.cache.Collect(ch) + e.ScrapeCache() + e.cache.Collect(ch) e.ScrapeOraerror() - e.oraerror.Collect(ch) + e.oraerror.Collect(ch) - e.ScrapeServices() - e.services.Collect(ch) + e.ScrapeServices() + e.services.Collect(ch) - e.ScrapeParameter() - e.parameter.Collect(ch) + e.ScrapeParameter() + e.parameter.Collect(ch) - ch <- e.duration - ch <- e.totalScrapes - ch <- e.error - e.scrapeErrors.Collect(ch) + ch <- e.duration + ch <- e.totalScrapes + ch <- e.error + e.scrapeErrors.Collect(ch) e.Close() } func (e *Exporter) Handler(w http.ResponseWriter, r *http.Request) { - ip, _, _ := net.SplitHostPort(r.RemoteAddr) - //TODO: error handling - e.lastIp = ip + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err == nil { + e.lastIp = ip + } else { + e.lastIp = "" + } prometheus.Handler().ServeHTTP(w, r) } func main() { - flag.Parse() - log.Infoln("Starting Prometheus Oracle exporter " + Version) - if loadConfig() { + flag.Parse() + log.Infoln("Starting Prometheus Oracle exporter " + Version) + if loadConfig() { - log.Infoln("Config loaded: ", *configFile) - exporter := NewExporter() - prometheus.MustRegister(exporter) + log.Infoln("Config loaded: ", *configFile) + exporter := NewExporter() + prometheus.MustRegister(exporter) -// http.Handle(*metricPath, prometheus.Handler()) - http.HandleFunc(*metricPath, exporter.Handler) +// http.Handle(*metricPath, prometheus.Handler()) + http.HandleFunc(*metricPath, exporter.Handler) - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write(landingPage)}) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write(landingPage)}) - log.Infoln("Listening on", *listenAddress) - log.Fatal(http.ListenAndServe(*listenAddress, nil)) - } + log.Infoln("Listening on", *listenAddress) + log.Fatal(http.ListenAndServe(*listenAddress, nil)) + } } diff --git a/misc.go b/misc.go index d94d254..bb4714a 100644 --- a/misc.go +++ b/misc.go @@ -1,58 +1,53 @@ package main import ( - "strings" + "strings" "io/ioutil" "gopkg.in/yaml.v2" "github.com/prometheus/common/log" - "os" - "time" - "path/filepath" + "os" + "path/filepath" ) func FormatBool(b bool) string { - if b { - return "1" - } - return "0" + if b { + return "1" + } + return "0" } // Oracle gives us some ugly names back. This function cleans things up for Prometheus. func cleanName(s string) string { - s = strings.Replace(s, " ", "_", -1) // Remove spaces - s = strings.Replace(s, "(", "", -1) // Remove open parenthesis - s = strings.Replace(s, ")", "", -1) // Remove close parenthesis - s = strings.Replace(s, "/", "", -1) // Remove forward slashes - s = strings.ToLower(s) - return s + s = strings.Replace(s, " ", "_", -1) // Remove spaces + s = strings.Replace(s, "(", "", -1) // Remove open parenthesis + s = strings.Replace(s, ")", "", -1) // Remove close parenthesis + s = strings.Replace(s, "/", "", -1) // Remove forward slashes + s = strings.ToLower(s) + return s +} + +func cleanIp(s string) string { + s = strings.Replace(s, ":", "", -1) // Remove spaces + s = strings.Replace(s, ".", "_", -1) // Remove open parenthesis + return s } func loadConfig() bool { - pwd, err := filepath.Abs(filepath.Dir(os.Args[0])) + path, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { - log.Fatalf("error: %v", err) + log.Fatalf("error: %v", err) } + pwd = path content, err := ioutil.ReadFile(*configFile) - if err != nil { - log.Fatalf("error: %v", err) - return false - } else { - err := yaml.Unmarshal(content, &config) - if err != nil { - log.Fatalf("error: %v", err) - return false - } - for conf, _ := range config.Cfgs { - file := pwd + "/prometheus_" + config.Cfgs[conf].Instance + ".dat" - config.Cfgs[conf].Alertlog[0].lastfile = file - content, err := ioutil.ReadFile(file) - if err == nil { - t, _ := time.Parse(layout,string(content)) - config.Cfgs[conf].Alertlog[0].lasttime = t - }else{ - config.Cfgs[conf].Alertlog[0].lasttime = time.Now() - } + if err != nil { + log.Fatalf("error: %v", err) + return false + } else { + err := yaml.Unmarshal(content, &config) + if err != nil { + log.Fatalf("error: %v", err) + return false } - return true + return true } } From 36e8c93aa8837d34ca255912dc8c4ac9530e8d6b Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Thu, 12 Apr 2018 15:25:17 +0200 Subject: [PATCH 17/27] Cleanup --- alertlog.go | 14 ++++++++------ main.go | 24 ++++++++++-------------- misc.go | 7 ------- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/alertlog.go b/alertlog.go index 53c95e8..54f9b8b 100644 --- a/alertlog.go +++ b/alertlog.go @@ -13,12 +13,13 @@ import ( type oraerr struct { ora string text string - ignore bool + ignore string count int } var Errors []oraerr +// Get individual ScrapeTime per Prometheus instance for alertlog func (e *Exporter) GetLastScrapeTime(conf int) time.Time { file := pwd + "/prometheus_" + config.Cfgs[conf].Instance + "_" + cleanIp(e.lastIp) + ".dat" content, err := ioutil.ReadFile(file) @@ -29,6 +30,7 @@ func (e *Exporter) GetLastScrapeTime(conf int) time.Time { return time.Now() } +// Set individual ScrapeTime per Prometheus instance for alertlog func (e *Exporter) SetLastScrapeTime(conf int,t time.Time) { file := pwd + "/prometheus_" + config.Cfgs[conf].Instance + "_" + cleanIp(e.lastIp) + ".dat" fh, err := os.Create(file) @@ -48,10 +50,10 @@ func addError(conf int, ora string, text string){ } } if ! found { - ignore := false + ignore := "0" for _ , e := range config.Cfgs[conf].Alertlog[0].Ignoreora { if e == ora { - ignore = true + ignore = "1" } } i := strings.Index(text, " ") @@ -63,7 +65,7 @@ func addError(conf int, ora string, text string){ } } -func (e *Exporter) ScrapeOraerror() { +func (e *Exporter) ScrapeAlertlog() { loc := time.Now().Location() re := regexp.MustCompile(`ORA-[0-9]+`) @@ -93,11 +95,11 @@ func (e *Exporter) ScrapeOraerror() { file.Close() e.SetLastScrapeTime(conf,lastTime) for i, _ := range Errors { - e.oraerror.WithLabelValues(config.Cfgs[conf].Database, + e.alertlog.WithLabelValues(config.Cfgs[conf].Database, config.Cfgs[conf].Instance, Errors[i].ora, Errors[i].text, - FormatBool(Errors[i].ignore)).Set(float64(Errors[i].count)) + Errors[i].ignore).Set(float64(Errors[i].count)) } } } diff --git a/main.go b/main.go index 004104d..9e6533c 100644 --- a/main.go +++ b/main.go @@ -18,17 +18,15 @@ const ( ) type Alert struct { - File string `yaml:"file"` + File string `yaml:"file"` Ignoreora []string `yaml:"ignoreora"` - lastfile string - lasttime time.Time } type Config struct { Connection string `yaml:"connection"` Database string `yaml:"database"` Instance string `yaml:"instance"` - Alertlog []Alert `yaml:"alertlog"` + Alertlog []Alert `yaml:"alertlog"` db *sql.DB } @@ -44,7 +42,7 @@ type Exporter struct { session *prometheus.GaugeVec sysstat *prometheus.GaugeVec waitclass *prometheus.GaugeVec - sysmetric *prometheus.GaugeVec + sysmetric *prometheus.GaugeVec interconnect *prometheus.GaugeVec uptime *prometheus.GaugeVec up *prometheus.GaugeVec @@ -52,7 +50,7 @@ type Exporter struct { recovery *prometheus.GaugeVec redo *prometheus.GaugeVec cache *prometheus.GaugeVec - oraerror *prometheus.GaugeVec + alertlog *prometheus.GaugeVec services *prometheus.GaugeVec parameter *prometheus.GaugeVec lastIp string @@ -153,7 +151,7 @@ func NewExporter() *Exporter { Name: "up", Help: "Whether the Oracle server is up.", }, []string{"database","dbinstance"}), - oraerror: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + alertlog: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "error", Help: "Oracle Errors occured during configured interval.", @@ -527,7 +525,7 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { e.cache.Describe(ch) e.uptime.Describe(ch) e.up.Describe(ch) - e.oraerror.Describe(ch) + e.alertlog.Describe(ch) e.services.Describe(ch) e.parameter.Describe(ch) } @@ -566,7 +564,7 @@ func (e *Exporter) Connect() { e.redo.Reset() e.cache.Reset() e.uptime.Reset() - e.oraerror.Reset() + e.alertlog.Reset() e.services.Reset() e.parameter.Reset() } @@ -628,8 +626,8 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.ScrapeCache() e.cache.Collect(ch) - e.ScrapeOraerror() - e.oraerror.Collect(ch) + e.ScrapeAlertlog() + e.alertlog.Collect(ch) e.ScrapeServices() e.services.Collect(ch) @@ -646,11 +644,10 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { } func (e *Exporter) Handler(w http.ResponseWriter, r *http.Request) { + e.lastIp = "" ip, _, err := net.SplitHostPort(r.RemoteAddr) if err == nil { e.lastIp = ip - } else { - e.lastIp = "" } prometheus.Handler().ServeHTTP(w, r) } @@ -664,7 +661,6 @@ func main() { exporter := NewExporter() prometheus.MustRegister(exporter) -// http.Handle(*metricPath, prometheus.Handler()) http.HandleFunc(*metricPath, exporter.Handler) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write(landingPage)}) diff --git a/misc.go b/misc.go index bb4714a..7a49484 100644 --- a/misc.go +++ b/misc.go @@ -9,13 +9,6 @@ import ( "path/filepath" ) -func FormatBool(b bool) string { - if b { - return "1" - } - return "0" -} - // Oracle gives us some ugly names back. This function cleans things up for Prometheus. func cleanName(s string) string { s = strings.Replace(s, " ", "_", -1) // Remove spaces From 197caa8ce40f8cb7729812b0cb3bd580f9beb5d3 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Thu, 12 Apr 2018 15:30:44 +0200 Subject: [PATCH 18/27] Cleanup --- oracle.conf.example | 2 -- 1 file changed, 2 deletions(-) diff --git a/oracle.conf.example b/oracle.conf.example index b062f98..510a0b6 100644 --- a/oracle.conf.example +++ b/oracle.conf.example @@ -4,7 +4,6 @@ connections: instance: DEVELOP alertlog: - file: /data/oracle/diag/rdbms/develop/DEVELOP/trace/alert_DEVELOP.log - scantime: 300 ignoreora: - ORA-00001 - ORA-01033 @@ -34,7 +33,6 @@ connections: instance: STAGE alertlog: - file: /data/oracle/diag/rdbms/stage/STAGE/trace/alert_STAGE.log - scantime: 300 ignoreora: - ORA-01033 - ORA-01041 From 8ae347a5094406fa9c1d030111a8b8a98e59260a Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Fri, 13 Apr 2018 09:57:38 +0200 Subject: [PATCH 19/27] Scrape of self Defined Queries --- README.md | 6 ++++- main.go | 45 ++++++++++++++++++++++++++++++++++++++ oracle.conf.example | 53 ++++++++++++++++----------------------------- 3 files changed, 69 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 18bbc65..4864392 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,16 @@ The following metrics are exposed currently. Support for RAC (databasename and i - oracledb_error (Errors parsed from the alert.log) - oracledb_services (Active Oracle Services (v$active_services)) - oracledb_parameter (Configuration Parameters (v$parameter)) +- oracledb_query (Self defined Queries in Configuration File) +The Oracle Alertlog file is scanned and the metrics are exposed as a gauge metric with a total occurence of the specific ORA. +Yo can define your on Queries and execute and scrape them + # Installation Ensure that the configfile (oracle.conf) is set correctly before starting. You can add multiple instances, e.g. the ASM instance. It is even possible to run one Exporter for all your Databases, but this is not recommended. We use it in our Company because on one host multiple Instances are running. -It is now possible to scan the alert.log File. The metrics are exposed as a gauge metric with a total occurence of the specific ORA in a definied timeframe (scantime). + ```bash export NLS_LANG=AMERICAN_AMERICA.UTF8 diff --git a/main.go b/main.go index 9e6533c..ce38f08 100644 --- a/main.go +++ b/main.go @@ -22,11 +22,17 @@ type Alert struct { Ignoreora []string `yaml:"ignoreora"` } +type Query struct { + Sql string `yaml:"sql"` + Name string `yaml:"name"` +} + type Config struct { Connection string `yaml:"connection"` Database string `yaml:"database"` Instance string `yaml:"instance"` Alertlog []Alert `yaml:"alertlog"` + Queries []Query `yaml:"queries"` db *sql.DB } @@ -53,6 +59,7 @@ type Exporter struct { alertlog *prometheus.GaugeVec services *prometheus.GaugeVec parameter *prometheus.GaugeVec + query *prometheus.GaugeVec lastIp string } @@ -166,6 +173,39 @@ func NewExporter() *Exporter { Name: "parameter", Help: "oracle Configuration Parameters (v$parameter).", }, []string{"database","dbinstance","name"}), + query: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "query", + Help: "Self defined Queries from Configuration File.", + }, []string{"database","dbinstance","name"}), + } +} + +// ScrapeQuery collects metrics from self defined queries from configuration file. +func (e *Exporter) ScrapeQuery() { + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + //num metric_name + //43 sessions + if conn.db != nil { + for _, query := range conn.Queries { + rows, err = conn.db.Query(query.Sql) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var value float64 + if err := rows.Scan(&value); err != nil { + break + } + e.query.WithLabelValues(conn.Database,conn.Instance,query.Name).Set(value) + } + } + } } } @@ -528,6 +568,7 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { e.alertlog.Describe(ch) e.services.Describe(ch) e.parameter.Describe(ch) + e.query.Describe(ch) } // Connect the DBs and gather Databasename and Instancename @@ -567,6 +608,7 @@ func (e *Exporter) Connect() { e.alertlog.Reset() e.services.Reset() e.parameter.Reset() + e.query.Reset() } // Close Connections @@ -635,6 +677,9 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.ScrapeParameter() e.parameter.Collect(ch) + e.ScrapeQuery() + e.query.Collect(ch) + ch <- e.duration ch <- e.totalScrapes ch <- e.error diff --git a/oracle.conf.example b/oracle.conf.example index 510a0b6..f5b6756 100644 --- a/oracle.conf.example +++ b/oracle.conf.example @@ -9,24 +9,16 @@ connections: - ORA-01033 - ORA-01041 - ORA-01089 - - ORA-02050 - - ORA-02068 - - ORA-03113 - - ORA-03135 - - ORA-12537 - - ORA-16011 - - ORA-16040 - - ORA-16055 - - ORA-16058 - - ORA-16401 - - ORA-00054 - - ORA-01013 - ORA-01555 - ORA-28 - ORA-235 - ORA-609 - ORA-3136 - - ABORT_REDEF_TABLE + queries: + - sql: "select 1 from dual" + name: sample1 + - sql: "select 2 from dual" + name: sample2 - connection: /@ database: STAGE @@ -34,24 +26,17 @@ connections: alertlog: - file: /data/oracle/diag/rdbms/stage/STAGE/trace/alert_STAGE.log ignoreora: - - ORA-01033 - - ORA-01041 - - ORA-01089 - - ORA-02050 - - ORA-02068 - - ORA-03113 - - ORA-03135 - - ORA-12537 - - ORA-16011 - - ORA-16040 - - ORA-16055 - - ORA-16058 - - ORA-16401 - - ORA-00054 - - ORA-01013 - - ORA-01555 - - ORA-28 - - ORA-235 - - ORA-609 - - ORA-3136 - - ABORT_REDEF_TABLE + - ORA-00001 + - ORA-01033 + - ORA-01041 + - ORA-01089 + - ORA-01555 + - ORA-28 + - ORA-235 + - ORA-609 + - ORA-3136 + queries: + - sql: "select 3 from dual" + name: sample3 + - sql: "select 4 from dual" + name: sample3 From 83da3d975e767fa53f8404f4303afd156a21ee9a Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Fri, 13 Apr 2018 11:03:51 +0200 Subject: [PATCH 20/27] Scrape of self Defined Queries --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4864392..29ac5f2 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Ensure that the configfile (oracle.conf) is set correctly before starting. You c ```bash export NLS_LANG=AMERICAN_AMERICA.UTF8 -/path/to/binary -l log.level error -l web.listen-address 9161 +/path/to/binary -configfile=/home/user/oracle.conf -web.listen-address :9161 ``` ## Usage From 98643ebe3b8e18c0fc2e802b3b85f5ce2a2dfec3 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Fri, 13 Apr 2018 15:09:04 +0200 Subject: [PATCH 21/27] Add Autoextensible on Tablepspace --- README.md | 4 ++-- main.go | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 29ac5f2..8c3393d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ The following metrics are exposed currently. Support for RAC (databasename and i The Oracle Alertlog file is scanned and the metrics are exposed as a gauge metric with a total occurence of the specific ORA. -Yo can define your on Queries and execute and scrape them +Yo can define your own Queries and execute/scrape them # Installation @@ -44,7 +44,7 @@ export NLS_LANG=AMERICAN_AMERICA.UTF8 ```bash Usage of ./prometheus_oracle_exporter: -configfile string - ConfigurationFile in YAML format. (default "oracle.conf") + Configuration file in YAML format. (default "oracle.conf") -web.listen-address string Address to listen on for web interface and telemetry. (default ":9161") -web.telemetry-path string diff --git a/main.go b/main.go index ce38f08..5b9507c 100644 --- a/main.go +++ b/main.go @@ -132,7 +132,7 @@ func NewExporter() *Exporter { Namespace: namespace, Name: "tablespace", Help: "Gauge metric with total/free size of the Tablespaces.", - }, []string{"database","dbinstance","type","name","contents"}), + }, []string{"database","dbinstance","type","name","contents","autoextend"}), interconnect: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "interconnect", @@ -386,17 +386,17 @@ func (e *Exporter) ScrapeTablespace() { for _, conn := range config.Cfgs { if conn.db != nil { rows, err = conn.db.Query(`WITH - getsize AS (SELECT tablespace_name, SUM(bytes) tsize - FROM dba_data_files GROUP BY tablespace_name), + getsize AS (SELECT tablespace_name, autoextensible, SUM(bytes) tsize + FROM dba_data_files GROUP BY tablespace_name, autoextensible), getfree as (SELECT tablespace_name, contents, SUM(blocks*block_size) tfree FROM DBA_LMT_FREE_SPACE a, v$tablespace b, dba_tablespaces c WHERE a.TABLESPACE_ID= b.ts# and b.name=c.tablespace_name GROUP BY tablespace_name,contents) - SELECT a.tablespace_name, b.contents, a.tsize, b.tfree + SELECT a.tablespace_name, b.contents, a.tsize, b.tfree, a.autoextensible autoextend FROM GETSIZE a, GETFREE b WHERE a.tablespace_name = b.tablespace_name UNION - SELECT tablespace_name, 'TEMPORARY', sum(tablespace_size), sum(free_space) + SELECT tablespace_name, 'TEMPORARY', sum(tablespace_size), sum(free_space), 'NO' FROM dba_temp_free_space GROUP BY tablespace_name`) if err != nil { @@ -408,12 +408,13 @@ func (e *Exporter) ScrapeTablespace() { var contents string var tsize float64 var tfree float64 - if err := rows.Scan(&name, &contents, &tsize, &tfree); err != nil { + var auto string + if err := rows.Scan(&name, &contents, &tsize, &tfree, &auto); err != nil { break } - e.tablespace.WithLabelValues(conn.Database,conn.Instance,"total",name,contents).Set(tsize) - e.tablespace.WithLabelValues(conn.Database,conn.Instance,"free",name,contents).Set(tfree) - e.tablespace.WithLabelValues(conn.Database,conn.Instance,"used",name,contents).Set(tsize-tfree) + e.tablespace.WithLabelValues(conn.Database,conn.Instance,"total",name,contents,auto).Set(tsize) + e.tablespace.WithLabelValues(conn.Database,conn.Instance,"free",name,contents,auto).Set(tfree) + e.tablespace.WithLabelValues(conn.Database,conn.Instance,"used",name,contents,auto).Set(tsize-tfree) } } } From fb3e6a7f8c0cd4605a756eb7f6010c238b088d95 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Tue, 17 Apr 2018 11:23:55 +0200 Subject: [PATCH 22/27] INFRAOP-5599:Migrate Oracle database checks to Prometheus --- README.md | 2 +- main.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c3393d..fa478e4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ The following metrics are exposed currently. Support for RAC (databasename and i - oracledb_sysstat (view v$sysstat (parse count (total) / execute count / user commits / user rollbacks)) - oracledb_waitclass (view v$waitclass) - oracledb_tablespace (tablespace total/free) +- oracledb_asmspace (Space in ASM (v$asm_disk/v$asm_diskgroup)) - oracledb_interconnect (view v$sysstat (gc cr blocks served / gc cr blocks flushed / gc cr blocks received)) - oracledb_recovery (percentage usage in FRA from V$RECOVERY_FILE_DEST) - oracledb_redo (Redo log switches over last 5 min from v$log_history) @@ -25,7 +26,6 @@ The following metrics are exposed currently. Support for RAC (databasename and i - oracledb_parameter (Configuration Parameters (v$parameter)) - oracledb_query (Self defined Queries in Configuration File) - The Oracle Alertlog file is scanned and the metrics are exposed as a gauge metric with a total occurence of the specific ORA. Yo can define your own Queries and execute/scrape them diff --git a/main.go b/main.go index 5b9507c..69068dd 100644 --- a/main.go +++ b/main.go @@ -60,6 +60,7 @@ type Exporter struct { services *prometheus.GaugeVec parameter *prometheus.GaugeVec query *prometheus.GaugeVec + asmspace *prometheus.GaugeVec lastIp string } @@ -178,6 +179,11 @@ func NewExporter() *Exporter { Name: "query", Help: "Self defined Queries from Configuration File.", }, []string{"database","dbinstance","name"}), + asmspace: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "asmspace", + Help: "Gauge metric with total/free size of the ASM Diskgroups.", + }, []string{"database","dbinstance","type","name"}), } } @@ -377,6 +383,39 @@ func (e *Exporter) ScrapeInterconnect() { } } +// ScrapeAsmspace collects ASM metrics +func (e *Exporter) ScrapeAsmspace() { + var ( + rows *sql.Rows + err error + ) + for _, conn := range config.Cfgs { + if conn.db != nil { + rows, err = conn.db.Query(`SELECT g.name, sum(d.total_mb), sum(d.free_mb) + FROM v$asm_disk d, v$asm_diskgroup g + WHERE d.group_number = g.group_number (+) + AND d.header_status = 'MEMBER' + GROUP by g.name, g.group_number`) + if err != nil { + break + } + defer rows.Close() + for rows.Next() { + var name string + var tsize float64 + var tfree float64 + if err := rows.Scan(&name, &tsize, &tfree); err != nil { + break + } + e.asmspace.WithLabelValues(conn.Database,conn.Instance,"total",name).Set(tsize) + e.asmspace.WithLabelValues(conn.Database,conn.Instance,"free",name).Set(tfree) + e.asmspace.WithLabelValues(conn.Database,conn.Instance,"used",name).Set(tsize-tfree) + } + } + } +} + + // ScrapeTablespaces collects tablespace metrics func (e *Exporter) ScrapeTablespace() { var ( @@ -570,6 +609,7 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { e.services.Describe(ch) e.parameter.Describe(ch) e.query.Describe(ch) + e.asmspace.Describe(ch) } // Connect the DBs and gather Databasename and Instancename @@ -610,6 +650,7 @@ func (e *Exporter) Connect() { e.services.Reset() e.parameter.Reset() e.query.Reset() + e.asmspace.Reset() } // Close Connections @@ -681,6 +722,9 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.ScrapeQuery() e.query.Collect(ch) + e.ScrapeAsmspace() + e.asmspace.Collect(ch) + ch <- e.duration ch <- e.totalScrapes ch <- e.error From d1f7aa635eb02031e26551fc9922202d38923fc9 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Mon, 23 Apr 2018 14:52:12 +0200 Subject: [PATCH 23/27] Fix open sessions after scrape abort --- main.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 69068dd..8a578f3 100644 --- a/main.go +++ b/main.go @@ -616,21 +616,28 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { func (e *Exporter) Connect() { var dbname string var inname string + var err error for i, conf := range config.Cfgs { - config.Cfgs[i].db = nil - db , err := sql.Open("oci8", conf.Connection) + // Close Connect from former scrape that not closed properly + if config.Cfgs[i].db != nil { + config.Cfgs[i].db.Close() + config.Cfgs[i].db = nil + } + config.Cfgs[i].db , err = sql.Open("oci8", conf.Connection) if err == nil { - err = db.QueryRow("select db_unique_name,instance_name from v$database,v$instance").Scan(&dbname,&inname) + err = config.Cfgs[i].db.QueryRow("select db_unique_name,instance_name from v$database,v$instance").Scan(&dbname,&inname) if err == nil { if (conf.Database != dbname) || (conf.Instance != inname) { config.Cfgs[i].Database = dbname config.Cfgs[i].Instance = inname } - config.Cfgs[i].db = db e.up.WithLabelValues(conf.Database,conf.Instance).Set(1) } else { + config.Cfgs[i].db.Close() + config.Cfgs[i].db = nil; e.up.WithLabelValues(conf.Database,conf.Instance).Set(0) + log.Infoln("Connect OK, Inital query failed: ", conf.Connection) } } else { e.up.WithLabelValues(conf.Database,conf.Instance).Set(0) @@ -658,6 +665,7 @@ func (e *Exporter) Close() { for _, conn := range config.Cfgs { if conn.db != nil { conn.db.Close() + conn.db = nil } } } @@ -746,7 +754,6 @@ func main() { flag.Parse() log.Infoln("Starting Prometheus Oracle exporter " + Version) if loadConfig() { - log.Infoln("Config loaded: ", *configFile) exporter := NewExporter() prometheus.MustRegister(exporter) From 27ac3fa919d2aaf773c6d5d97cd8ea1349ec784f Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Tue, 8 May 2018 16:26:44 +0200 Subject: [PATCH 24/27] INFRAOP-0: Added default oracle Error --- alertlog.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/alertlog.go b/alertlog.go index 54f9b8b..1594cdd 100644 --- a/alertlog.go +++ b/alertlog.go @@ -66,12 +66,14 @@ func addError(conf int, ora string, text string){ } func (e *Exporter) ScrapeAlertlog() { + var noError bool loc := time.Now().Location() re := regexp.MustCompile(`ORA-[0-9]+`) for conf, _ := range config.Cfgs { var lastTime time.Time Errors = nil + noError = true lastScrapeTime := e.GetLastScrapeTime(conf) file, err := os.Open(config.Cfgs[conf].Alertlog[0].File) @@ -88,6 +90,7 @@ func (e *Exporter) ScrapeAlertlog() { if re.MatchString(scanner.Text()) { ora := re.FindString(scanner.Text()) addError(conf,ora, scanner.Text()) + noError = false } } } @@ -102,5 +105,10 @@ func (e *Exporter) ScrapeAlertlog() { Errors[i].ignore).Set(float64(Errors[i].count)) } } + if noError { + e.alertlog.WithLabelValues(config.Cfgs[conf].Database, + config.Cfgs[conf].Instance, + "ORA-0000","normal, successful completion","1").Set(0) + } } } From bfd2887724b2ed4cc314e435aa49469bf8776ebf Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Wed, 9 May 2018 13:57:12 +0200 Subject: [PATCH 25/27] INFRAOP-0: Added Logfile for alertlog --- .gitignore | 3 ++- alertlog.go | 2 +- main.go | 1 + misc.go | 10 ++++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 21b08da..aefa795 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ oracle.conf prometheus_oracle_exporter -prometheus_*.dat +*.dat +*.log diff --git a/alertlog.go b/alertlog.go index 1594cdd..ba6364f 100644 --- a/alertlog.go +++ b/alertlog.go @@ -40,7 +40,6 @@ func (e *Exporter) SetLastScrapeTime(conf int,t time.Time) { } } - func addError(conf int, ora string, text string){ var found bool = false for i, _ := range Errors { @@ -103,6 +102,7 @@ func (e *Exporter) ScrapeAlertlog() { Errors[i].ora, Errors[i].text, Errors[i].ignore).Set(float64(Errors[i].count)) + WriteLog(config.Cfgs[conf].Instance + "(" + Errors[i].ignore + "): " + Errors[i].ora + " - " + Errors[i].text) } } if noError { diff --git a/main.go b/main.go index 8a578f3..66e9796 100644 --- a/main.go +++ b/main.go @@ -70,6 +70,7 @@ var ( listenAddress = flag.String("web.listen-address", ":9161", "Address to listen on for web interface and telemetry.") metricPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") configFile = flag.String("configfile", "oracle.conf", "ConfigurationFile in YAML format.") + logFile = flag.String("logfile", "exporter.log", "Logfile for parsed Oracle Alerts.") landingPage = []byte("Prometheus Oracle exporter

Prometheus Oracle exporter

Metrics

") config Configs oralayout = "Mon Jan 02 15:04:05 2006" diff --git a/misc.go b/misc.go index 7a49484..6d7bb42 100644 --- a/misc.go +++ b/misc.go @@ -6,6 +6,7 @@ import ( "gopkg.in/yaml.v2" "github.com/prometheus/common/log" "os" + "time" "path/filepath" ) @@ -44,3 +45,12 @@ func loadConfig() bool { return true } } + +func WriteLog(message string) { + file := pwd + "/" + *logFile + fh, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err == nil { + fh.WriteString(time.Now().Format("2006-01-02 15:04:05") + " " + message + "\n") + fh.Close() + } +} From e3fe145f3a691f3886881d17b55104aab23f706a Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Thu, 17 May 2018 16:59:41 +0200 Subject: [PATCH 26/27] INFRAOP-0: Altered Last Alertlogaccess --- .gitignore | 2 +- alertlog.go | 96 ++++++++++++++++++++++++++++++++++++----------------- main.go | 27 +-------------- misc.go | 60 +++++++++++++++++++++++++++++---- 4 files changed, 121 insertions(+), 64 deletions(-) diff --git a/.gitignore b/.gitignore index aefa795..eb0bf8c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ oracle.conf +access.conf prometheus_oracle_exporter -*.dat *.log diff --git a/alertlog.go b/alertlog.go index ba6364f..e7bd2f0 100644 --- a/alertlog.go +++ b/alertlog.go @@ -6,10 +6,25 @@ import ( "time" "regexp" "strings" - "io/ioutil" + "strconv" "github.com/prometheus/common/log" ) + +type Client struct { + Ip string `yaml:"ip"` + Date string `yaml:"date"` +} + +type Lastlog struct { + Instance string `yaml:"instance"` + Clients []Client `yaml:"clients"` +} + +type Lastlogs struct { + Cfgs []Lastlog `yaml:"lastlog"` +} + type oraerr struct { ora string text string @@ -17,26 +32,53 @@ type oraerr struct { count int } -var Errors []oraerr +var ( + Errors []oraerr + oralayout = "Mon Jan 02 15:04:05 2006" + lastlog Lastlogs +) + // Get individual ScrapeTime per Prometheus instance for alertlog func (e *Exporter) GetLastScrapeTime(conf int) time.Time { - file := pwd + "/prometheus_" + config.Cfgs[conf].Instance + "_" + cleanIp(e.lastIp) + ".dat" - content, err := ioutil.ReadFile(file) - if err == nil { - t, _ := time.Parse("2006-01-02 15:04:05 -0700 MST",string(content)) - return t + for i, _ := range lastlog.Cfgs { + if lastlog.Cfgs[i].Instance == config.Cfgs[conf].Instance { + for n, _ := range lastlog.Cfgs[i].Clients { + if lastlog.Cfgs[i].Clients[n].Ip == e.lastIp { + t, _ := time.Parse("2006-01-02 15:04:05 -0700 MST",string(lastlog.Cfgs[i].Clients[n].Date)) + return t + } + } + } } return time.Now() } // Set individual ScrapeTime per Prometheus instance for alertlog func (e *Exporter) SetLastScrapeTime(conf int,t time.Time) { - file := pwd + "/prometheus_" + config.Cfgs[conf].Instance + "_" + cleanIp(e.lastIp) + ".dat" - fh, err := os.Create(file) - if err == nil { - fh.WriteString(t.String()) - fh.Close() + var indInst int = -1 + var indIp int = -1 + for i, _ := range lastlog.Cfgs { + if lastlog.Cfgs[i].Instance == config.Cfgs[conf].Instance { + indInst = i + for n, _ := range lastlog.Cfgs[i].Clients { + if lastlog.Cfgs[i].Clients[n].Ip == e.lastIp { + indIp = n + } + } + } + } + if indInst == -1 { + cln := Client{Ip: e.lastIp, Date: t.String()} + lastlog.Cfgs = append(lastlog.Cfgs, Lastlog{Instance: config.Cfgs[conf].Instance, + Clients: []Client{ cln } } ) + }else{ + if indIp == -1 { + cln := Client{Ip: e.lastIp, Date: t.String()} + lastlog.Cfgs[indInst].Clients = append(lastlog.Cfgs[indInst].Clients, cln) + }else{ + lastlog.Cfgs[indInst].Clients[indIp].Date = t.String() + } } } @@ -51,29 +93,26 @@ func addError(conf int, ora string, text string){ if ! found { ignore := "0" for _ , e := range config.Cfgs[conf].Alertlog[0].Ignoreora { - if e == ora { - ignore = "1" - } + if e == ora {; ignore = "1"; } } - i := strings.Index(text, " ") - if i < 0{ - i = 0 - } - ora := oraerr{ora: ora, text: text[i+1:], ignore: ignore, count: 1} + is := strings.Index(text, " ") + ip := strings.Index(text, ". ") + if is < 0 {; is = 0; } + if ip < 0 {; ip = len(text); } + ora := oraerr{ora: ora, text: text[is+1:ip-is], ignore: ignore, count: 1} Errors = append (Errors, ora) } } func (e *Exporter) ScrapeAlertlog() { - var noError bool loc := time.Now().Location() re := regexp.MustCompile(`ORA-[0-9]+`) + ReadAccess() for conf, _ := range config.Cfgs { var lastTime time.Time Errors = nil - noError = true - lastScrapeTime := e.GetLastScrapeTime(conf) + lastScrapeTime := e.GetLastScrapeTime(conf).Add(time.Second) file, err := os.Open(config.Cfgs[conf].Alertlog[0].File) if err != nil { @@ -89,7 +128,6 @@ func (e *Exporter) ScrapeAlertlog() { if re.MatchString(scanner.Text()) { ora := re.FindString(scanner.Text()) addError(conf,ora, scanner.Text()) - noError = false } } } @@ -102,13 +140,11 @@ func (e *Exporter) ScrapeAlertlog() { Errors[i].ora, Errors[i].text, Errors[i].ignore).Set(float64(Errors[i].count)) - WriteLog(config.Cfgs[conf].Instance + "(" + Errors[i].ignore + "): " + Errors[i].ora + " - " + Errors[i].text) + WriteLog(config.Cfgs[conf].Instance + + "(" + Errors[i].ignore + "/" + strconv.Itoa(Errors[i].count) + "): " + + Errors[i].ora + " - " + Errors[i].text) } } - if noError { - e.alertlog.WithLabelValues(config.Cfgs[conf].Database, - config.Cfgs[conf].Instance, - "ORA-0000","normal, successful completion","1").Set(0) - } } + WriteAccess() } diff --git a/main.go b/main.go index 66e9796..e2021d1 100644 --- a/main.go +++ b/main.go @@ -17,29 +17,6 @@ const ( exporter = "exporter" ) -type Alert struct { - File string `yaml:"file"` - Ignoreora []string `yaml:"ignoreora"` -} - -type Query struct { - Sql string `yaml:"sql"` - Name string `yaml:"name"` -} - -type Config struct { - Connection string `yaml:"connection"` - Database string `yaml:"database"` - Instance string `yaml:"instance"` - Alertlog []Alert `yaml:"alertlog"` - Queries []Query `yaml:"queries"` - db *sql.DB -} - -type Configs struct { - Cfgs []Config `yaml:"connections"` -} - // Exporter collects Oracle DB metrics. It implements prometheus.Collector. type Exporter struct { duration, error prometheus.Gauge @@ -71,10 +48,8 @@ var ( metricPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") configFile = flag.String("configfile", "oracle.conf", "ConfigurationFile in YAML format.") logFile = flag.String("logfile", "exporter.log", "Logfile for parsed Oracle Alerts.") + accessFile = flag.String("accessfile", "access.conf", "Last access for parsed Oracle Alerts.") landingPage = []byte("Prometheus Oracle exporter

Prometheus Oracle exporter

Metrics

") - config Configs - oralayout = "Mon Jan 02 15:04:05 2006" - pwd string ) diff --git a/misc.go b/misc.go index 6d7bb42..cbd831f 100644 --- a/misc.go +++ b/misc.go @@ -1,13 +1,43 @@ package main import ( + "os" + "time" "strings" "io/ioutil" "gopkg.in/yaml.v2" - "github.com/prometheus/common/log" - "os" - "time" "path/filepath" + "database/sql" + "github.com/prometheus/common/log" + _ "github.com/mattn/go-oci8" +) + +type Alert struct { + File string `yaml:"file"` + Ignoreora []string `yaml:"ignoreora"` +} + +type Query struct { + Sql string `yaml:"sql"` + Name string `yaml:"name"` +} + +type Config struct { + Connection string `yaml:"connection"` + Database string `yaml:"database"` + Instance string `yaml:"instance"` + Alertlog []Alert `yaml:"alertlog"` + Queries []Query `yaml:"queries"` + db *sql.DB +} + +type Configs struct { + Cfgs []Config `yaml:"connections"` +} + +var ( + config Configs + pwd string ) // Oracle gives us some ugly names back. This function cleans things up for Prometheus. @@ -46,11 +76,27 @@ func loadConfig() bool { } } +func ReadAccess(){ + var file = pwd + "/" + *accessFile + content, err := ioutil.ReadFile(file) + if err == nil { + err := yaml.Unmarshal(content, &lastlog) + if err != nil { + log.Fatalf("error1: %v", err) + } + } +} + +func WriteAccess(){ + content, _ := yaml.Marshal(&lastlog) + ioutil.WriteFile(pwd + "/" + *accessFile, content, 0644) +} + func WriteLog(message string) { - file := pwd + "/" + *logFile - fh, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + fh, err := os.OpenFile(pwd + "/" + *logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err == nil { - fh.WriteString(time.Now().Format("2006-01-02 15:04:05") + " " + message + "\n") - fh.Close() + fh.Seek(0,2) + fh.WriteString(time.Now().Format("2006-01-02 15:04:05") + " " + message + "\n") + fh.Close() } } From 4f8ed5c26fc1cfaa1a656888b87bb3e58480aa76 Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Thu, 17 May 2018 17:02:52 +0200 Subject: [PATCH 27/27] INFRAOP-0: Altered Last Alertlogaccess --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fa478e4..17868f2 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,14 @@ export NLS_LANG=AMERICAN_AMERICA.UTF8 ```bash Usage of ./prometheus_oracle_exporter: + -accessfile string + Last access for parsed Oracle Alerts. (default "access.conf") -configfile string - Configuration file in YAML format. (default "oracle.conf") + ConfigurationFile in YAML format. (default "oracle.conf") + -logfile string + Logfile for parsed Oracle Alerts. (default "exporter.log") -web.listen-address string - Address to listen on for web interface and telemetry. (default ":9161") + Address to listen on for web interface and telemetry. (default ":9161") -web.telemetry-path string - Path under which to expose metrics. (default "/metrics") + Path under which to expose metrics. (default "/metrics") ```