6
6
package main
7
7
8
8
import (
9
+ "flag"
9
10
"fmt"
10
11
"io/ioutil"
11
12
"log"
12
13
"path/filepath"
13
14
"strings"
15
+ "sync"
14
16
"time"
15
17
18
+ "github.com/m-lab/go/flagx"
16
19
"github.com/m-lab/go/prometheusx"
20
+ "github.com/m-lab/go/rtx"
21
+ "github.com/m-lab/prometheus-bigquery-exporter/internal/setup"
17
22
"github.com/m-lab/prometheus-bigquery-exporter/query"
18
23
"github.com/m-lab/prometheus-bigquery-exporter/sql"
19
24
20
- flag "github.com/spf13/pflag"
21
-
22
25
"cloud.google.com/go/bigquery"
23
26
"golang.org/x/net/context"
24
27
25
28
"github.com/prometheus/client_golang/prometheus"
26
29
)
27
30
28
31
var (
29
- valueTypes = []string {}
30
- querySources = []string {}
31
- project = flag .String ("project" , "" , "GCP project name." )
32
- port = flag .String ("port" , ":9050" , "Exporter port." )
33
- refresh = flag .Duration ("refresh" , 5 * time .Minute , "Interval between updating metrics." )
32
+ counterSources = flagx.StringArray {}
33
+ gaugeSources = flagx.StringArray {}
34
+ project = flag .String ("project" , "" , "GCP project name." )
35
+ refresh = flag .Duration ("refresh" , 5 * time .Minute , "Interval between updating metrics." )
34
36
)
35
37
36
38
func init () {
37
- flag .StringArrayVar (& valueTypes , "type" , nil , "Name of the prometheus value type, e.g. 'counter' or 'gauge'." )
38
- flag .StringArrayVar (& querySources , "query" , nil , "Name of file with query string." )
39
+ // TODO: support counter queries.
40
+ // flag.Var(&counterSources, "counter-query", "Name of file containing a counter query.")
41
+ flag .Var (& gaugeSources , "gauge-query" , "Name of file containing a gauge query." )
39
42
43
+ // Port registered at https://github.com/prometheus/prometheus/wiki/Default-port-allocations
44
+ * prometheusx .ListenAddress = ":9348"
40
45
log .SetFlags (log .LstdFlags | log .Lshortfile )
41
46
}
42
47
@@ -53,109 +58,69 @@ func fileToMetric(filename string) string {
53
58
return strings .TrimSuffix (fname , filepath .Ext (fname ))
54
59
}
55
60
56
- // createCollector creates a sql.Collector initialized with the BQ query
57
- // contained in filename. The returned collector should be registered with
58
- // prometheus.Register.
59
- func createCollector (client * bigquery.Client , filename , typeName string , vars map [string ]string ) (* sql.Collector , error ) {
61
+ // fileToQuery reads the content of the given file and returns the query with template values repalced with those in vars.
62
+ func fileToQuery (filename string , vars map [string ]string ) string {
60
63
queryBytes , err := ioutil .ReadFile (filename )
61
- if err != nil {
62
- return nil , err
63
- }
64
-
65
- var v prometheus.ValueType
66
- if typeName == "counter" {
67
- v = prometheus .CounterValue
68
- } else if typeName == "gauge" {
69
- v = prometheus .GaugeValue
70
- } else {
71
- v = prometheus .UntypedValue
72
- }
64
+ rtx .Must (err , "Failed to open %q" , filename )
73
65
74
- // TODO: use to text/template
75
66
q := string (queryBytes )
76
67
q = strings .Replace (q , "UNIX_START_TIME" , vars ["UNIX_START_TIME" ], - 1 )
77
68
q = strings .Replace (q , "REFRESH_RATE_SEC" , vars ["REFRESH_RATE_SEC" ], - 1 )
78
-
79
- c := sql .NewCollector (query .NewBQRunner (client ), v , fileToMetric (filename ), string (q ))
80
-
81
- return c , nil
69
+ return q
82
70
}
83
71
84
- // updatePeriodically runs in an infinite loop, and updates registered
85
- // collectors every refresh period.
86
- func updatePeriodically (unregistered chan * sql.Collector , refresh time.Duration ) {
87
- var collectors = []* sql.Collector {}
88
-
89
- // Attempt to register all unregistered collectors.
90
- if len (unregistered ) > 0 {
91
- collectors = append (collectors , tryRegister (unregistered )... )
92
- }
93
- for sleepUntilNext (refresh ); ; sleepUntilNext (refresh ) {
94
- log .Printf ("Starting a new round at: %s" , time .Now ())
95
- for i := range collectors {
96
- log .Printf ("Running query for %s" , collectors [i ])
97
- collectors [i ].Update ()
98
- log .Printf ("Done" )
99
- }
100
- if len (unregistered ) > 0 {
101
- collectors = append (collectors , tryRegister (unregistered )... )
102
- }
72
+ func reloadRegisterUpdate (client * bigquery.Client , files []setup.File , vars map [string ]string ) {
73
+ var wg sync.WaitGroup
74
+ for i := range files {
75
+ wg .Add (1 )
76
+ go func (f * setup.File ) {
77
+ modified , err := f .IsModified ()
78
+ if modified && err == nil {
79
+ c := sql .NewCollector (
80
+ newRunner (client ), prometheus .GaugeValue ,
81
+ fileToMetric (f .Name ), fileToQuery (f .Name , vars ))
82
+
83
+ log .Println ("Registering:" , fileToMetric (f .Name ))
84
+ err = f .Register (c )
85
+ } else {
86
+ log .Println ("Updating:" , fileToMetric (f .Name ))
87
+ err = f .Update ()
88
+ }
89
+ if err != nil {
90
+ log .Println ("Error:" , f .Name , err )
91
+ }
92
+ wg .Done ()
93
+ }(& files [i ])
103
94
}
95
+ wg .Wait ()
104
96
}
105
97
106
- // tryRegister attempts to prometheus.Register every sql.Collectors queued in
107
- // unregistered. Any collectors that fail are placed back on the channel. All
108
- // successfully registered collectors are returned.
109
- func tryRegister (unregistered chan * sql.Collector ) []* sql.Collector {
110
- var registered = []* sql.Collector {}
111
- count := len (unregistered )
112
- for i := 0 ; i < count ; i ++ {
113
- // Take collector off of channel.
114
- c := <- unregistered
115
-
116
- // Try to register this collector.
117
- err := prometheus .Register (c )
118
- if err != nil {
119
- // Registration failed, so place collector back on channel.
120
- unregistered <- c
121
- continue
122
- }
123
- log .Printf ("Registered %s" , c )
124
- registered = append (registered , c )
125
- }
126
- return registered
98
+ var mainCtx , mainCancel = context .WithCancel (context .Background ())
99
+ var newRunner = func (client * bigquery.Client ) sql.QueryRunner {
100
+ return query .NewBQRunner (client )
127
101
}
128
102
129
103
func main () {
130
104
flag .Parse ()
105
+ rtx .Must (flagx .ArgsFromEnv (flag .CommandLine ), "Could not get args from env" )
131
106
132
- if len (querySources ) != len (valueTypes ) {
133
- log .Fatal ("You must provide a --type flag for every --query source." )
134
- }
135
-
136
- // Create a channel with capacity for all collectors.
137
- unregistered := make (chan * sql.Collector , len (querySources ))
107
+ srv := prometheusx .MustServeMetrics ()
108
+ defer srv .Shutdown (mainCtx )
138
109
139
- ctx := context .Background ()
140
- client , err := bigquery .NewClient (ctx , * project )
141
- if err != nil {
142
- log .Fatal (err )
110
+ files := make ([]setup.File , len (gaugeSources ))
111
+ for i := range files {
112
+ files [i ].Name = gaugeSources [i ]
143
113
}
144
114
115
+ client , err := bigquery .NewClient (mainCtx , * project )
116
+ rtx .Must (err , "Failed to allocate a new bigquery.Client" )
145
117
vars := map [string ]string {
146
118
"UNIX_START_TIME" : fmt .Sprintf ("%d" , time .Now ().UTC ().Unix ()),
147
119
"REFRESH_RATE_SEC" : fmt .Sprintf ("%d" , int (refresh .Seconds ())),
148
120
}
149
- for i := range querySources {
150
- c , err := createCollector (client , querySources [i ], valueTypes [i ], vars )
151
- if err != nil {
152
- log .Printf ("Failed to create collector %s: %s" , querySources [i ], err )
153
- continue
154
- }
155
- // Store collector in channel.
156
- unregistered <- c
157
- }
158
121
159
- prometheusx .MustStartPrometheus (* port )
160
- updatePeriodically (unregistered , * refresh )
122
+ for mainCtx .Err () == nil {
123
+ reloadRegisterUpdate (client , files , vars )
124
+ sleepUntilNext (* refresh )
125
+ }
161
126
}
0 commit comments