func (p *Pusher) fullURL() string {
	urlComponents := []string{}
	if encodedJob, base64 := encodeComponent(p.job); base64 {
		urlComponents = append(urlComponents, "job"+base64Suffix, encodedJob)
	} else {
		urlComponents = append(urlComponents, "job", encodedJob)
	}
	for ln, lv := range p.grouping {
		if encodedLV, base64 := encodeComponent(lv); base64 {
			urlComponents = append(urlComponents, ln+base64Suffix, encodedLV)
		} else {
			urlComponents = append(urlComponents, ln, encodedLV)
		}
	}
	return fmt.Sprintf("%s/metrics/%s", p.url, strings.Join(urlComponents, "/"))
}
fullURL assembles the URL used to push/delete metrics and returns it as a
string. The job name and any grouping label values containing a '/' will
trigger a base64 encoding of the affected component and proper suffixing of
the preceding component. Similarly, an empty grouping label value will be
encoded as base64 just with a single `=` padding character (to avoid an empty
path component). If the component does not contain a '/' but other special
characters, the usual url.QueryEscape is used for compatibility with older
versions of the Pushgateway and for better readability.
package push
import (
	"bytes"
	"encoding/base64"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"github.com/prometheus/common/expfmt"
	"github.com/prometheus/common/model"
	"github.com/prometheus/client_golang/prometheus"
)
const (
	contentTypeHeader = "Content-Type"
	base64Suffix = "@base64"
)
var errJobEmpty = errors.New("job name is empty")
type HTTPDoer interface {
	Do(*http.Request) (*http.Response, error)
}
type Pusher struct {
	error error
	url, job string
	grouping map[string]string
	gatherers  prometheus.Gatherers
	registerer prometheus.Registerer
	client             HTTPDoer
	useBasicAuth       bool
	username, password string
	expfmt expfmt.Format
}
func New(url, job string) *Pusher {
	var (
		reg = prometheus.NewRegistry()
		err error
	)
	if job == "" {
		err = errJobEmpty
	}
	if !strings.Contains(url, "://") {
		url = "http://" + url
	}
	if strings.HasSuffix(url, "/") {
		url = url[:len(url)-1]
	}
	return &Pusher{
		error:      err,
		url:        url,
		job:        job,
		grouping:   map[string]string{},
		gatherers:  prometheus.Gatherers{reg},
		registerer: reg,
		client:     &http.Client{},
		expfmt:     expfmt.FmtProtoDelim,
	}
}
func (p *Pusher) Push() error {
	return p.push(http.MethodPut)
}
func (p *Pusher) Add() error {
	return p.push(http.MethodPost)
}
func (p *Pusher) Gatherer(g prometheus.Gatherer) *Pusher {
	p.gatherers = append(p.gatherers, g)
	return p
}
func (p *Pusher) Collector(c prometheus.Collector) *Pusher {
	if p.error == nil {
		p.error = p.registerer.Register(c)
	}
	return p
}
func (p *Pusher) Grouping(name, value string) *Pusher {
	if p.error == nil {
		if !model.LabelName(name).IsValid() {
			p.error = fmt.Errorf("grouping label has invalid name: %s", name)
			return p
		}
		p.grouping[name] = value
	}
	return p
}
func (p *Pusher) Client(c HTTPDoer) *Pusher {
	p.client = c
	return p
}
func (p *Pusher) BasicAuth(username, password string) *Pusher {
	p.useBasicAuth = true
	p.username = username
	p.password = password
	return p
}
func (p *Pusher) Format(format expfmt.Format) *Pusher {
	p.expfmt = format
	return p
}
func (p *Pusher) Delete() error {
	if p.error != nil {
		return p.error
	}
	req, err := http.NewRequest(http.MethodDelete, p.fullURL(), nil)
	if err != nil {
		return err
	}
	if p.useBasicAuth {
		req.SetBasicAuth(p.username, p.password)
	}
	resp, err := p.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusAccepted {
		body, _ := ioutil.ReadAll(resp.Body)  
		return fmt.Errorf("unexpected status code %d while deleting %s: %s", resp.StatusCode, p.fullURL(), body)
	}
	return nil
}
func (p *Pusher) push(method string) error {
	if p.error != nil {
		return p.error
	}
	mfs, err := p.gatherers.Gather()
	if err != nil {
		return err
	}
	buf := &bytes.Buffer{}
	enc := expfmt.NewEncoder(buf, p.expfmt)
	for _, mf := range mfs {
		for _, m := range mf.GetMetric() {
			for _, l := range m.GetLabel() {
				if l.GetName() == "job" {
					return fmt.Errorf("pushed metric %s (%s) already contains a job label", mf.GetName(), m)
				}
				if _, ok := p.grouping[l.GetName()]; ok {
					return fmt.Errorf(
						"pushed metric %s (%s) already contains grouping label %s",
						mf.GetName(), m, l.GetName(),
					)
				}
			}
		}
		enc.Encode(mf)
	}
	req, err := http.NewRequest(method, p.fullURL(), buf)
	if err != nil {
		return err
	}
	if p.useBasicAuth {
		req.SetBasicAuth(p.username, p.password)
	}
	req.Header.Set(contentTypeHeader, string(p.expfmt))
	resp, err := p.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
		body, _ := ioutil.ReadAll(resp.Body)  
		return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, p.fullURL(), body)
	}
	return nil
}