circuit

package module
v4.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 19, 2024 License: Apache-2.0 Imports: 7 Imported by: 2

README

Mascot

Circuit

Build Status GoDoc Coverage Status

Circuit is an efficient and feature complete Hystrix like Go implementation of the circuit breaker pattern. Learn more about the problems Hystrix and other circuit breakers solve on the Hystrix Wiki. A short summary of advantages are:

  • A downstream service failed and all requests hang forever. Without a circuit, your service would also hang forever. Because you have a circuit, you detect this failure quickly and can return errors quickly while waiting for the downstream service to recover.
  • Circuits make great monitoring and metrics boundaries, creating common metric names for the common downstream failure types. This package goes further to formalize this in a SLO tracking pattern.
  • Circuits create a common place for downstream failure fallback logic.
  • Downstream services sometimes fail entirely when overloaded. While in a degraded state, circuits allow you to push downstream services to the edge between absolute failure and mostly working.
  • Open/Close state of a circuit is a clear early warning sign of downstream failures.
  • Circuits allow you to protect your dependencies from abnormal rushes of traffic.

There are a large number of examples on the godoc that are worth looking at. They tend to be more up to date than the README doc.

Feature set

  • No forced goroutines
  • recoverable panic()
  • Integrated with context.Context
  • Comprehensive metric tracking
  • Efficient implementation with Benchmarks
  • Low/zero memory allocation costs
  • Support for Netflix Hystrix dashboards, even with custom circuit transition logic
  • Multiple error handling features
  • Expose circuit health and configuration on expvar
  • SLO tracking
  • Customizable state transition logic, allowing complex circuit state changes
  • Live configuration changes
  • Many tests and examples
  • Good inline documentation
  • Generatable interface wrapping support with https://github.com/twitchtv/circuitgen
  • Support for Additive increase/multiplicative decrease
  • Prometheus metrics collector.

Upgrading

See UPGRADE_GUIDE.md for upgrade instructions if you're upgrading from v3 to v4.

Usage

Hello world circuit

This example shows how to create a hello-world circuit from the circuit manager

// Manages all our circuits
h := circuit.Manager{}
// Create a circuit with a unique name
c := h.MustCreateCircuit("hello-world")
// Call the circuit
errResult := c.Execute(context.Background(), func(ctx context.Context) error {
  return nil
}, nil)
fmt.Println("Result of execution:", errResult)
// Output: Result of execution: <nil>

Hello world fallback

This example shows how fallbacks execute to return alternate errors or provide logic when the circuit is open.

// You can create circuits without using the manager
c := circuit.NewCircuitFromConfig("hello-world-fallback", circuit.Config{})
errResult := c.Execute(context.Background(), func(ctx context.Context) error {
	return errors.New("this will fail")
}, func(ctx context.Context, err error) error {
	fmt.Println("Circuit failed with error, but fallback returns nil")
	return nil
})
fmt.Println("Execution result:", errResult)
// Output: Circuit failed with error, but fallback returns nil
// Execution result: <nil>

Running inside a Goroutine

It is recommended to use circuit.Execute and a context aware function. If, however, you want to exit your run function early and leave it hanging (possibly forever), then you can call circuit.Go.

h := circuit.Manager{}
c := h.MustCreateCircuit("untrusting-circuit", circuit.Config{
  Execution: circuit.ExecutionConfig{
    // Time out the context after a few ms
    Timeout: time.Millisecond * 30,
  },
})

errResult := c.Go(context.Background(), func(ctx context.Context) error {
  // Sleep 30 seconds, way longer than our timeout
  time.Sleep(time.Second * 30)
  return nil
}, nil)
fmt.Printf("err=%v", errResult)
// Output: err=context deadline exceeded

Hystrix Configuration

All configuration parameters are documented in config.go. Your circuit open/close logic configuration is documented with the logic. For hystrix, this configuration is in closers/hystrix and well documented on the Hystrix wiki.

This example configures the circuit to use Hystrix open/close logic with the default Hystrix parameters

configuration := hystrix.Factory{
  // Hystrix open logic is to open the circuit after an % of errors
  ConfigureOpener: hystrix.ConfigureOpener{
    // We change the default to wait for 10 requests, not 20, before checking to close
    RequestVolumeThreshold: 10,
    // The default values match what hystrix does by default
  },
  // Hystrix close logic is to sleep then check
  ConfigureCloser: hystrix.ConfigureCloser{
    // The default values match what hystrix does by default
  },
}
h := circuit.Manager{
  // Tell the manager to use this configuration factory whenever it makes a new circuit
  DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{configuration.Configure},
}
// This circuit will inherit the configuration from the example
c := h.MustCreateCircuit("hystrix-circuit")
fmt.Println("This is a hystrix configured circuit", c.Name())
// Output: This is a hystrix configured circuit hystrix-circuit

Enable dashboard metrics

Dashboard metrics can be enabled with the MetricEventStream object. This example creates an event stream handler, starts it, then later closes the handler

// metriceventstream uses rolling stats to report circuit information
sf := rolling.StatFactory{}
h := circuit.Manager{
  DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{sf.CreateConfig},
}
es := metriceventstream.MetricEventStream{
  Manager: &h,
}
go func() {
  if err := es.Start(); err != nil {
    log.Fatal(err)
  }
}()
// ES is a http.Handler, so you can pass it directly to your mux
http.Handle("/hystrix.stream", &es)
// ...
if err := es.Close(); err != nil {
  log.Fatal(err)
}
// Output:

Enable expvar

If you wanted to publish hystrix information on Expvar, you can register your manager.

h := circuit.Manager{}
expvar.Publish("hystrix", h.Var())

Custom metrics

Implement interfaces CmdMetricCollector or FallbackMetricCollector to know what happens with commands or fallbacks. Then pass those implementations to configure.

config := circuit.Config{
  Metrics: circuit.MetricsCollectors{
    Run: []circuit.RunMetrics{
      // Here is where I would insert my custom metric collector
    },
  },
}
circuit.NewCircuitFromConfig("custom-metrics", config)

