1// Copyright 2015 The Prometheus Authors 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14// Copyright (c) 2013, The Prometheus Authors 15// All rights reserved. 16// 17// Use of this source code is governed by a BSD-style license that can be found 18// in the LICENSE file. 19 20// Package push provides functions to push metrics to a Pushgateway. The metrics 21// to push are either collected from a provided registry, or from explicitly 22// listed collectors. 23// 24// See the documentation of the Pushgateway to understand the meaning of the 25// grouping parameters and the differences between push.Registry and 26// push.Collectors on the one hand and push.AddRegistry and push.AddCollectors 27// on the other hand: https://github.com/prometheus/pushgateway 28package push 29 30import ( 31 "bytes" 32 "fmt" 33 "io/ioutil" 34 "net/http" 35 "net/url" 36 "os" 37 "strings" 38 39 "github.com/prometheus/common/expfmt" 40 "github.com/prometheus/common/model" 41 42 "github.com/prometheus/client_golang/prometheus" 43) 44 45const contentTypeHeader = "Content-Type" 46 47// FromGatherer triggers a metric collection by the provided Gatherer (which is 48// usually implemented by a prometheus.Registry) and pushes all gathered metrics 49// to the Pushgateway specified by url, using the provided job name and the 50// (optional) further grouping labels (the grouping map may be nil). See the 51// Pushgateway documentation for detailed implications of the job and other 52// grouping labels. Neither the job name nor any grouping label value may 53// contain a "/". The metrics pushed must not contain a job label of their own 54// nor any of the grouping labels. 55// 56// You can use just host:port or ip:port as url, in which case 'http://' is 57// added automatically. You can also include the schema in the URL. However, do 58// not include the '/metrics/jobs/...' part. 59// 60// Note that all previously pushed metrics with the same job and other grouping 61// labels will be replaced with the metrics pushed by this call. (It uses HTTP 62// method 'PUT' to push to the Pushgateway.) 63func FromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error { 64 return push(job, grouping, url, g, "PUT") 65} 66 67// AddFromGatherer works like FromGatherer, but only previously pushed metrics 68// with the same name (and the same job and other grouping labels) will be 69// replaced. (It uses HTTP method 'POST' to push to the Pushgateway.) 70func AddFromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error { 71 return push(job, grouping, url, g, "POST") 72} 73 74func push(job string, grouping map[string]string, pushURL string, g prometheus.Gatherer, method string) error { 75 if !strings.Contains(pushURL, "://") { 76 pushURL = "http://" + pushURL 77 } 78 if strings.HasSuffix(pushURL, "/") { 79 pushURL = pushURL[:len(pushURL)-1] 80 } 81 82 if strings.Contains(job, "/") { 83 return fmt.Errorf("job contains '/': %s", job) 84 } 85 urlComponents := []string{url.QueryEscape(job)} 86 for ln, lv := range grouping { 87 if !model.LabelNameRE.MatchString(ln) { 88 return fmt.Errorf("grouping label has invalid name: %s", ln) 89 } 90 if strings.Contains(lv, "/") { 91 return fmt.Errorf("value of grouping label %s contains '/': %s", ln, lv) 92 } 93 urlComponents = append(urlComponents, ln, lv) 94 } 95 pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/")) 96 97 mfs, err := g.Gather() 98 if err != nil { 99 return err 100 } 101 buf := &bytes.Buffer{} 102 enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim) 103 // Check for pre-existing grouping labels: 104 for _, mf := range mfs { 105 for _, m := range mf.GetMetric() { 106 for _, l := range m.GetLabel() { 107 if l.GetName() == "job" { 108 return fmt.Errorf("pushed metric %s (%s) already contains a job label", mf.GetName(), m) 109 } 110 if _, ok := grouping[l.GetName()]; ok { 111 return fmt.Errorf( 112 "pushed metric %s (%s) already contains grouping label %s", 113 mf.GetName(), m, l.GetName(), 114 ) 115 } 116 } 117 } 118 enc.Encode(mf) 119 } 120 req, err := http.NewRequest(method, pushURL, buf) 121 if err != nil { 122 return err 123 } 124 req.Header.Set(contentTypeHeader, string(expfmt.FmtProtoDelim)) 125 resp, err := http.DefaultClient.Do(req) 126 if err != nil { 127 return err 128 } 129 defer resp.Body.Close() 130 if resp.StatusCode != 202 { 131 body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only. 132 return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, pushURL, body) 133 } 134 return nil 135} 136 137// Collectors works like FromGatherer, but it does not use a Gatherer. Instead, 138// it collects from the provided collectors directly. It is a convenient way to 139// push only a few metrics. 140func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error { 141 return pushCollectors(job, grouping, url, "PUT", collectors...) 142} 143 144// AddCollectors works like AddFromGatherer, but it does not use a Gatherer. 145// Instead, it collects from the provided collectors directly. It is a 146// convenient way to push only a few metrics. 147func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error { 148 return pushCollectors(job, grouping, url, "POST", collectors...) 149} 150 151func pushCollectors(job string, grouping map[string]string, url, method string, collectors ...prometheus.Collector) error { 152 r := prometheus.NewRegistry() 153 for _, collector := range collectors { 154 if err := r.Register(collector); err != nil { 155 return err 156 } 157 } 158 return push(job, grouping, url, r, method) 159} 160 161// HostnameGroupingKey returns a label map with the only entry 162// {instance="<hostname>"}. This can be conveniently used as the grouping 163// parameter if metrics should be pushed with the hostname as label. The 164// returned map is created upon each call so that the caller is free to add more 165// labels to the map. 166func HostnameGroupingKey() map[string]string { 167 hostname, err := os.Hostname() 168 if err != nil { 169 return map[string]string{"instance": "unknown"} 170 } 171 return map[string]string{"instance": hostname} 172} 173