expfmt.go 6.7 KB

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