Panics

Code executed with Execute does not spawn a goroutine and panics naturally go up the call stack to the caller. This is also true for Go, where we attempt to recover and throw panics on the same stack that calls Go. This example will panic, and the panic can be caught up the stack.

h := circuit.Manager{}
c := h.MustCreateCircuit("panic_up")

defer func() {
 r := recover()
 if r != nil {
   fmt.Println("I recovered from a panic", r)
 }
}()
c.Execute(context.Background(), func(ctx context.Context) error {
 panic("oh no")
}, nil)
// Output: I recovered from a panic oh no

Runtime configuration changes

Most configuration properties on the Hystrix Configuration page that say they are modifyable at runtime can be changed on the Circuit in a thread safe way. Most of the ones that cannot are related to stat collection.

This example shows how to update hystrix configuration at runtime.

// Start off using the defaults
configuration := hystrix.ConfigFactory{}
h := circuit.Manager{
  // Tell the manager to use this configuration factory whenever it makes a new circuit
  DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{configuration.Configure},
}
c := h.MustCreateCircuit("hystrix-circuit")
fmt.Println("The default sleep window", c.OpenToClose.(*hystrix.Closer).Config().SleepWindow)
// This configuration update function is thread safe.  We can modify this at runtime while the circuit is active
c.OpenToClose.(*hystrix.Closer).SetConfigThreadSafe(hystrix.ConfigureCloser{
  SleepWindow: time.Second * 3,
})
fmt.Println("The new sleep window", c.OpenToClose.(*hystrix.Closer).Config().SleepWindow)
// Output:
// The default sleep window 5s
// The new sleep window 3s

Not counting early terminations as failures

If the context passed into a circuit function ends, before the circuit can finish, it does not count the circuit as unhealthy. You can disable this behavior with the IgnoreInterrupts flag.

This example proves that terminating a circuit call early because the passed in context died does not, by default, count as an error on the circuit. It also demonstrates setting up internal stat collection by default for all circuits

// Inject stat collection to prove these failures don't count
f := rolling.StatFactory{}
manager := circuit.Manager{
  DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{
    f.CreateConfig,
  },
}
c := manager.MustCreateCircuit("don't fail me bro")
// The passed in context times out in one millisecond
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
defer cancel()
errResult := c.Execute(ctx, func(ctx context.Context) error {
  select {
  case <- ctx.Done():
    // This will return early, with an error, since the parent context was canceled after 1 ms
    return ctx.Err()
  case <- time.After(time.Hour):
    panic("We never actually get this far")
  }
}, nil)
rs := f.RunStats("don't fail me bro")
fmt.Println("errResult is", errResult)
fmt.Println("The error and timeout count is", rs.ErrTimeouts.TotalSum() + rs.ErrFailures.TotalSum())
// Output: errResult is context deadline exceeded
// The error and timeout count is 0

Configuration factories

Configuration factories are supported on the root manager object. This allows you to create dynamic configuration per circuit name.

You can use DefaultCircuitProperties to set configuration dynamically for any circuit

myFactory := func(circuitName string) circuit.Config {
  timeoutsByName := map[string]time.Duration{
    "v1": time.Second,
    "v2": time.Second * 2,
  }
  customTimeout := timeoutsByName[circuitName]
  if customTimeout == 0 {
    // Just return empty if you don't want to set any config
    return circuit.Config{}
  }
  return circuit.Config{
    Execution: circuit.ExecutionConfig{
      Timeout: customTimeout,
    },
  }
}

// Hystrix manages circuits with unique names
h := circuit.Manager{
  DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{myFactory},
}
h.MustCreateCircuit("v1")
fmt.Println("The timeout of v1 is", h.GetCircuit("v1").Config().Execution.Timeout)
// Output: The timeout of v1 is 1s

StatsD configuration factory

A configuration factory for statsd is provided inside ./metrics/statsdmetrics

This example shows how to inject a statsd metric collector into a circuit.

// This factory allows us to report statsd metrics from the circuit
f := statsdmetrics.CommandFactory{
  SubStatter: &statsd.NoopClient{},
}

// Wire the statsd factory into the circuit manager
h := circuit.Manager{
  DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{f.CommandProperties},
}
// This created circuit will now use statsd
h.MustCreateCircuit("using-statsd")
// Output:

Service health tracking

Most services have the concept of an SLA, or service level agreement. Unfortunantly, this is usually tracked by the service owners, which creates incentives for people to inflate the health of their service.

This Circuit implementation formalizes an SLO of the template "X% of requests will return faster than Y ms". This is a value that canont be calculated just by looking at the p90 or p99 of requests in aggregate, but must be tracked per request. You can define a SLO for your service, which is a time less than the timeout time of a request, that works as a promise of health for the service. You can then report per circuit not just fail/pass but an extra "healthy" % over time that counts only requests that resopnd quickly enough.

This example creates a SLO tracker that counts failures at less than 20 ms. You will need to provide your own Collectors.

sloTrackerFactory := responsetimeslo.Factory{
  Config: responsetimeslo.Config{
    // Consider requests faster than 20 ms as passing
    MaximumHealthyTime: time.Millisecond * 20,
  },
  // Pass in your collector here: for example, statsd
  CollectorConstructors: nil,
}
h := circuit.Manager{
  DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{sloTrackerFactory.CommandProperties},
}
h.CreateCircuit("circuit-with-slo")

Not counting user error as a fault

Sometimes users pass invalid functions to the input of your circuit. You want to return an error in that case, but not count the error as a failure of the circuit. Use SimpleBadRequest in this case.

This example shows how to return errors in a circuit without considering the circuit at fault. Here, even if someone tries to divide by zero, the circuit will not consider it a failure even if the function returns non nil error.

c := circuit.NewCircuitFromConfig("divider", circuit.Config{})
divideInCircuit := func(numerator, denominator int) (int, error) {
  var result int
  err := c.Run(context.Background(), func(ctx context.Context) error {
    if denominator == 0 {
      // This error type is not counted as a failure of the circuit
      return &circuit.SimpleBadRequest{
        Err: errors.New("someone tried to divide by zero"),
      }
    }
    result = numerator / denominator
    return nil
  })
  return result, err
}
_, err := divideInCircuit(10, 0)
fmt.Println("Result of 10/0 is", err)
// Output: Result of 10/0 is someone tried to divide by zero

