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 14package expfmt 15 16import ( 17 "fmt" 18 "io" 19 "net/http" 20 21 "github.com/golang/protobuf/proto" 22 "github.com/matttproud/golang_protobuf_extensions/pbutil" 23 "github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg" 24 25 dto "github.com/prometheus/client_model/go" 26) 27 28// Encoder types encode metric families into an underlying wire protocol. 29type Encoder interface { 30 Encode(*dto.MetricFamily) error 31} 32 33// Closer is implemented by Encoders that need to be closed to finalize 34// encoding. (For example, OpenMetrics needs a final `# EOF` line.) 35// 36// Note that all Encoder implementations returned from this package implement 37// Closer, too, even if the Close call is a no-op. This happens in preparation 38// for adding a Close method to the Encoder interface directly in a (mildly 39// breaking) release in the future. 40type Closer interface { 41 Close() error 42} 43 44type encoderCloser struct { 45 encode func(*dto.MetricFamily) error 46 close func() error 47} 48 49func (ec encoderCloser) Encode(v *dto.MetricFamily) error { 50 return ec.encode(v) 51} 52 53func (ec encoderCloser) Close() error { 54 return ec.close() 55} 56 57// Negotiate returns the Content-Type based on the given Accept header. If no 58// appropriate accepted type is found, FmtText is returned (which is the 59// Prometheus text format). This function will never negotiate FmtOpenMetrics, 60// as the support is still experimental. To include the option to negotiate 61// FmtOpenMetrics, use NegotiateOpenMetrics. 62func Negotiate(h http.Header) Format { 63 for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) { 64 ver := ac.Params["version"] 65 if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol { 66 switch ac.Params["encoding"] { 67 case "delimited": 68 return FmtProtoDelim 69 case "text": 70 return FmtProtoText 71 case "compact-text": 72 return FmtProtoCompact 73 } 74 } 75 if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { 76 return FmtText 77 } 78 } 79 return FmtText 80} 81 82// NegotiateIncludingOpenMetrics works like Negotiate but includes 83// FmtOpenMetrics as an option for the result. Note that this function is 84// temporary and will disappear once FmtOpenMetrics is fully supported and as 85// such may be negotiated by the normal Negotiate function. 86func NegotiateIncludingOpenMetrics(h http.Header) Format { 87 for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) { 88 ver := ac.Params["version"] 89 if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol { 90 switch ac.Params["encoding"] { 91 case "delimited": 92 return FmtProtoDelim 93 case "text": 94 return FmtProtoText 95 case "compact-text": 96 return FmtProtoCompact 97 } 98 } 99 if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { 100 return FmtText 101 } 102 if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion || ver == "") { 103 return FmtOpenMetrics 104 } 105 } 106 return FmtText 107} 108 109// NewEncoder returns a new encoder based on content type negotiation. All 110// Encoder implementations returned by NewEncoder also implement Closer, and 111// callers should always call the Close method. It is currently only required 112// for FmtOpenMetrics, but a future (breaking) release will add the Close method 113// to the Encoder interface directly. The current version of the Encoder 114// interface is kept for backwards compatibility. 115func NewEncoder(w io.Writer, format Format) Encoder { 116 switch format { 117 case FmtProtoDelim: 118 return encoderCloser{ 119 encode: func(v *dto.MetricFamily) error { 120 _, err := pbutil.WriteDelimited(w, v) 121 return err 122 }, 123 close: func() error { return nil }, 124 } 125 case FmtProtoCompact: 126 return encoderCloser{ 127 encode: func(v *dto.MetricFamily) error { 128 _, err := fmt.Fprintln(w, v.String()) 129 return err 130 }, 131 close: func() error { return nil }, 132 } 133 case FmtProtoText: 134 return encoderCloser{ 135 encode: func(v *dto.MetricFamily) error { 136 _, err := fmt.Fprintln(w, proto.MarshalTextString(v)) 137 return err 138 }, 139 close: func() error { return nil }, 140 } 141 case FmtText: 142 return encoderCloser{ 143 encode: func(v *dto.MetricFamily) error { 144 _, err := MetricFamilyToText(w, v) 145 return err 146 }, 147 close: func() error { return nil }, 148 } 149 case FmtOpenMetrics: 150 return encoderCloser{ 151 encode: func(v *dto.MetricFamily) error { 152 _, err := MetricFamilyToOpenMetrics(w, v) 153 return err 154 }, 155 close: func() error { 156 _, err := FinalizeOpenMetrics(w) 157 return err 158 }, 159 } 160 } 161 panic(fmt.Errorf("expfmt.NewEncoder: unknown format %q", format)) 162} 163