|
1 | 1 | package locator |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bufio" |
4 | 5 | "context" |
5 | 6 | "fmt" |
6 | 7 | "io/ioutil" |
| 8 | + "math/big" |
| 9 | + "net/http" |
7 | 10 | "regexp" |
8 | 11 | "strings" |
9 | 12 | "time" |
10 | 13 |
|
11 | 14 | log "github.com/Sirupsen/logrus" |
12 | 15 |
|
13 | 16 | "github.com/prometheus/client_golang/api/prometheus" |
14 | | - "github.com/prometheus/common/model" |
15 | 17 | ) |
16 | 18 |
|
17 | 19 | var ( |
@@ -64,31 +66,93 @@ func ToPrometheusClients(endpointURLs []string) ([]*PrometheusEndpoint, error) { |
64 | 66 | for _, endpoint := range endpointURLs { |
65 | 67 | addr := strings.Trim(endpoint, " ") |
66 | 68 | if len(addr) > 0 { |
| 69 | + var uptime time.Duration |
| 70 | + var queryAPI prometheus.QueryAPI |
67 | 71 | client, err := prometheus.New(prometheus.Config{ |
68 | 72 | Address: addr, |
69 | 73 | }) |
70 | 74 | if err == nil { |
71 | | - queryAPI := prometheus.NewQueryAPI(client) |
72 | | - result, err := queryAPI.Query(context.TODO(), "(time() - max(process_start_time_seconds{job=\"prometheus\"}))", time.Now()) |
| 75 | + // Scape the /metrics endpoint of the individual prometheus instance, since |
| 76 | + // self-scaping of prometheus' own metrics might not be configured |
73 | 77 | if log.GetLevel() >= log.DebugLevel { |
74 | | - log.Debugf("Endpoint %v returned uptime result: %v", addr, result) |
| 78 | + log.Debugf("Testing %s/metrics", addr) |
75 | 79 | } |
76 | | - if err == nil { |
77 | | - if vector, ok := result.(model.Vector); ok && len(vector) > 0 { |
78 | | - uptime := time.Duration(float64(result.(model.Vector)[0].Value)) * time.Second |
79 | | - endpoints = append(endpoints, &PrometheusEndpoint{QueryAPI: prometheus.NewQueryAPI(client), Address: addr, Uptime: uptime}) |
80 | | - continue |
81 | | - } else { |
82 | | - log.Errorf("Endpoint %v returned unexpected uptime result: %v", addr, result) |
83 | | - err = fmt.Errorf("Unexpected uptime result: '%v'", result) |
| 80 | + scraped, err := ScrapeMetric(addr, "process_start_time_seconds") |
| 81 | + if err == nil && scraped != nil { |
| 82 | + processStartTimeSeconds := scraped.Value |
| 83 | + uptime = time.Duration(time.Now().UTC().Unix()-int64(processStartTimeSeconds)) * time.Second |
| 84 | + if log.GetLevel() >= log.DebugLevel { |
| 85 | + log.Debugf("Parsed current uptime for %s: %s", addr, uptime) |
| 86 | + } |
| 87 | + queryAPI = prometheus.NewQueryAPI(client) |
| 88 | + _, err = queryAPI.Query(context.TODO(), "up", time.Now()) |
| 89 | + if err != nil && log.GetLevel() >= log.DebugLevel { |
| 90 | + log.Debugf("Query 'up' returned error: %v", err) |
84 | 91 | } |
85 | 92 | } |
86 | 93 | } |
87 | | - endpoints = append(endpoints, &PrometheusEndpoint{Address: addr, Uptime: time.Duration(0), Error: err}) |
| 94 | + |
| 95 | + if err == nil { |
| 96 | + endpoints = append(endpoints, &PrometheusEndpoint{QueryAPI: queryAPI, Address: addr, Uptime: uptime}) |
| 97 | + } else { |
| 98 | + log.Errorf("Failed to resolve build_info and uptime for %v: %v", addr, err) |
| 99 | + endpoints = append(endpoints, &PrometheusEndpoint{Address: addr, Uptime: time.Duration(0), Error: err}) |
| 100 | + } |
88 | 101 | } |
89 | 102 | } |
90 | 103 | if len(endpoints) == 0 { |
91 | 104 | return nil, fmt.Errorf("Unable to locate any potential endpoints") |
92 | 105 | } |
93 | 106 | return endpoints, nil |
94 | 107 | } |
| 108 | + |
| 109 | +// LabeledValue represents a persed metric instance |
| 110 | +type LabeledValue struct { |
| 111 | + Name string |
| 112 | + Labels string |
| 113 | + Value float64 |
| 114 | +} |
| 115 | + |
| 116 | +func (lv *LabeledValue) String() string { |
| 117 | + return fmt.Sprintf("%s%s %f", lv.Name, lv.Labels, lv.Value) |
| 118 | +} |
| 119 | + |
| 120 | +// ScrapeMetric parses metrics in a simple fashion, returning |
| 121 | +// the first instance of each metric for a given name; results may be unexpected |
| 122 | +// for metrics with multiple instances |
| 123 | +func ScrapeMetric(addr string, name string) (*LabeledValue, error) { |
| 124 | + |
| 125 | + resp, err := http.Get(fmt.Sprintf("%s/metrics", addr)) |
| 126 | + if err != nil { |
| 127 | + return nil, err |
| 128 | + } |
| 129 | + |
| 130 | + if resp.StatusCode != http.StatusOK { |
| 131 | + return nil, fmt.Errorf("%s/metrics returned %d", addr, resp.StatusCode) |
| 132 | + } |
| 133 | + |
| 134 | + defer resp.Body.Close() |
| 135 | + scanner := bufio.NewScanner(resp.Body) |
| 136 | + scanner.Split(bufio.ScanLines) |
| 137 | + for scanner.Scan() { |
| 138 | + line := scanner.Text() |
| 139 | + if !strings.HasPrefix(line, "#") { |
| 140 | + parts := strings.Split(line, " ") |
| 141 | + nameParts := strings.Split(parts[0], "{") |
| 142 | + if nameParts[0] == name { |
| 143 | + f := new(big.Float) |
| 144 | + _, err := fmt.Sscan(parts[1], f) |
| 145 | + if err == nil { |
| 146 | + v := &LabeledValue{Name: nameParts[0]} |
| 147 | + v.Value, _ = f.Float64() |
| 148 | + if len(nameParts) > 1 { |
| 149 | + v.Labels = "{" + nameParts[1] |
| 150 | + } |
| 151 | + return v, nil |
| 152 | + } |
| 153 | + return nil, fmt.Errorf("Failed to parse value for metric %s", line) |
| 154 | + } |
| 155 | + } |
| 156 | + } |
| 157 | + return nil, nil |
| 158 | +} |
0 commit comments