expfmt.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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. // Package expfmt contains tools for reading and writing Prometheus metrics.
  14. package expfmt
  15. import (
  16. "errors"
  17. "strings"
  18. "github.com/prometheus/common/model"
  19. )
  20. // Format specifies the HTTP content type of the different wire protocols.
  21. type Format string
  22. // Constants to assemble the Content-Type values for the different wire
  23. // protocols. The Content-Type strings here are all for the legacy exposition
  24. // formats, where valid characters for metric names and label names are limited.
  25. // Support for arbitrary UTF-8 characters in those names is already partially
  26. // implemented in this module (see model.ValidationScheme), but to actually use
  27. // it on the wire, new content-type strings will have to be agreed upon and
  28. // added here.
  29. const (
  30. TextVersion = "0.0.4"
  31. ProtoType = `application/vnd.google.protobuf`
  32. ProtoProtocol = `io.prometheus.client.MetricFamily`
  33. // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead.
  34. ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";"
  35. OpenMetricsType = `application/openmetrics-text`
  36. OpenMetricsVersion_0_0_1 = "0.0.1"
  37. OpenMetricsVersion_1_0_0 = "1.0.0"
  38. // The Content-Type values for the different wire protocols. Do not do direct
  39. // comparisons to these constants, instead use the comparison functions.
  40. // Deprecated: Use expfmt.NewFormat(expfmt.TypeUnknown) instead.
  41. FmtUnknown Format = `<unknown>`
  42. // Deprecated: Use expfmt.NewFormat(expfmt.TypeTextPlain) instead.
  43. FmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8`
  44. // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoDelim) instead.
  45. FmtProtoDelim Format = ProtoFmt + ` encoding=delimited`
  46. // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoText) instead.
  47. FmtProtoText Format = ProtoFmt + ` encoding=text`
  48. // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead.
  49. FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
  50. // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead.
  51. FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8`
  52. // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead.
  53. FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8`
  54. )
  55. const (
  56. hdrContentType = "Content-Type"
  57. hdrAccept = "Accept"
  58. )
  59. // FormatType is a Go enum representing the overall category for the given
  60. // Format. As the number of Format permutations increases, doing basic string
  61. // comparisons are not feasible, so this enum captures the most useful
  62. // high-level attribute of the Format string.
  63. type FormatType int
  64. const (
  65. TypeUnknown FormatType = iota
  66. TypeProtoCompact
  67. TypeProtoDelim
  68. TypeProtoText
  69. TypeTextPlain
  70. TypeOpenMetrics
  71. )
  72. // NewFormat generates a new Format from the type provided. Mostly used for
  73. // tests, most Formats should be generated as part of content negotiation in
  74. // encode.go. If a type has more than one version, the latest version will be
  75. // returned.
  76. func NewFormat(t FormatType) Format {
  77. switch t {
  78. case TypeProtoCompact:
  79. return FmtProtoCompact
  80. case TypeProtoDelim:
  81. return FmtProtoDelim
  82. case TypeProtoText:
  83. return FmtProtoText
  84. case TypeTextPlain:
  85. return FmtText
  86. case TypeOpenMetrics:
  87. return FmtOpenMetrics_1_0_0
  88. default:
  89. return FmtUnknown
  90. }
  91. }
  92. // NewOpenMetricsFormat generates a new OpenMetrics format matching the
  93. // specified version number.
  94. func NewOpenMetricsFormat(version string) (Format, error) {
  95. if version == OpenMetricsVersion_0_0_1 {
  96. return FmtOpenMetrics_0_0_1, nil
  97. }
  98. if version == OpenMetricsVersion_1_0_0 {
  99. return FmtOpenMetrics_1_0_0, nil
  100. }
  101. return FmtUnknown, errors.New("unknown open metrics version string")
  102. }
  103. // WithEscapingScheme returns a copy of Format with the specified escaping
  104. // scheme appended to the end. If an escaping scheme already exists it is
  105. // removed.
  106. func (f Format) WithEscapingScheme(s model.EscapingScheme) Format {
  107. var terms []string
  108. for _, p := range strings.Split(string(f), ";") {
  109. toks := strings.Split(p, "=")
  110. if len(toks) != 2 {
  111. trimmed := strings.TrimSpace(p)
  112. if len(trimmed) > 0 {
  113. terms = append(terms, trimmed)
  114. }
  115. continue
  116. }
  117. key := strings.TrimSpace(toks[0])
  118. if key != model.EscapingKey {
  119. terms = append(terms, strings.TrimSpace(p))
  120. }
  121. }
  122. terms = append(terms, model.EscapingKey+"="+s.String())
  123. return Format(strings.Join(terms, "; "))
  124. }
  125. // FormatType deduces an overall FormatType for the given format.
  126. func (f Format) FormatType() FormatType {
  127. toks := strings.Split(string(f), ";")
  128. params := make(map[string]string)
  129. for i, t := range toks {
  130. if i == 0 {
  131. continue
  132. }
  133. args := strings.Split(t, "=")
  134. if len(args) != 2 {
  135. continue
  136. }
  137. params[strings.TrimSpace(args[0])] = strings.TrimSpace(args[1])
  138. }
  139. switch strings.TrimSpace(toks[0]) {
  140. case ProtoType:
  141. if params["proto"] != ProtoProtocol {
  142. return TypeUnknown
  143. }
  144. switch params["encoding"] {
  145. case "delimited":
  146. return TypeProtoDelim
  147. case "text":
  148. return TypeProtoText
  149. case "compact-text":
  150. return TypeProtoCompact
  151. default:
  152. return TypeUnknown
  153. }
  154. case OpenMetricsType:
  155. if params["charset"] != "utf-8" {
  156. return TypeUnknown
  157. }
  158. return TypeOpenMetrics
  159. case "text/plain":
  160. v, ok := params["version"]
  161. if !ok {
  162. return TypeTextPlain
  163. }
  164. if v == TextVersion {
  165. return TypeTextPlain
  166. }
  167. return TypeUnknown
  168. default:
  169. return TypeUnknown
  170. }
  171. }
  172. // ToEscapingScheme returns an EscapingScheme depending on the Format. Iff the
  173. // Format contains a escaping=allow-utf-8 term, it will select NoEscaping. If a valid
  174. // "escaping" term exists, that will be used. Otherwise, the global default will
  175. // be returned.
  176. func (format Format) ToEscapingScheme() model.EscapingScheme {
  177. for _, p := range strings.Split(string(format), ";") {
  178. toks := strings.Split(p, "=")
  179. if len(toks) != 2 {
  180. continue
  181. }
  182. key, value := strings.TrimSpace(toks[0]), strings.TrimSpace(toks[1])
  183. if key == model.EscapingKey {
  184. scheme, err := model.ToEscapingScheme(value)
  185. if err != nil {
  186. return model.NameEscapingScheme
  187. }
  188. return scheme
  189. }
  190. }
  191. return model.NameEscapingScheme
  192. }