Benchmarking

This implementation is more efficient than go-hystrix in every configuration. It has comparable efficiency to other implementations, faster for most when running with high concurrency. Run benchmarks with make bench.

I benchmark the following alternative circuit implementations. I try to be fair and if there is a better way to benchmark one of these circuits, please let me know!

> make bench
cd benchmarking && go test -v -benchmem -run=^$ -bench=. . 2> /dev/null
goos: darwin
goarch: amd64
pkg: github.com/cep21/circuit/benchmarking
BenchmarkCiruits/cep21-circuit/Hystrix/passing/1-8       	 2000000	       896 ns/op	     192 B/op	       4 allocs/op
BenchmarkCiruits/cep21-circuit/Hystrix/passing/75-8      	 3000000	       500 ns/op	     192 B/op	       4 allocs/op
BenchmarkCiruits/cep21-circuit/Hystrix/failing/1-8       	10000000	       108 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/cep21-circuit/Hystrix/failing/75-8      	20000000	        82.5 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/cep21-circuit/Minimal/passing/1-8       	10000000	       165 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/cep21-circuit/Minimal/passing/75-8      	20000000	        87.7 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/cep21-circuit/Minimal/failing/1-8       	20000000	        64.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/cep21-circuit/Minimal/failing/75-8      	100000000	        19.6 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/cep21-circuit/UseGo/passing/1-8         	 1000000	      1300 ns/op	     256 B/op	       5 allocs/op
BenchmarkCiruits/cep21-circuit/UseGo/passing/75-8        	 5000000	       374 ns/op	     256 B/op	       5 allocs/op
BenchmarkCiruits/cep21-circuit/UseGo/failing/1-8         	 1000000	      1348 ns/op	     256 B/op	       5 allocs/op
BenchmarkCiruits/cep21-circuit/UseGo/failing/75-8        	 5000000	       372 ns/op	     256 B/op	       5 allocs/op
BenchmarkCiruits/GoHystrix/DefaultConfig/passing/1-8     	  200000	      8146 ns/op	    1001 B/op	      18 allocs/op
BenchmarkCiruits/GoHystrix/DefaultConfig/passing/75-8    	  500000	      2498 ns/op	     990 B/op	      20 allocs/op
BenchmarkCiruits/GoHystrix/DefaultConfig/failing/1-8     	  200000	      6299 ns/op	    1020 B/op	      19 allocs/op
BenchmarkCiruits/GoHystrix/DefaultConfig/failing/75-8    	 1000000	      1582 ns/op	    1003 B/op	      20 allocs/op
BenchmarkCiruits/rubyist/Threshold-10/passing/1-8        	 1000000	      1834 ns/op	     332 B/op	       5 allocs/op
BenchmarkCiruits/rubyist/Threshold-10/passing/75-8       	 2000000	       849 ns/op	     309 B/op	       4 allocs/op
BenchmarkCiruits/rubyist/Threshold-10/failing/1-8        	20000000	       114 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/rubyist/Threshold-10/failing/75-8       	 5000000	       302 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/gobreaker/Default/passing/1-8           	10000000	       202 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/gobreaker/Default/passing/75-8          	 2000000	       698 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/gobreaker/Default/failing/1-8           	20000000	        90.6 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/gobreaker/Default/failing/75-8          	 5000000	       346 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/handy/Default/passing/1-8               	 2000000	      1075 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/handy/Default/passing/75-8              	 1000000	      1795 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/handy/Default/failing/1-8               	 1000000	      1272 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/handy/Default/failing/75-8              	 1000000	      1686 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/iand_circuit/Default/passing/1-8        	10000000	       119 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/iand_circuit/Default/passing/75-8       	 5000000	       349 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/iand_circuit/Default/failing/1-8        	100000000	        20.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/iand_circuit/Default/failing/75-8       	300000000	         5.46 ns/op	       0 B/op	       0 allocs/op
PASS
ok      github.com/cep21/circuit/benchmarking   59.518s

Limiting to just high concurrency passing circuits (the common case).

BenchmarkCiruits/cep21-circuit/Minimal/passing/75-8      	20000000	        87.7 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/GoHystrix/DefaultConfig/passing/75-8    	  500000	      2498 ns/op	     990 B/op	      20 allocs/op
BenchmarkCiruits/rubyist/Threshold-10/passing/75-8       	 2000000	       849 ns/op	     309 B/op	       4 allocs/op
BenchmarkCiruits/gobreaker/Default/passing/75-8          	 2000000	       698 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/handy/Default/passing/75-8              	 1000000	      1795 ns/op	       0 B/op	       0 allocs/op
BenchmarkCiruits/iand_circuit/Default/passing/75-8       	 5000000	       349 ns/op	       0 B/op	       0 allocs/op

Development

Make sure your tests pass with go test and your lints pass with golangci-lint run.

Example

You can run an example set of circuits inside the /example directory

go run example/main.go

The output looks something like this:

go run example/main.go
2017/12/19 15:24:42 Serving on socket :8123
2017/12/19 15:24:42 To view the stream, execute:
2017/12/19 15:24:42   curl http://localhost:8123/hystrix.stream
2017/12/19 15:24:42
2017/12/19 15:24:42 To view expvar metrics, visit expvar in your browser
2017/12/19 15:24:42   http://localhost:8123/debug/vars
2017/12/19 15:24:42
2017/12/19 15:24:42 To view a dashboard, follow the instructions at https://github.com/Netflix/Hystrix/wiki/Dashboard#run-via-gradle
2017/12/19 15:24:42   git clone git@github.com:Netflix/Hystrix.git
2017/12/19 15:24:42   cd Hystrix/hystrix-dashboard
2017/12/19 15:24:42   ../gradlew jettyRun
2017/12/19 15:24:42
2017/12/19 15:24:42 Then, add the stream http://localhost:8123/hystrix.stream

