-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Add exemplar support #65
Changes from all commits
c3c581b
52b7a25
856f5fc
ced84b2
99bfcbb
a32300b
328943b
243b57b
2b26af7
863797f
a53ef95
68dce79
73091ef
30556f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright 2022 Permutive | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package prometheus4cats.internal | ||
|
||
import prometheus4cats.Exemplar | ||
|
||
import scala.reflect.macros.blackbox | ||
|
||
private[prometheus4cats] trait ExemplarLabelNameFromStringLiteral { | ||
|
||
def apply(t: String): Exemplar.LabelName = | ||
macro ExemplarLabelNameMacros.fromStringLiteral | ||
|
||
implicit def fromStringLiteral(t: String): Exemplar.LabelName = | ||
macro ExemplarLabelNameMacros.fromStringLiteral | ||
|
||
} | ||
|
||
private[prometheus4cats] class ExemplarLabelNameMacros(val c: blackbox.Context) extends MacroUtils { | ||
|
||
def fromStringLiteral(t: c.Expr[String]): c.Expr[Exemplar.LabelName] = { | ||
val string: String = literal(t, or = "Exemplar.LabelName.from({string})") | ||
|
||
Exemplar.LabelName | ||
.from(string) | ||
.fold( | ||
abort, | ||
_ => c.universe.reify(Exemplar.LabelName.from(t.splice).toOption.get) | ||
) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* Copyright 2022 Permutive | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package prometheus4cats.internal | ||
|
||
import prometheus4cats._ | ||
|
||
import scala.quoted.* | ||
|
||
private[prometheus4cats] trait ExemplarLabelNameFromStringLiteral { | ||
|
||
inline def apply(inline t: String): Exemplar.LabelName = ${ | ||
ExemplarLabelNameFromStringLiteral.nameLiteral('t) | ||
} | ||
|
||
implicit inline def fromStringLiteral(inline t: String): Exemplar.LabelName = ${ | ||
ExemplarLabelNameFromStringLiteral.nameLiteral('t) | ||
} | ||
|
||
} | ||
|
||
private[prometheus4cats] object ExemplarLabelNameFromStringLiteral extends MacroUtils { | ||
def nameLiteral(s: Expr[String])(using q: Quotes): Expr[Exemplar.LabelName] = | ||
s.value match { | ||
case Some(string) => | ||
Exemplar.LabelName | ||
.from(string) | ||
.fold( | ||
error, | ||
_ => | ||
'{ | ||
Exemplar.LabelName.from(${ Expr(string) }).toOption.get | ||
} | ||
) | ||
case None => | ||
abort("Exemplar.LabelName.from") | ||
'{ ??? } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* | ||
* Copyright 2022 Permutive | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package prometheus4cats | ||
|
||
import cats.Applicative | ||
import cats.syntax.show._ | ||
import prometheus4cats.internal.ExemplarLabelNameFromStringLiteral | ||
|
||
import java.util.regex.Pattern | ||
import scala.collection.immutable.SortedMap | ||
|
||
/** A typeclass to provide exemplars to counters and histograms, which may be used by [[MetricRegistry]] | ||
* implementations. | ||
*/ | ||
trait Exemplar[F[_]] { | ||
def get: F[Option[Exemplar.Labels]] | ||
} | ||
|
||
object Exemplar { | ||
def apply[F[_]: Exemplar]: Exemplar[F] = implicitly | ||
|
||
object Implicits { | ||
implicit def noop[F[_]: Applicative]: Exemplar[F] = new Exemplar[F] { | ||
override def get: F[Option[Labels]] = Applicative[F].pure(None) | ||
} | ||
} | ||
|
||
/** Refined value class for an exemplar label name that has been parsed from a string | ||
*/ | ||
final class LabelName private (val value: String) extends AnyVal with internal.Refined.Value[String] { | ||
override def toString: String = s"""Exemplar.LabelName("$value")""" | ||
} | ||
|
||
object LabelName extends internal.Refined.StringRegexRefinement[LabelName] with ExemplarLabelNameFromStringLiteral { | ||
protected val regex: Pattern = "^[a-zA-Z_][a-zA-Z_0-9]*$".r.pattern | ||
|
||
implicit val ordering: Ordering[LabelName] = catsInstances.toOrdering | ||
|
||
override protected def make(a: String): LabelName = new LabelName(a) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Why this method? This comment follows the conventionalcomments.org standard There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That comes from our internal refinement code |
||
} | ||
|
||
/** Refined value class for a set of exemplar labels. | ||
* | ||
* Validation will fail if the labels are empty or if the length of all the label names and values does not exceed | ||
* 128 UTF-8 characters | ||
*/ | ||
final class Labels private (val value: SortedMap[LabelName, String]) | ||
extends AnyVal | ||
with internal.Refined.Value[SortedMap[LabelName, String]] { | ||
override def toString: String = s"""Exemplar.Labels("${value.show}")""" | ||
} | ||
|
||
object Labels extends internal.Refined[SortedMap[LabelName, String], Labels] { | ||
def of(first: (LabelName, String), rest: (LabelName, String)*): Either[String, Labels] = | ||
from(rest.foldLeft(SortedMap.empty[LabelName, String].updated(first._1, first._2)) { case (acc, (k, v)) => | ||
acc.updated(k, v) | ||
}) | ||
|
||
def fromMap(a: Map[LabelName, String]): Either[String, Labels] = from( | ||
a.foldLeft(SortedMap.empty[LabelName, String]) { case (acc, (k, v)) => acc.updated(k, v) } | ||
) | ||
|
||
override protected def make(a: SortedMap[LabelName, String]): Labels = new Labels(a) | ||
|
||
override protected def test(a: SortedMap[LabelName, String]): Boolean = | ||
a.nonEmpty && a.map { case (k, v) => s"${k.value}$v".length }.sum <= 128 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. praise: Very clever! This comment follows the conventionalcomments.org standard |
||
|
||
override protected def nonMatchMessage(a: SortedMap[LabelName, String]): String = | ||
"exemplar labels must not be empty and the combined length of the label names and values must not exceed 128 UTF-8 characters" | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: You should be able to remove it since cats'
Order
already provides this as an implicit conversion.This comment follows the conventionalcomments.org standard
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep it does, but we're going the other way as
SortedMap
needsOrdering