For Gauges that are logically counters, Spectator doesn't have an API for expressing these. I would like to request either a) a new type that expresses this use, or b) extend the existing Gauge API to support it. Several alternatives were proposed to work around this, but they all have their own faults, which is why I believe the correct place to fix it is in Spectator itself.
A counter is a type where the value starts at 0, and goes up by integer amounts. A gauge is a type where the value starts at NaN, and can be set to any floating point amount. What I need is partly a counter, partly a gauge. The use case is keeping track of the number of active connections on a machine, which is an integral quantity, starts at 0, and can go up or down. On the surface, this seems trivial to implement with a PolledMeter. It gets much more complicated when tags are associated, and the Id is dynamic.
Alternatives Considered
Use PolledMeter and keep track of the counts myself.
This approach says to use my own AtomicLong to keep track of counts, and use PolledMeter to track the values. To implement this correctly, I would need to maintain my own ConcurrentMap of (Registry, Id) to AtomicLong, and increment them accordingly. The problem here is that Registries do not have a well defined equality , so it is risky to use this as a key value in the map. Registries do not claim they use instance identity, or that the hashCode is constant. This makes it unsuitable as a map key. Using just the the Id instead is almost workable, but it means keeping the registry around, rather than being able to pass it in as a param. Lastly, considering that Registry is already maintaining this map, it doesn't make sense to make users do it too.
PolledMeter has a second weakness in that it lazily checks the value of the meter. In a unit test, it is reasonable to get the meter from the registry, and check that value has been incremented. When using PolledMeter, the update is delayed, forcing the test to either: a) block at the end until it notices the counter has been updated b) implement a custom Executor for the test that updates values immediately. c) Skip checking the registry and look at the Id->AtomicLong map. All three are less than optimal and push burden onto the user.
Use Gauge API, and use set(value() + 1).
The idea here is to use the existing API, and call set() with the previous value plus the difference. This solution also works, but it forces additional burden on the caller. When creating a gauge for the first time, the initial value is NaN, which means it cannot be incremented. The initial value needs to be set to 0 if the previous value is NaN. Secondly, writing the value after reading it is racy, which means that synchronization needs to be involved to ensure that the gauge is updated safely. In a multithreaded program, including these locks increases the risk of blocking, and adds additional boilerplate to the code. The operation is logically a compareAndSwap (or getAndAdd), which means that using locks is overkill for the operation.
Implement a custom Gauge, rather than a whole new Type in Spectator.
A third way would be to implement the functionality I need, implementing the Gauge interface. The problem here is that registering my custom type with the registry is unnecessarily difficult. Registry provides a nice property that asking for the same Id will always return the same Meter. When creating my own type, this is no longer true, because there is no deduplication of the Ids to Meters. The user has to create a new gauge every time, attempt to call register(), and hope that the meter doesn't overwrite the previous one. (and from reading the code, it's not even clear this would work, since the meter state is the same, but the Meter itself is different.
An alternative to this would be managing the meter state directly from the registry using the state() function, but this again is unnecessarily verbose. It's a ton of boilerplate for what amounts to an getAndAdd.
Use DistributionSummary.
DistributionSummary (DS) is also proposed as an alternative, based on the claim that the values can be made lower. This has multiple issues as well. First, the value being measured, (e.g. active connections), is not a distribution. It wouldn't make sense to record this with a PercentileDistribution for instance. There is a fixed, known quantity at any given point in time, unlike a distribution. Second, DS only allows positive values, meaning that the meter cannot be decremented when a connection closes (see DefaultDistributionSummary.record). Instead, it is the responsibility of the caller to keep track of the actual counts, which is now the same as the polled meter case. Third, even if this was a workable solution, distribution adds tags to recorded meter called count. This name is confusing, because the meter name is already a count (like conn.active.count). The tag is not a count of the connections, but the count of how many times it has been called. This is needlessly confusing, and a user looking at the metrics in a UI would need to remember this peculiarity.
Proposed Alternative
Any non-negative integer gauge would be able to benefit from a proper CountingGauge type. Things like number of active requests, number of active threads, number of connections, number of elements in a queue. All of these would benefit from a type that can express their semantics. It would also be possible to add a compareAndSwap method to Gauge; the issue here is that the default value is difficult to set. (CAS(NaN, newValue) ?) Also, the existing gauge never claims what the default value is; this is something a new datatype would be able to express.
For Gauges that are logically counters, Spectator doesn't have an API for expressing these. I would like to request either a) a new type that expresses this use, or b) extend the existing Gauge API to support it. Several alternatives were proposed to work around this, but they all have their own faults, which is why I believe the correct place to fix it is in Spectator itself.
A counter is a type where the value starts at 0, and goes up by integer amounts. A gauge is a type where the value starts at NaN, and can be set to any floating point amount. What I need is partly a counter, partly a gauge. The use case is keeping track of the number of active connections on a machine, which is an integral quantity, starts at 0, and can go up or down. On the surface, this seems trivial to implement with a PolledMeter. It gets much more complicated when tags are associated, and the Id is dynamic.
Alternatives Considered
Use PolledMeter and keep track of the counts myself.
This approach says to use my own AtomicLong to keep track of counts, and use PolledMeter to track the values. To implement this correctly, I would need to maintain my own ConcurrentMap of
(Registry, Id)toAtomicLong, and increment them accordingly. The problem here is that Registries do not have a well defined equality , so it is risky to use this as a key value in the map. Registries do not claim they use instance identity, or that the hashCode is constant. This makes it unsuitable as a map key. Using just the theIdinstead is almost workable, but it means keeping the registry around, rather than being able to pass it in as a param. Lastly, considering that Registry is already maintaining this map, it doesn't make sense to make users do it too.PolledMeter has a second weakness in that it lazily checks the value of the meter. In a unit test, it is reasonable to get the meter from the registry, and check that value has been incremented. When using PolledMeter, the update is delayed, forcing the test to either: a) block at the end until it notices the counter has been updated b) implement a custom Executor for the test that updates values immediately. c) Skip checking the registry and look at the Id->AtomicLong map. All three are less than optimal and push burden onto the user.
Use Gauge API, and use
set(value() + 1).The idea here is to use the existing API, and call set() with the previous value plus the difference. This solution also works, but it forces additional burden on the caller. When creating a gauge for the first time, the initial value is NaN, which means it cannot be incremented. The initial value needs to be set to 0 if the previous value is NaN. Secondly, writing the value after reading it is racy, which means that synchronization needs to be involved to ensure that the gauge is updated safely. In a multithreaded program, including these locks increases the risk of blocking, and adds additional boilerplate to the code. The operation is logically a compareAndSwap (or getAndAdd), which means that using locks is overkill for the operation.
Implement a custom Gauge, rather than a whole new Type in Spectator.
A third way would be to implement the functionality I need, implementing the Gauge interface. The problem here is that registering my custom type with the registry is unnecessarily difficult. Registry provides a nice property that asking for the same Id will always return the same Meter. When creating my own type, this is no longer true, because there is no deduplication of the Ids to Meters. The user has to create a new gauge every time, attempt to call register(), and hope that the meter doesn't overwrite the previous one. (and from reading the code, it's not even clear this would work, since the meter state is the same, but the Meter itself is different.
An alternative to this would be managing the meter state directly from the registry using the
state()function, but this again is unnecessarily verbose. It's a ton of boilerplate for what amounts to an getAndAdd.Use DistributionSummary.
DistributionSummary (DS) is also proposed as an alternative, based on the claim that the values can be made lower. This has multiple issues as well. First, the value being measured, (e.g. active connections), is not a distribution. It wouldn't make sense to record this with a PercentileDistribution for instance. There is a fixed, known quantity at any given point in time, unlike a distribution. Second, DS only allows positive values, meaning that the meter cannot be decremented when a connection closes (see DefaultDistributionSummary.record). Instead, it is the responsibility of the caller to keep track of the actual counts, which is now the same as the polled meter case. Third, even if this was a workable solution, distribution adds tags to recorded meter called
count. This name is confusing, because the meter name is already a count (likeconn.active.count). The tag is not a count of the connections, but the count of how many times it has been called. This is needlessly confusing, and a user looking at the metrics in a UI would need to remember this peculiarity.Proposed Alternative
Any non-negative integer gauge would be able to benefit from a proper
CountingGaugetype. Things like number of active requests, number of active threads, number of connections, number of elements in a queue. All of these would benefit from a type that can express their semantics. It would also be possible to add a compareAndSwap method to Gauge; the issue here is that the default value is difficult to set. (CAS(NaN, newValue) ?) Also, the existing gauge never claims what the default value is; this is something a new datatype would be able to express.