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