-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Adding a Description field to ZIO MetricKey #8159
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
Conversation
0f618c5
to
9f5118b
Compare
This change is not binary compatible. |
@vladimirkl thank you for mentioning you issue! @adamgfraser do I have a chance to bring this MR to its logical end or you are categorically against adding a description to the metric? The solution with passing description via tags is really looks like workaround... Now we are very close to the long-awaited release of Micrometer integration (zio/zio-metrics-connectors#42). And if we stay with the solution when description is passed through tags, then we will be forced to make the Micrometer layer depend on the MetricsConfig (like here https://github.com/zio/zio-metrics-connectors/blob/zio/series2.x/core/src/main/scala/zio/metrics/connectors/prometheus/package.scala#L10) in which the description tag key will be located. It seems to be very strange for user without context of our discussion. |
@Grryum I'm not opposed to adding the description but it needs to be done in a binary compatible way. What I proposed before plus using overloads instead of variable arguments should do the trick: sealed case class MetricKey[+Type] private (
name: String,
keyType: Type,
tags: Set[MetricLabel] = Set.empty
) { self =>
def description: Option[String] =
None
def copy[Type2](name: String = name, keyType: Type2 = keyType, tags: Set[MetricLabel] = tags): MetricKey[Type2] =
new MetricKey(name, keyType, tags) {
override def description: Option[String] =
self.description
}
} We also need to think about whether the description should be part of the identity of the key. |
@adamgfraser thank you very much! I will rewrite |
9f5118b
to
1304c93
Compare
@adamgfraser I rewrote the MR. Now it adds description in binary compatible way. The main focus of this update is to redefine the semantic behavior of the "description" field, making it "unmodifiable". Please have a look |
@Grryum Instead we should make it part of the constructor of a metric as you had previously done, using overloading instead of default arguments to avoid binary compatibility issues: object Metric {
def gauge(name: String, description: String): Gauge[Double] =
fromMetricKey(MetricKey.gauge(name))
} |
1304c93
to
b857b94
Compare
@adamgfraser sorry, seems that I misunderstood you.
leaded to a compile error Both version of |
@Grryum This is not binary compatible. You need to use overloading, not default arguments. Do this: object Metric {
def gauge(name: String): Gauge[Double] =
???
def gauge(name: String, description: String): Gauge[Double] =
???
} Not this: object Metric {
def gauge(name: String, description: Option[String] = None): Gauge[Double] =
???
} |
b857b94
to
4935718
Compare
4935718
to
e9fedd5
Compare
@adamgfraser finally got it :) |
/** | ||
* Returns a new `MetricKey` with the specified description. | ||
*/ | ||
def described(description0: String): MetricKey[Type] = |
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.
We should delete this and instead provide variants of the constructors allowing specifying a description just like we did for the metrics themselves since there should be no way to update the description once a metric is created.
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.
@adamgfraser sure, done!
…overloaded alternatives.
@Grryum What is your thought regarding whether the description should be part of the identity of the key? |
@adamgfraser In my opinion, I believe that adding a description to the identity of Metric key is not advisable. This is because metrics with different descriptions would be treated as separate metrics with separate counters. In my view, the semantic of the description field should be considered as some kind of label that can be optionally set. Furthermore, incorporating a description into the metric key does not align with the integration concepts of Prometheus, Micrometer, and potentially other systems. According to Micrometer's documentation (https://micrometer.io/docs/concepts#_meters): Similarly, the Prometheus documentation (https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels) highlights that each time series is uniquely identified by its metric name and optional tags: Additionally, as I mentioned before I had verified that Micrometer follows the approach of not allowing updates to the metric description once it is initially set. This aligns with the philosophy of maintaining metrics identity. Thank you for considering this perspective, and I am open to further discussions or clarification on this matter. |
@Grryum I think everything you said makes sense but I'm not sure it addresses the issue. If you look at import zio.metrics._
object Example extends App {
val metric1 = MetricKey.counter("metric1")
val metric2 = MetricKey.counter("metric1").tagged("key1", "value1")
println(metric1 == metric2) // false
} This obviously works okay with all of the metric backends we have implemented. Under the current implementation on the other hand we would have the following: import zio.metrics._
object Example extends App {
val metric1 = MetricKey.counter("metric1")
val metric2 = MetricKey.counter("metric1", "description1")
println(metric1 == metric2) // true
} This arguably isn't crazy, perhaps you say the description is an "implementation detail", but it is at the very least a little weird. These two metric keys have obviously different properties (calling Note also that in your original pull request the description would have been part of the identity of the key since it was a field of a case class. And I think that was indeed the correct way to do that absent these binary compatibility issues. So I tend to think we should try to mirror as much as possible what we would have done if we were implementing this today and didn't have to work around the binary compatibility issues, which would mean overriding |
@adamgfraser Thank you for your detailed response. If we consider implementing a metric key with a description from scratch, I think there are globally three possible options: Option 1: Implement a case class Option 2: Implement a case class Option 3: Implement a case class We would also need to modify the MetricListener methods to accept an optional description parameter.
This way, the description can be propagated through the listener callbacks at internal method
Additionally, a public constructor for Metric can be created, taking a DescribedMetricKey as a parameter and utilizing the description via notifyListeners.
This approach maintains transparency for the user and avoids binary compatibility issues (only internal modifications). First option is not possible for us because of binary compatibility issues. Second we discussed above. |
I would just override |
@adamgfraser but if we override
would it be ok for users? |
@Grryum We already have: test("Test metrics with tags") {
val name = "counterName"
val key = "key"
val value = "value"
val counter1 = Metric.counter(name)
val counter2 = Metric.counter(name).tagged(key, value)
for {
_ <- counter1.update(1L)
_ <- counter2.update(1L)
r1 <- counter1.value
r2 <- counter2.value
} yield assertTrue(
r1 == MetricState.Counter(1d),
r2 == MetricState.Counter(1d)
)
} So I think your test should fail. |
…y the description.
aedd49f
to
99b96b6
Compare
@adamgfraser I overridden |
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.
Looks great! Shall we override toString
as well so that the MetricKey
shows the description as if it was a field of the case class. Then I think this will be ready to go!
@adamgfraser sure! I have pushed a new commit |
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.
Thank you for your work on this! 🙏
@adamgfraser glad to work together to make the world a little better :) |
The MetricKey describes metric with its name and tags.
With this MR, the MetricKey is extended to include a "description" field, allowing developers to provide meaningful descriptions for each metric.
Problem
While working on new metric connector (zio/zio-metrics-connectors#42) I was faced with the need to extract the description of the metric.
Similar issue (zio/zio-metrics-connectors#23) was solved via throwing description through metrics tags set (https://github.com/zio/zio-metrics-connectors/pull/32/files#diff-fcbe5916f5b460e7cca68e385fbb00d736fcb533e5b2867ee09a7a3e38be17b9R35). IMO this is not very clean approach because tags is not a storage for metadata at all.
We need to have a separate typed field (optional) in MetricKey to be able to operate with metric description without any non clear workarounds.
Additional motivation
If MR is accepted I would be able to fix description extraction in zio-metrics-connector module.