Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDK metrics prototype #4

Open
wants to merge 131 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
274c571
somewhat works
iRevive Jan 12, 2024
7989154
refactor aggregator
iRevive Jan 14, 2024
3b6725a
add `ExplicitBucketHistogramAggregator`
iRevive Jan 15, 2024
8889f2a
experiment with measurement value
iRevive Jan 15, 2024
7d37db5
Merge branch 'upstream-main' into sdk-metrics/prototype
iRevive Jan 20, 2024
10f8cec
Merge branch 'sdk-metrics/prototype' into sdk-metrics/prototype-measu…
iRevive Jan 20, 2024
64f947c
Merge branch 'upstream-main' into sdk-metrics/prototype-measurement-v…
iRevive Jan 28, 2024
31b5fd7
merge upstream
iRevive Jan 28, 2024
1ffde15
run scalafix and scalafmt
iRevive Jan 28, 2024
bdad54c
support batch callback
iRevive Jan 28, 2024
254ac4a
add observable updowncounter and counter
iRevive Jan 29, 2024
c4c11eb
add OtlpHttpMetricExporter
iRevive Jan 30, 2024
bd350aa
refactor exporter modules
iRevive Jan 30, 2024
3bf6232
Merge branch 'upstream-main' into sdk-metrics/prototype-measurement-v…
iRevive Feb 13, 2024
3dd6938
merge upstream
iRevive Feb 13, 2024
954da41
rollback redundant changes
iRevive Feb 13, 2024
7ed6c08
cleanup
iRevive Feb 13, 2024
f515dd9
simplify `Data` model: use `Sum`, `Gauge` instead of `DoubleSum`, `Lo…
iRevive Feb 13, 2024
fe6fa2b
move type constraint to the metric storage
iRevive Feb 13, 2024
0a12926
move type constraint to the exemplar reservoir
iRevive Feb 13, 2024
6603750
run scalafix, scalafmt, etc
iRevive Feb 13, 2024
490484f
remove unused api
iRevive Feb 14, 2024
c5933c4
implement exemplar reservoir
iRevive Feb 15, 2024
760b608
generate ci files
iRevive Feb 15, 2024
9fd8c65
implement async metric storage
iRevive Feb 16, 2024
9d5571e
chores
iRevive Feb 16, 2024
1300b51
move storage to internal
iRevive Feb 16, 2024
98c387e
chores
iRevive Feb 16, 2024
19efc90
cleanup
iRevive Feb 16, 2024
a27a5c6
cleanup
iRevive Feb 18, 2024
3edaa2f
Merge branch 'upstream-main' into sdk-metrics/prototype
iRevive Feb 26, 2024
dca7d76
Restructure sdk metrics module
iRevive Feb 26, 2024
ae6e792
Add scaladoc
iRevive Feb 26, 2024
af551bd
Cleanup
iRevive Feb 27, 2024
f53d6d6
update docs
iRevive Feb 28, 2024
0221d46
Add `AggregationTemporalitySelector`, add docs
iRevive Feb 28, 2024
52b4e60
Add `ExemplarData.TraceContext`, add `SdkMetrics`, add MeterProvider …
iRevive Feb 29, 2024
ccc07ad
Merge branch 'upstream-main' into sdk-metrics/prototype
iRevive Mar 8, 2024
9ff5bbb
merge upstream
iRevive Mar 8, 2024
c8b4f6d
fix imports
iRevive Mar 8, 2024
4aeeb19
Merge branch 'upstream-main' into sdk-metrics/prototype
iRevive Mar 14, 2024
7a905e3
merge upstream
iRevive Mar 14, 2024
8946c94
rename handle -> accumulator, add `diff`
iRevive Mar 16, 2024
5894e21
rename async -> observable
iRevive Mar 21, 2024
d28340e
add Synchronous/Observable instruments
iRevive Mar 22, 2024
c9c4f0b
split synchronous and observable aggregators
iRevive Mar 22, 2024
767a503
clean up
iRevive Mar 22, 2024
68465f7
merge observable and synchronous aggregators
iRevive Mar 23, 2024
cad589a
use ByteVector in TraceContext
iRevive Mar 23, 2024
f28cf74
refactor exemplars
iRevive Mar 23, 2024
cff3eaf
use sealed traits for Data and PointData
iRevive Mar 24, 2024
e870803
add `Histogram.Stats`
iRevive Mar 24, 2024
4bfa349
add `TimeWindow`
iRevive Mar 24, 2024
98f8ac2
move count to stats
iRevive Mar 24, 2024
b0e6ea5
backport changes, drop redundant classes
iRevive Mar 24, 2024
ecadde7
update exemplar filter
iRevive Mar 24, 2024
3fc44a6
Merge branch 'upstream-main' into sdk-metrics/prototype
iRevive Mar 27, 2024
48f93eb
add docs
iRevive Mar 27, 2024
3b7b94b
Merge branch 'upstream-main' into sdk-metrics/prototype
iRevive Mar 29, 2024
f355055
make `sdk` depend on `sdk-metrics`
iRevive Mar 29, 2024
41e1ddb
refactor `AttributesProcessor`
iRevive Mar 29, 2024
6d87491
move view-specific interfaces to view package
iRevive Mar 29, 2024
f36ee11
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 6, 2024
9651908
merge upstream
iRevive Apr 6, 2024
adfee49
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 6, 2024
bfe65e1
rename `Data` -> `MetricPoints`
iRevive Apr 6, 2024
1fdc5fe
restructure modules
iRevive Apr 7, 2024
d4109c3
simplify `ViewRegistry`
iRevive Apr 7, 2024
3bf649a
cleanup
iRevive Apr 7, 2024
e13a496
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 7, 2024
a19241e
cleanup
iRevive Apr 9, 2024
e5c1d04
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 13, 2024
aad633f
rename `Measurement` -> `AsynchronousMeasurement`
iRevive Apr 13, 2024
0463cc3
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 16, 2024
b044a5a
Add scaladoc to `MetricData`
iRevive Apr 16, 2024
453316e
MetricDescriptor update
iRevive Apr 16, 2024
6fd29c4
remove default views from the ViewRegistry
iRevive Apr 17, 2024
3b25687
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 17, 2024
0195783
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 17, 2024
630fd84
minor updates
iRevive Apr 17, 2024
a0fb411
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 17, 2024
ea5f635
rearrange arguments, add scaladoc
iRevive Apr 18, 2024
842f191
add scaladocs
iRevive Apr 18, 2024
16dbdb0
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 18, 2024
a6187a2
minor refactor
iRevive Apr 18, 2024
3301d89
refactor, add warnings
iRevive Apr 19, 2024
d248cd2
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 19, 2024
fa7f47c
remove redundant changes
iRevive Apr 19, 2024
d599885
fix tests
iRevive Apr 19, 2024
a3182f3
simplify metric reader, remove CollectionRegistration
iRevive Apr 19, 2024
b13a197
minor refactoring, add scaladocs
iRevive Apr 19, 2024
348229d
fix doc
iRevive Apr 19, 2024
5b14653
cleanup
iRevive Apr 19, 2024
a6f8b01
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 19, 2024
a70c2a7
merge upstream
iRevive Apr 19, 2024
abc9989
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 19, 2024
02393eb
sdk-metrics: add `MetricProducer`
iRevive Apr 21, 2024
a751e38
update docs
iRevive Apr 21, 2024
a88762d
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 21, 2024
70e2666
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 22, 2024
43e987e
eliminate duplicated code
iRevive Apr 22, 2024
efb2c5e
rename storages
iRevive Apr 22, 2024
7610718
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 22, 2024
204a82b
cleanup
iRevive Apr 23, 2024
91ddc67
add scaladoc
iRevive Apr 23, 2024
92c5b1e
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 23, 2024
a752a7a
merge upstream
iRevive Apr 23, 2024
721c869
move `MeterSharedState` to `internal` package
iRevive Apr 23, 2024
a99281e
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 23, 2024
99282e2
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 23, 2024
a70b86f
merge upstream
iRevive Apr 23, 2024
f1b1c79
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 23, 2024
6102ffb
merge upstream
iRevive Apr 23, 2024
04f7a5b
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 24, 2024
ab3b023
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 24, 2024
d4a0091
merge upstream
iRevive Apr 24, 2024
e288e70
merge upstream
iRevive Apr 24, 2024
7a8e1cb
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 24, 2024
bf11770
merge upstream
iRevive Apr 24, 2024
90e34c9
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 24, 2024
99693b6
refactor MeterProviderAutoConfigure
iRevive Apr 24, 2024
476da1e
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 26, 2024
0884b9d
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 27, 2024
2562b2f
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 27, 2024
d3d6a15
add `ProtocolAutoConfigure`, `OtlpMetricExporterAutoConfigure`
iRevive Apr 28, 2024
09242c4
prepr
iRevive Apr 28, 2024
a4b479a
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 30, 2024
d51fe30
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive Apr 30, 2024
ae9e952
Merge branch 'refs/heads/upstream-main' into sdk-metrics/prototype
iRevive May 4, 2024
eee3c2e
Merge branch 'upstream-main' into sdk-metrics/prototype
iRevive Sep 2, 2024
2508a6b
merge upstream
iRevive Sep 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,67 @@ private object MetricsProtoEncoder {
Proto.Gauge(gauge.points.toVector.map(ProtoEncoder.encode(_)))
)

case summary: MetricPoints.Summary =>
Proto.Metric.Data.Summary(
Proto.Summary(
summary.points.toVector.map(p =>
Proto.SummaryDataPoint(
ProtoEncoder.encode(p.attributes),
p.timeWindow.start.toNanos,
p.timeWindow.end.toNanos,
p.count,
p.sum,
p.percentileValues.map { q =>
Proto.SummaryDataPoint
.ValueAtQuantile(q.quantile, q.value)
}
)
)
)
)

case histogram: MetricPoints.Histogram =>
Proto.Metric.Data.Histogram(
Proto.Histogram(
histogram.points.toVector.map(ProtoEncoder.encode(_)),
ProtoEncoder.encode(histogram.aggregationTemporality)
)
)

case exponentialHistogram: MetricPoints.ExponentialHistogram =>
Proto.Metric.Data.ExponentialHistogram(
Proto.ExponentialHistogram(
exponentialHistogram.points.toVector.map(p =>
Proto
.ExponentialHistogramDataPoint(
attributes = ProtoEncoder.encode(p.attributes),
startTimeUnixNano = p.timeWindow.start.toNanos,
timeUnixNano = p.timeWindow.end.toNanos,
count = p.count,
sum = Some(p.sum),
scale = 0, // todo scale
zeroCount = p.zeroCount,
positive = Some(
Proto.ExponentialHistogramDataPoint.Buckets(
p.positiveBuckets.offset,
p.positiveBuckets.bucketCounts
)
),
negative = Some(
Proto.ExponentialHistogramDataPoint.Buckets(
p.negativeBuckets.offset,
p.negativeBuckets.bucketCounts
)
),
exemplars = p.exemplars.map(ProtoEncoder.encode(_)),
min = p.min,
max = p.max,
// zeroThreshold = , // todo?
)
),
ProtoEncoder.encode(exponentialHistogram.aggregationTemporality)
)
)
}

implicit val metricDataEncoder: ProtoEncoder[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ object Aggregation {
* Compatible instruments:
* - [[org.typelevel.otel4s.metrics.Counter Counter]]
* - [[org.typelevel.otel4s.metrics.Histogram Histogram]]
*
* @see
* [[org.typelevel.otel4s.metrics.BucketBoundaries.default]] - default
* bucket boundaries
*/
def explicitBucketHistogram: Aggregation =
ExplicitBucketHistogram(Defaults.Boundaries)
Expand All @@ -111,6 +115,37 @@ object Aggregation {
def explicitBucketHistogram(boundaries: BucketBoundaries): Aggregation =
ExplicitBucketHistogram(boundaries)

/** Aggregates measurements into a base-2
* [[org.typelevel.otel4s.sdk.metrics.data.MetricPoints.ExponentialHistogram MetricPoints.ExponentialHistogram]]
* using the default `maxBuckets` and `maxScale`.
*
* Compatible instruments:
* - [[org.typelevel.otel4s.metrics.Counter Counter]]
* - [[org.typelevel.otel4s.metrics.Histogram Histogram]]
*/
def base2ExponentialBucketHistogram: Aggregation =
Base2ExponentialHistogram(160, 20) // todo: use const variables

/** Aggregates measurements into a base-2
* [[org.typelevel.otel4s.sdk.metrics.data.MetricPoints.ExponentialHistogram Data.ExponentialHistogram]]
* using the given `maxBuckets` and `maxScale`.
*
* Compatible instruments:
* - [[org.typelevel.otel4s.metrics.Counter Counter]]
* - [[org.typelevel.otel4s.metrics.Histogram Histogram]]
*
* @param maxBuckets
* the max number of positive and negative buckets
*
* @param maxScale
* the maximum and initial scale
*/
def base2ExponentialBucketHistogram(
maxBuckets: Int,
maxScale: Int
): Aggregation =
Base2ExponentialHistogram(maxBuckets, maxScale)

implicit val aggregationHash: Hash[Aggregation] =
Hash.fromUniversalHashCode

Expand All @@ -122,6 +157,8 @@ object Aggregation {
case LastValue => "Aggregation.LastValue"
case ExplicitBucketHistogram(boundaries) =>
show"Aggregation.ExplicitBucketHistogram{boundaries=$boundaries}"
case Base2ExponentialHistogram(maxBuckets, maxScale) =>
show"Aggregation.Base2ExponentialHistogram{maxBuckets=$maxBuckets, maxScale=$maxScale}"
}

private[metrics] sealed trait Synchronous { self: Aggregation => }
Expand Down Expand Up @@ -149,6 +186,12 @@ object Aggregation {
) extends Aggregation(Compatability.ExplicitBucketHistogram)
with Synchronous

private[metrics] final case class Base2ExponentialHistogram(
maxBuckets: Int,
maxScale: Int
) extends Aggregation(Compatability.Base2ExponentialHistogram)
with Synchronous

private object Compatability {
val Drop: Set[InstrumentType] =
InstrumentType.values
Expand All @@ -169,6 +212,9 @@ object Aggregation {

val ExplicitBucketHistogram: Set[InstrumentType] =
Set(InstrumentType.Counter, InstrumentType.Histogram)

val Base2ExponentialHistogram: Set[InstrumentType] =
Set(InstrumentType.Counter, InstrumentType.Histogram)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ private[metrics] object Aggregator {

case Aggregation.ExplicitBucketHistogram(boundaries) =>
histogram(boundaries)

case Aggregation.Base2ExponentialHistogram(_, _) =>
???
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ object MetricPoints {
def points: NonEmptyVector[Point]
}

sealed trait Summary extends MetricPoints {
def points: NonEmptyVector[PointData.Summary]
}

/** Histogram represents the type of a metric that is calculated by
* aggregating as a histogram of all reported double measurements over a time
* interval.
Expand All @@ -94,6 +98,11 @@ object MetricPoints {
def aggregationTemporality: AggregationTemporality
}

sealed trait ExponentialHistogram extends MetricPoints {
def points: NonEmptyVector[PointData.ExponentialHistogram]
def aggregationTemporality: AggregationTemporality
}

/** Creates a [[Sum]] with the given values.
*/
def sum[A <: PointData.NumberPoint](
Expand All @@ -110,6 +119,11 @@ object MetricPoints {
): Gauge =
GaugeImpl(points)

def summary(
points: NonEmptyVector[PointData.Summary]
): Summary =
SummaryImpl(points)

/** Creates a [[Histogram]] with the given values.
*/
def histogram(
Expand All @@ -118,6 +132,12 @@ object MetricPoints {
): Histogram =
HistogramImpl(points, aggregationTemporality)

def exponentialHistogram(
points: NonEmptyVector[PointData.ExponentialHistogram],
aggregationTemporality: AggregationTemporality
): ExponentialHistogram =
ExponentialHistogramImpl(points, aggregationTemporality)

implicit val metricPointsHash: Hash[MetricPoints] = {
val sumHash: Hash[Sum] =
Hash.by { s =>
Expand Down Expand Up @@ -187,9 +207,18 @@ object MetricPoints {
points: NonEmptyVector[A]
) extends Gauge { type Point = A }

private final case class SummaryImpl(
points: NonEmptyVector[PointData.Summary]
) extends Summary

private final case class HistogramImpl(
points: NonEmptyVector[PointData.Histogram],
aggregationTemporality: AggregationTemporality
) extends Histogram

private final case class ExponentialHistogramImpl(
points: NonEmptyVector[PointData.ExponentialHistogram],
aggregationTemporality: AggregationTemporality
) extends ExponentialHistogram

}
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,39 @@ object PointData {

}

sealed trait Summary extends PointData {
def count: Long
def sum: Double
def percentileValues: Vector[Summary.ValueAtQuantile]
}

object Summary {
sealed trait ValueAtQuantile {
def quantile: Double
def value: Double
}
}

sealed trait ExponentialHistogram extends PointData {
def exemplars: Vector[ExemplarData.DoubleExemplar]
def sum: Double
def zeroCount: Long
def min: Option[Double]
def max: Option[Double]
val count: Long
def positiveBuckets: ExponentialHistogram.Buckets
def negativeBuckets: ExponentialHistogram.Buckets
}

object ExponentialHistogram {
sealed trait Buckets {
def scale: Int
def offset: Int
def bucketCounts: Vector[Long]
def totalCount: Long
}
}

/** Creates a [[LongNumber]] with the given values.
*/
def longNumber(
Expand Down Expand Up @@ -204,6 +237,44 @@ object PointData {
): Histogram =
HistogramImpl(timeWindow, attributes, exemplars, stats, boundaries, counts)

def summary(
timeWindow: TimeWindow,
attributes: Attributes,
count: Long,
sum: Double,
percentileValues: Vector[Summary.ValueAtQuantile]
): Summary =
SummaryImpl(
timeWindow,
attributes,
count,
sum,
percentileValues
)

def exponentialHistogram(
timeWindow: TimeWindow,
attributes: Attributes,
exemplars: Vector[ExemplarData.DoubleExemplar],
sum: Double,
zeroCount: Long,
min: Option[Double],
max: Option[Double],
positiveBuckets: ExponentialHistogram.Buckets,
negativeBuckets: ExponentialHistogram.Buckets
): ExponentialHistogram =
ExponentialHistogramImpl(
timeWindow,
attributes,
exemplars,
sum,
zeroCount,
min,
max,
positiveBuckets,
negativeBuckets
)

implicit val pointDataHash: Hash[PointData] = {
val numberHash: Hash[NumberPoint] = {
// a value can be either Long or Double. The universal hashcode is safe
Expand Down Expand Up @@ -298,4 +369,27 @@ object PointData {
counts: Vector[Long]
) extends Histogram

private final case class SummaryImpl(
timeWindow: TimeWindow,
attributes: Attributes,
count: Long,
sum: Double,
percentileValues: Vector[Summary.ValueAtQuantile]
) extends Summary

private final case class ExponentialHistogramImpl(
timeWindow: TimeWindow,
attributes: Attributes,
exemplars: Vector[ExemplarData.DoubleExemplar],
sum: Double,
zeroCount: Long,
min: Option[Double],
max: Option[Double],
positiveBuckets: ExponentialHistogram.Buckets,
negativeBuckets: ExponentialHistogram.Buckets
) extends ExponentialHistogram {
val count: Long =
zeroCount + positiveBuckets.totalCount + negativeBuckets.totalCount
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package org.typelevel.otel4s.sdk.metrics.internal
import cats.Hash
import cats.Show
import cats.syntax.foldable._
import org.typelevel.otel4s.AttributeKey
import org.typelevel.otel4s.metrics.BucketBoundaries

/** Advisory options influencing aggregation configuration parameters.
Expand All @@ -33,6 +34,10 @@ private[metrics] sealed trait Advice {
*/
def explicitBucketBoundaries: Option[BucketBoundaries]

/** The list of the attribute keys to be used for the resulting instrument.
*/
def attributeKeys: Option[Set[AttributeKey[_]]]

override final def hashCode(): Int =
Hash[Advice].hash(this)

Expand All @@ -55,20 +60,26 @@ private[metrics] object Advice {
* aggregation is `explicit bucket histogram`
*/
def apply(bucketBoundaries: Option[BucketBoundaries]): Advice =
Impl(bucketBoundaries)
Impl(bucketBoundaries, None)

implicit val adviceHash: Hash[Advice] =
Hash.by(_.explicitBucketBoundaries)
Hash.by(a => (a.explicitBucketBoundaries, a.attributeKeys))

implicit val adviceShow: Show[Advice] =
Show.show { advice =>
val boundaries = advice.explicitBucketBoundaries.foldMap { b =>
val boundaries = advice.explicitBucketBoundaries.map { b =>
s"explicitBucketBoundaries=$b"
}
s"Advice{$boundaries}"

val attributeKeys = advice.attributeKeys.map { k =>
s"attributeKeys=${k.mkString("[", ",", "]")}"
}

Vector(attributeKeys, boundaries).flatten.mkString("Advice{", ", ", "}")
}

private final case class Impl(
explicitBucketBoundaries: Option[BucketBoundaries]
explicitBucketBoundaries: Option[BucketBoundaries],
attributeKeys: Option[Set[AttributeKey[_]]]
) extends Advice
}
Loading