If you load the Hystrix dasbhoard (following the above instructions), you should see metrics for all the example circuits.

dashboard

Documentation

Overview

Package circuit is a Go implementation of the circuit breaker pattern. Most documentation is available on the GitHub README page https://github.com/cep21/circuit/blob/master/README.md

Use case

Netflix describes most use cases on their wiki for Hystrix at https://github.com/Netflix/Hystrix/wiki. Quoting the wiki:

Give protection from and control over latency and failure from dependencies accessed (typically over the network) via third-party client libraries.
Stop cascading failures in a complex distributed system.
Fail fast and rapidly recover.
Fallback and gracefully degrade when possible.
Enable near real-time monitoring, alerting, and operational control.

It is a great library for microservice applications that require a large number of calls to many, small services where any one of these calls could fail, or any of these services could be down or degraded.

Getting started

The godoc contains many examples. Look at them for a good start on how to get started integrated and using the Hystrix library for Go.

Circuit Flowchart

A circuits start Closed. The default logic is to open a circuit if more than 20 requests have come in during a 10-second window, and over 50% of requests during that 10-second window are failing.

Once failed, the circuit waits 10 seconds before allowing a single request. If that request succeeds, then the circuit closes. If it fails, then the circuit waits another 10 seconds before allowing another request (and so on).

Almost every part of this flow can be configured. See the CommandProperties struct for information.

Metric tracking

All circuits record circuit stats that you can fetch out of the Circuit at any time. In addition, you can also inject your own circuit stat trackers by modifying the MetricsCollectors structure.

Example (Http)

This is a full example of using a circuit around HTTP requests.

h := circuit.Manager{}
c := h.MustCreateCircuit("hello-http", circuit.Config{
	Execution: circuit.ExecutionConfig{
		// Timeout after 3 seconds
		Timeout: time.Second * 3,
	},
})

testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
	_, _ = io.WriteString(rw, "hello world")
}))
defer testServer.Close()

var body bytes.Buffer
runErr := c.Run(context.Background(), func(ctx context.Context) error {
	req, err := http.NewRequest("GET", testServer.URL, nil)
	if err != nil {
		return circuit.SimpleBadRequest{Err: err}
	}
	req = req.WithContext(ctx)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	if resp.StatusCode >= 400 && resp.StatusCode <= 499 {
		return circuit.SimpleBadRequest{Err: errors.New("server found your request invalid")}
	}
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		return fmt.Errorf("invalid status code: %d", resp.StatusCode)
	}
	if _, err := io.Copy(&body, resp.Body); err != nil {
		return err
	}
	return resp.Body.Close()
})
if runErr == nil {
	fmt.Printf("We saw a body\n")
	return
}
fmt.Printf("There was an error with the request: %s\n", runErr)
Output:

We saw a body

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsBadRequest

func IsBadRequest(err error) bool

IsBadRequest returns true if the error is of type BadRequest

Types

type BadRequest

type BadRequest interface {
	BadRequest() bool
}

BadRequest is implemented by an error returned by runFunc if you want to consider the requestor bad, not the circuit bad. See http://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/exception/HystrixBadRequestException.html and https://github.com/Netflix/Hystrix/wiki/How-To-Use#error-propagation for information.

Example

This example shows how to return errors in a circuit without considering the circuit at fault. Here, even if someone tries to divide by zero, the circuit will not consider it a failure even if the function returns non nil error.

c := circuit.NewCircuitFromConfig("divider", circuit.Config{})
divideInCircuit := func(numerator, denominator int) (int, error) {
	var result int
	err := c.Run(context.Background(), func(ctx context.Context) error {
		if denominator == 0 {
			// This error type is not counted as a failure of the circuit
			return &circuit.SimpleBadRequest{
				Err: errors.New("someone tried to divide by zero"),
			}
		}
		result = numerator / denominator
		return nil
	})
	return result, err
}
_, err := divideInCircuit(10, 0)
fmt.Println("Result of 10/0 is", err)
Output:

Result of 10/0 is someone tried to divide by zero

type Circuit

type Circuit struct {
	// circuitStats
	CmdMetricCollector      RunMetricsCollection
	FallbackMetricCollector FallbackMetricsCollection
	CircuitMetricsCollector MetricsCollection

	// ClosedToOpen controls when to open a closed circuit
	ClosedToOpen ClosedToOpen
	// openToClosed controls when to close an open circuit
	OpenToClose OpenToClosed
	// contains filtered or unexported fields
}

Circuit is a circuit breaker pattern implementation that can accept commands and open/close on failures

Example (Noearlyterminate)

If the context passed into a circuit function ends, before the circuit can finish, it does not count the circuit as unhealthy. You can disable this behavior with the `IgnoreInterrupts` flag.

This example proves that terminating a circuit call early because the passed in context died does not, by default, count as an error on the circuit. It also demonstrates setting up internal stat collection by default for all circuits

// Inject stat collection to prove these failures don't count
f := rolling.StatFactory{}
manager := circuit.Manager{
	DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{
		f.CreateConfig,
	},
}
c := manager.MustCreateCircuit("don't fail me bro")
// The passed in context times out in one millisecond
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
defer cancel()
errResult := c.Execute(ctx, func(ctx context.Context) error {
	select {
	case <-ctx.Done():
		// This will return early, with an error, since the parent context was canceled after 1 ms
		return ctx.Err()
	case <-time.After(time.Hour):
		panic("We never actually get this far")
	}
}, nil)
rs := f.RunStats("don't fail me bro")
fmt.Println("errResult is", errResult)
fmt.Println("The error and timeout count is", rs.ErrTimeouts.TotalSum()+rs.ErrFailures.TotalSum())
Output:

errResult is context deadline exceeded
The error and timeout count is 0

func NewCircuitFromConfig

func NewCircuitFromConfig(name string, config Config) *Circuit

NewCircuitFromConfig creates an inline circuit. If you want to group all your circuits together, you should probably just use Manager struct instead.

func (*Circuit) CloseCircuit

func (c *Circuit) CloseCircuit(ctx context.Context)

CloseCircuit closes an open circuit. Usually because we think it's healthy again. Be aware, if the circuit isn't actually healthy, it will just open back up again.

func (*Circuit) ConcurrentCommands

func (c *Circuit) ConcurrentCommands() int64

ConcurrentCommands returns how many commands are currently running

func (*Circuit) ConcurrentFallbacks

func (c *Circuit) ConcurrentFallbacks() int64

ConcurrentFallbacks returns how many fallbacks are currently running

func (*Circuit) Config

func (c *Circuit) Config() Config

Config returns the circuit's configuration. Modifications to this configuration are not reflected by the circuit. In other words, this creates a copy.

func (*Circuit) Execute

func (c *Circuit) Execute(ctx context.Context, runFunc func(context.Context) error, fallbackFunc func(context.Context, error) error) error

Execute the circuit. Prefer this over Go. Similar to http://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html#execute-- The returned error will either be the result of runFunc, the result of fallbackFunc, or an internal library error. Internal library errors will match the interface Error and you can use type casting to check this.

Example (Fallback)

This example shows execute failing (marking the circuit with a failure), but not returning an error back to the user since the fallback was able to execute. For this case, we try to load the size of the largest message a user can send, but fall back to 140 if the load fails.

c := circuit.NewCircuitFromConfig("divider", circuit.Config{})
var maximumMessageSize int
err := c.Execute(context.Background(), func(_ context.Context) error {
	return errors.New("your circuit failed")
}, func(ctx context.Context, err2 error) error {
	maximumMessageSize = 140
	return nil
})
fmt.Printf("value=%d err=%v", maximumMessageSize, err)
Output:

value=140 err=<nil>
Example (Fallbackhelloworld)

This example shows how fallbacks execute to return alternate errors or provide logic when the circuit is open.

// You can create circuits without using the manager
c := circuit.NewCircuitFromConfig("hello-world-fallback", circuit.Config{})
errResult := c.Execute(context.Background(), func(ctx context.Context) error {
	return errors.New("this will fail")
}, func(ctx context.Context, err error) error {
	fmt.Println("Circuit failed with error, but fallback returns nil")
	return nil
})
fmt.Println("Execution result:", errResult)
Output:

Circuit failed with error, but fallback returns nil
Execution result: <nil>
Example (Helloworld)

This example shows execute failing (marking the circuit with a failure), but not returning an error back to the user since the fallback was able to execute. For this case, we try to load the size of the largest message a user can send, but fall back to 140 if the load fails.

c := circuit.NewCircuitFromConfig("hello-world", circuit.Config{})
err := c.Execute(context.Background(), func(_ context.Context) error {
	return nil
}, nil)
fmt.Printf("err=%v", err)
Output:

err=<nil>
Example (Panics)

Code executed with `Execute` does not spawn a goroutine and panics naturally go up the call stack to the caller. This is also true for `Go`, where we attempt to recover and throw panics on the same stack that calls Go. This example will panic, and the panic can be caught up the stack.

h := circuit.Manager{}
c := h.MustCreateCircuit("panic_up")

defer func() {
	r := recover()
	if r != nil {
		fmt.Println("I recovered from a panic", r)
	}
}()
_ = c.Execute(context.Background(), func(ctx context.Context) error {
	panic("oh no")
}, nil)
Output:

I recovered from a panic oh no

func (*Circuit) Go

func (c *Circuit) Go(ctx context.Context, runFunc func(context.Context) error, fallbackFunc func(context.Context, error) error) error

Go executes `Execute`, but uses spawned goroutines to end early if the context is canceled. Use this if you don't trust the runFunc to end correctly if context fails. This is a design mirroed in the go-hystrix library, but be warned it is very dangerous and could leave orphaned goroutines hanging around forever doing who knows what.

Example

It is recommended to use `circuit.Execute` and a context aware function. If, however, you want to exit your run function early and leave it hanging (possibly forever), then you can call `circuit.Go`.

h := circuit.Manager{}
c := h.MustCreateCircuit("untrusting-circuit", circuit.Config{
	Execution: circuit.ExecutionConfig{
		// Time out the context after a few ms
		Timeout: time.Millisecond * 30,
	},
})

errResult := c.Go(context.Background(), func(ctx context.Context) error {
	// Sleep 30 seconds, way longer than our timeout
	time.Sleep(time.Second * 30)
	return nil
}, nil)
fmt.Printf("err=%v", errResult)
Output:

err=context deadline exceeded
Example (Panics)

Even though Go executes inside a goroutine, we catch that panic and bubble it up the same call stack that called Go

c := circuit.NewCircuitFromConfig("panic_up", circuit.Config{})

defer func() {
	r := recover()
	if r != nil {
		fmt.Println("I recovered from a panic", r)
	}
}()
_ = c.Go(context.Background(), func(ctx context.Context) error {
	panic("oh no")
}, nil)
Output:

I recovered from a panic oh no

func (*Circuit) IsOpen

func (c *Circuit) IsOpen() bool

IsOpen returns true if the circuit should be considered 'open' (ie not allowing runFunc calls)

func (*Circuit) Name

func (c *Circuit) Name() string

Name of this circuit

func (*Circuit) OpenCircuit

func (c *Circuit) OpenCircuit(ctx context.Context)

OpenCircuit will open a closed circuit. The circuit will then try to repair itself

func (*Circuit) Run

func (c *Circuit) Run(ctx context.Context, runFunc func(context.Context) error) error

Run will execute the circuit without a fallback. It is the equivalent of calling Execute with a nil fallback function

func (*Circuit) SetConfigNotThreadSafe

func (c *Circuit) SetConfigNotThreadSafe(config Config)

SetConfigNotThreadSafe is only useful during construction before a circuit is being used. It is not thread safe, but will modify all the circuit's internal structs to match what the config wants. It also doe *NOT* use the default configuration parameters.

func (*Circuit) SetConfigThreadSafe

func (c *Circuit) SetConfigThreadSafe(config Config)

SetConfigThreadSafe changes the current configuration of this circuit. Note that many config parameters, specifically those around creating stat tracking buckets, are not modifiable during runtime for efficiency reasons. Those buckets will stay the same.

Example

Many configuration variables can be set at runtime in a thread safe way

h := circuit.Manager{}
c := h.MustCreateCircuit("changes-at-runtime", circuit.Config{})
// ... later on (during live)
c.SetConfigThreadSafe(circuit.Config{
	Execution: circuit.ExecutionConfig{
		MaxConcurrentRequests: int64(12),
	},
})
Output:

func (*Circuit) Var

func (c *Circuit) Var() expvar.Var

Var exports that help diagnose the circuit

type ClosedToOpen

type ClosedToOpen interface {
	RunMetrics
	Metrics
	// ShouldOpen will attempt to open a circuit that is currently closed, after a bad request comes in.  Only called
	// after bad requests, never called after a successful request
	ShouldOpen(ctx context.Context, now time.Time) bool
	// Prevent a single request from going through while the circuit is closed.
	// Even though the circuit is closed, and we want to allow the circuit to remain closed, we still prevent this
	// command from happening.  The error will return as a short circuit to the caller, as well as trigger fallback
	// logic.  This could be useful if your circuit is closed, but some external force wants you to pretend to be open.
	Prevent(ctx context.Context, now time.Time) bool
}

ClosedToOpen receives events and controls if the circuit should open or close as a result of those events. Return true if the circuit should open, false if the circuit should close.

type CommandPropertiesConstructor

type CommandPropertiesConstructor func(circuitName string) Config

CommandPropertiesConstructor is a generic function that can create command properties to configure a circuit by name It is safe to leave not configured properties their empty value.

Example

You can use DefaultCircuitProperties to set configuration dynamically for any circuit

myFactory := func(circuitName string) circuit.Config {
	timeoutsByName := map[string]time.Duration{
		"v1": time.Second,
		"v2": time.Second * 2,
	}
	customTimeout := timeoutsByName[circuitName]
	if customTimeout == 0 {
		// Just return empty if you don't want to set any config
		return circuit.Config{}
	}
	return circuit.Config{
		Execution: circuit.ExecutionConfig{
			Timeout: customTimeout,
		},
	}
}

// Manager manages circuits with unique names
h := circuit.Manager{
	DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{myFactory},
}
h.MustCreateCircuit("v1")
fmt.Println("The timeout of v1 is", h.GetCircuit("v1").Config().Execution.Timeout)
Output:

The timeout of v1 is 1s

type Config

type Config struct {
	General   GeneralConfig
	Execution ExecutionConfig
	Fallback  FallbackConfig
	Metrics   MetricsCollectors
}

Config controls how a circuit operates

Example (Custommetrics)

Implement interfaces CmdMetricCollector or FallbackMetricCollector to know what happens with commands or fallbacks.

Then pass those implementations to configure.
config := circuit.Config{
	Metrics: circuit.MetricsCollectors{
		Run: []circuit.RunMetrics{
			// Here is where I would insert my custom metric collector
		},
	},
}
circuit.NewCircuitFromConfig("custom-metrics", config)
Output:

func (*Config) Merge

func (c *Config) Merge(other Config) *Config

Merge these properties with another command's properties. Anything set to the zero value, will takes values from other.

type Configurable

type Configurable interface {
	// SetConfigThreadSafe can be called while the circuit is currently being used and will modify things that are
	// safe to change live.
	SetConfigThreadSafe(props Config)
	// SetConfigNotThreadSafe should only be called when the circuit is not in use: otherwise it will fail -race
	// detection
	SetConfigNotThreadSafe(props Config)
}

Configurable is anything that can receive configuration changes while live

type Error

type Error interface {
	error
	// ConcurrencyLimitReached returns true if this error is because the concurrency limit has been reached.
	ConcurrencyLimitReached() bool
	// CircuitOpen returns true if this error is because the circuit is open.
	CircuitOpen() bool
}

Error is the type of error returned by internal errors using the circuit library.

Example (Checking)

Shows how to check if an error is part of the circuit library.

x := errors.New("an error")
if _, ok := x.(circuit.Error); ok {
	fmt.Println("this error is a circuit library error, not the result of runFunc or fallbackFunc")
}
Output:

type ExecutionConfig

type ExecutionConfig struct {
	// ExecutionTimeout is https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.thread.timeoutInMilliseconds
	Timeout time.Duration
	// MaxConcurrentRequests is https://github.com/Netflix/Hystrix/wiki/Configuration#executionisolationsemaphoremaxconcurrentrequests
	MaxConcurrentRequests int64
	// Normally if the parent context is canceled before a timeout is reached, we don't consider the circuit
	// unhealthy.  Set this to true to consider those circuits unhealthy.
	IgnoreInterrupts bool `json:",omitempty"`
	// IsErrInterrupt should return true if the error from the original context should be considered an interrupt error.
	// The error passed in will be a non-nil error returned by calling `Err()` on the context passed into Run.
	// The default behavior is to consider all errors from the original context interrupt caused errors.
	// Default behaviour:
	// 		IsErrInterrupt: function(e err) bool { return true }
	IsErrInterrupt func(originalContextError error) bool `json:"-"`
}

ExecutionConfig is https://github.com/Netflix/Hystrix/wiki/Configuration#execution

type FallbackConfig

type FallbackConfig struct {
	// Enabled is opposite of https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerenabled
	// Note: Java Manager calls this "Enabled".  I call it "Disabled" so the zero struct can fill defaults
	Disabled bool `json:",omitempty"`
	// MaxConcurrentRequests is https://github.com/Netflix/Hystrix/wiki/Configuration#fallback.isolation.semaphore.maxConcurrentRequests
	MaxConcurrentRequests int64
}

FallbackConfig is https://github.com/Netflix/Hystrix/wiki/Configuration#fallback

type FallbackMetrics

type FallbackMetrics interface {

	// Success each time fallback is called and succeeds.
	Success(ctx context.Context, now time.Time, duration time.Duration)
	// ErrFailure each time fallback callback fails.
	ErrFailure(ctx context.Context, now time.Time, duration time.Duration)
	// ErrConcurrencyLimitReject each time fallback fails due to concurrency limit
	ErrConcurrencyLimitReject(ctx context.Context, now time.Time)
}

FallbackMetrics is guaranteed to execute one (and only one) of the following functions each time a fallback is executed. Methods with durations are when the fallback is actually executed. Methods without durations are when the fallback was never called, probably because of some circuit condition.

type FallbackMetricsCollection

type FallbackMetricsCollection []FallbackMetrics

FallbackMetricsCollection sends fallback metrics to all collectors

func (FallbackMetricsCollection) ErrConcurrencyLimitReject

func (r FallbackMetricsCollection) ErrConcurrencyLimitReject(ctx context.Context, now time.Time)

ErrConcurrencyLimitReject sends ErrConcurrencyLimitReject to all collectors

func (FallbackMetricsCollection) ErrFailure

func (r FallbackMetricsCollection) ErrFailure(ctx context.Context, now time.Time, duration time.Duration)

ErrFailure sends ErrFailure to all collectors

func (FallbackMetricsCollection) Success

func (r FallbackMetricsCollection) Success(ctx context.Context, now time.Time, duration time.Duration)

Success sends Success to all collectors

func (FallbackMetricsCollection) Var

Var exposes run collectors as expvar

type GeneralConfig

type GeneralConfig struct {
	// if disabled, Execute functions pass to just calling runFunc and do no tracking or fallbacks
	// Note: Java Manager calls this "Enabled".  I call it "Disabled" so the zero struct can fill defaults
	Disabled bool `json:",omitempty"`
	// ForceOpen is https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerforceopen
	ForceOpen bool `json:",omitempty"`
	// ForcedClosed is https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerforceclosed
	ForcedClosed bool `json:",omitempty"`
	// GoLostErrors can receive errors that would otherwise be lost by `Go` executions.  For example, if Go returns
	// early but some long time later an error or panic eventually happens.
	GoLostErrors func(err error, panics interface{}) `json:"-"`
	// ClosedToOpenFactory creates logic that determines if the circuit should go from Closed to Open state.
	// By default, it never opens
	ClosedToOpenFactory func() ClosedToOpen `json:"-"`
	// OpenToClosedFactory creates logic that determines if the circuit should go from Open to Closed state.
	// By default, it never closes
	OpenToClosedFactory func() OpenToClosed `json:"-"`
	// CustomConfig is anything you want.
	CustomConfig map[interface{}]interface{} `json:"-"`
	// TimeKeeper returns the current way to keep time.  You only want to modify this for testing.
	TimeKeeper TimeKeeper `json:"-"`
}

GeneralConfig controls the general logic of the circuit. Things specific to metrics, execution, or fallback are in their own configs

type Manager

type Manager struct {
	// DefaultCircuitProperties is a list of Config constructors called, in reverse order,
	// to append or modify configuration for your circuit.
	DefaultCircuitProperties []CommandPropertiesConstructor
	// contains filtered or unexported fields
}

Manager manages circuits with unique names

func (*Manager) AllCircuits

func (h *Manager) AllCircuits() []*Circuit

AllCircuits returns every hystrix circuit tracked

func (*Manager) CreateCircuit

func (h *Manager) CreateCircuit(name string, configs ...Config) (*Circuit, error)

CreateCircuit creates a new circuit, or returns error if a circuit with that name already exists

func (*Manager) GetCircuit

func (h *Manager) GetCircuit(name string) *Circuit

GetCircuit returns the circuit with a given name, or nil if the circuit does not exist. You should not call this in live code. Instead, store the circuit somewhere and use the circuit directly.

func (*Manager) MustCreateCircuit

func (h *Manager) MustCreateCircuit(name string, config ...Config) *Circuit

MustCreateCircuit calls CreateCircuit, but panics if the circuit name already exists

Example (Helloworld)

This example shows how to create a hello-world circuit from the circuit manager

// Manages all our circuits
h := circuit.Manager{}
// Create a circuit with a unique name
c := h.MustCreateCircuit("hello-world")
// Call the circuit
errResult := c.Execute(context.Background(), func(ctx context.Context) error {
	return nil
}, nil)
fmt.Println("Result of execution:", errResult)
Output:

Result of execution: <nil>

func (*Manager) Var

func (h *Manager) Var() expvar.Var

Var allows you to expose all your hystrix circuits on expvar

Example

If you wanted to publish hystrix information on Expvar, you can register your manager.

h := circuit.Manager{}
expvar.Publish("hystrix", h.Var())
Output:

type Metrics

type Metrics interface {
	// Closed is called when the circuit transitions from Open to Closed.
	Closed(ctx context.Context, now time.Time)
	// Opened is called when the circuit transitions from Closed to Opened.
	Opened(ctx context.Context, now time.Time)
}

Metrics reports internal circuit metric events

type MetricsCollection

type MetricsCollection []Metrics

MetricsCollection allows reporting multiple circuit metrics at once

func (MetricsCollection) Closed

func (r MetricsCollection) Closed(ctx context.Context, now time.Time)

Closed sends Closed to all collectors

func (MetricsCollection) Opened

func (r MetricsCollection) Opened(ctx context.Context, now time.Time)

Opened sends Opened to all collectors

type MetricsCollectors

type MetricsCollectors struct {
	Run      []RunMetrics      `json:"-"`
	Fallback []FallbackMetrics `json:"-"`
	Circuit  []Metrics         `json:"-"`
}

MetricsCollectors can receive metrics during a circuit. They should be fast, as they will block circuit operation during function calls.

type OpenToClosed

type OpenToClosed interface {
	RunMetrics
	Metrics
	// ShouldClose is called after a request is allowed to go through, and the circuit is open.  If the circuit should
	// now close, return true.  If the circuit should remain open, return false.
	ShouldClose(ctx context.Context, now time.Time) bool
	// Allow a single request while remaining in the closed state
	Allow(ctx context.Context, now time.Time) bool
}

OpenToClosed controls logic that tries to close an open circuit

type RunMetrics

type RunMetrics interface {
	// Success each time `Execute` does not return an error
	Success(ctx context.Context, now time.Time, duration time.Duration)
	// ErrFailure each time a runFunc (the circuit part) ran, but failed
	ErrFailure(ctx context.Context, now time.Time, duration time.Duration)
	// ErrTimeout increments the number of timeouts that occurred in the circuit breaker.
	ErrTimeout(ctx context.Context, now time.Time, duration time.Duration)
	// ErrBadRequest is counts of http://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/exception/HystrixBadRequestException.html
	// See https://github.com/Netflix/Hystrix/wiki/How-To-Use#error-propagation
	ErrBadRequest(ctx context.Context, now time.Time, duration time.Duration)
	// ErrInterrupt means the request ended, not because the runFunc failed, but probably because the original
	// context canceled.  Your circuit returned an error, but it's probably because someone else killed the context,
	// and not that your circuit is broken.  Java Manager doesn't have an equivalent for this, but it would be like if
	// an interrupt was called on the thread.
	//
	// A note on stat tracking: you may or may not consider this duration valid.  Yes, that's how long it executed,
	// but the circuit never finished correctly since it was asked to end early, so the value is smaller than the
	// circuit would have otherwise taken.
	ErrInterrupt(ctx context.Context, now time.Time, duration time.Duration)

	// ErrConcurrencyLimitReject each time a circuit is rejected due to concurrency limits
	ErrConcurrencyLimitReject(ctx context.Context, now time.Time)
	// ErrShortCircuit each time runFunc is not called because the circuit was open.
	ErrShortCircuit(ctx context.Context, now time.Time)
}

RunMetrics is guaranteed to execute one (and only one) of the following functions each time the circuit attempts to call a run function. Methods with durations are when run was actually executed. Methods without durations never called run, probably because of the circuit.

type RunMetricsCollection

type RunMetricsCollection []RunMetrics

RunMetricsCollection send metrics to multiple RunMetrics

func (RunMetricsCollection) ErrBadRequest

func (r RunMetricsCollection) ErrBadRequest(ctx context.Context, now time.Time, duration time.Duration)

ErrBadRequest sends ErrBadRequest to all collectors

func (RunMetricsCollection) ErrConcurrencyLimitReject

func (r RunMetricsCollection) ErrConcurrencyLimitReject(ctx context.Context, now time.Time)

ErrConcurrencyLimitReject sends ErrConcurrencyLimitReject to all collectors

func (RunMetricsCollection) ErrFailure

func (r RunMetricsCollection) ErrFailure(ctx context.Context, now time.Time, duration time.Duration)

ErrFailure sends ErrFailure to all collectors

func (RunMetricsCollection) ErrInterrupt

func (r RunMetricsCollection) ErrInterrupt(ctx context.Context, now time.Time, duration time.Duration)

ErrInterrupt sends ErrInterrupt to all collectors

func (RunMetricsCollection) ErrShortCircuit

func (r RunMetricsCollection) ErrShortCircuit(ctx context.Context, now time.Time)

ErrShortCircuit sends ErrShortCircuit to all collectors

func (RunMetricsCollection) ErrTimeout

func (r RunMetricsCollection) ErrTimeout(ctx context.Context, now time.Time, duration time.Duration)

ErrTimeout sends ErrTimeout to all collectors

func (RunMetricsCollection) Success

func (r RunMetricsCollection) Success(ctx context.Context, now time.Time, duration time.Duration)

Success sends Success to all collectors

func (RunMetricsCollection) Var

Var exposes run collectors as expvar

type SimpleBadRequest

type SimpleBadRequest struct {
	Err error
}

SimpleBadRequest is a simple wrapper for an error to mark it as a bad request

func (SimpleBadRequest) BadRequest

func (s SimpleBadRequest) BadRequest() bool

BadRequest always returns true

func (SimpleBadRequest) Cause

func (s SimpleBadRequest) Cause() error

Cause returns the wrapped error

func (SimpleBadRequest) Error

func (s SimpleBadRequest) Error() string

Cause returns the wrapped error

type TimeKeeper

type TimeKeeper struct {
	// Now should simulate time.Now
	Now func() time.Time
	// AfterFunc should simulate time.AfterFunc
	AfterFunc func(time.Duration, func()) *time.Timer
}

TimeKeeper allows overriding time to test the circuit

Directories

Path Synopsis
Package closers contains subpackages that control circuit open and close logic.
Package closers contains subpackages that control circuit open and close logic.
hystrix
Package hystrix is a Go implementation of Netflix's Hystrix logic for circuit breakers.
Package hystrix is a Go implementation of Netflix's Hystrix logic for circuit breakers.
simplelogic
Package simplelogic is a holding place for close and open circuit logic that is otherwise simple in use or complexity.
Package simplelogic is a holding place for close and open circuit logic that is otherwise simple in use or complexity.
Run this simple Go program to see what circuits look like.
Run this simple Go program to see what circuits look like.
Package faststats contains helpers to calculate circuit statistics quickly (usually atomically).
Package faststats contains helpers to calculate circuit statistics quickly (usually atomically).
internal
Package metriceventstream allows exposing your circuit's health as a metric stream that you can visualize with the hystrix dashboard.
Package metriceventstream allows exposing your circuit's health as a metric stream that you can visualize with the hystrix dashboard.
Package metrics contains implementations of MetricsCollectors to aid circuit health detection.
Package metrics contains implementations of MetricsCollectors to aid circuit health detection.
responsetimeslo
Package responsetimeslo contains a MetricsCollector that tracks a SLO metric for circuits.
Package responsetimeslo contains a MetricsCollector that tracks a SLO metric for circuits.
rolling
Package rolling contains a MetricsCollector that tracks in memory rolling stats about a circuit.
Package rolling contains a MetricsCollector that tracks in memory rolling stats about a circuit.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL