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

Expose CircuitBreaker current condition (or state) to the metrics recorder interface and Prometheus. #25

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

mickdwyer
Copy link

No description provided.

@mickdwyer mickdwyer requested a review from slok as a code owner May 5, 2019 10:20
Copy link
Owner

@slok slok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mickdwyer thanks for the contribution.

I think that this metric is valuable, adding to this I've written you some comments on the implementation, but the most important thing is how the metric is designed, having the user to "interpret" what state is the gauge value is difficult, a better approach that I've seen in some exporters and apps (e.g. kube-metrics with kube_pod_status_phase) would be to have the state on a label and setting the active state with a 1 and the others to 0.

What do you think?

return stateStrings[st]
}

func (st state) condition () int {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we could remove the integer usage for state and use only the type and the string representation of it. It would simplify the usage and we remove code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, will refactor accordingly. (Am I right in that you're happy with the use of iota?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The iota is perfect

}

func (st state) label () string {
return stateStrings[st]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 things:

  • Instead of label what do you think about implementing the Stringer interface?

  • This could panic if a state does not have an associated string in the array, although we control this it could lead to a runtime panic in a change. A better approach could be using a switch with a fallback string, would be safer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to read up on the Stringer interface but it feels a more natural fit.
I agree using a switch offers a more defensive implementation.

Will consider both when refactoring.

stateOpen state = "open"
stateHalfOpen state = "halfopen"
stateClosed state = "closed"
stateNew state = iota
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New state is the same as a closed state because a new circuitbreaker starts in a closed state. Do you have a use case where you need to know the difference between new and closed?

Copy link
Author

@mickdwyer mickdwyer May 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My choice to implement stateNew and the transition to stateClosed on first traversal provided a mechanism to ensure that the state of the breaker was recorded on first use.

Otherwise, I found that there are no state metrics recorded until the first state change occurs.

I might take a few moments to review how I'm using the circuitbreaker instance by my application.

@@ -22,6 +22,8 @@ type Recorder interface {
IncBulkheadTimeout()
// IncCircuitbreakerState increments the number of state change.
IncCircuitbreakerState(state string)
// SetCircuitbreakerCurrentCondition sets the condition of ciruit breaker.
SetCircuitbreakerCurrentCondition(condition int)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be better to have uniformity between method signatures:

  • On name: SetCircuitbreakerCurrentCondition -> SetCircuitbreakerCurrentState
  • On parameter: condition int -> state string

Copy link
Author

@mickdwyer mickdwyer May 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will make the name change.
I suspect the parameter change will fall out of the refactoring of the metric approach. Stay tuned!

@@ -157,6 +173,9 @@ func (c *circuitbreaker) Run(ctx context.Context, f goresilience.Func) error {
func (c *circuitbreaker) preDecideState(metricsRec metrics.Recorder) {
state := c.getState()
switch state {
case stateNew:
// Close the breaker as this is the first time through and generate a statistic.
c.moveState(stateClosed, metricsRec)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As stated before, the initial state of a circuit breaker is closed, so the above change of state: stateNew, and this moveState would not be required.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, although it's coupled to my comment above.

@@ -28,6 +31,7 @@ type prometheusRec struct {
bulkProcessed *prometheus.CounterVec
bulkTimeouts *prometheus.CounterVec
cbStateChanges *prometheus.CounterVec
cbCurrentCondition *prometheus.GaugeVec
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uniformity naming: cbCurrentCondition -> cbCurrentState

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will make this change

@@ -122,6 +127,13 @@ func (p *prometheusRec) registerMetrics() {
Help: "Total number of state changes made by the circuit breaker runner.",
}, []string{"id", "state"})

p.cbCurrentCondition = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above Condition -> State

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will make this change.

@@ -210,6 +223,10 @@ func (p prometheusRec) IncCircuitbreakerState(state string) {
p.cbStateChanges.WithLabelValues(p.id, state).Inc()
}

func (p prometheusRec) SetCircuitbreakerCurrentCondition(condition int) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with this metric approach is that the user needs to be aware of what is the number when it queries, this is not very natural and could lead to easy misinterpretation of the metrics.

To have the state of a resource in a metric what I have seen as a standardized way is that the state is in a label and when one of the states is set to "active" is set to 1 and the others to 0. Is the same approach as the state counter but with a gauge of (0,1).

What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that your proposal to follow the conventions you have seen elsewhere is worth adopting. I'll make the necessary refactoring to implement this design.

@mickdwyer
Copy link
Author

Hi @slok, thank you for the very constructive feed back. I found it particularly helpful as this is my first use of Golang.

I'll comment again once I've recoded to the feedback above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants