<commit_before>// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

import (
	"fmt"
	"net/http"
	"path"

	"github.com/gorilla/context"
)

// NewRouter returns a new router instance.
func NewRouter() *Router {
	return &Router{namedRoutes: make(map[string]*Route)}
}

// Router registers routes to be matched and dispatches a handler.
//
// It implements the http.Handler interface, so it can be registered to serve
// requests:
//
//     var router = mux.NewRouter()
//
//     func main() {
//         http.Handle("/", router)
//     }
//
// Or, for Google App Engine, register it in a init() function:
//
//     func init() {
//         http.Handle("/", router)
//     }
//
// This will send all incoming requests to the router.
type Router struct {
	// Configurable Handler to be used when no route matches.
	NotFoundHandler http.Handler
	// Parent route, if this is a subrouter.
	parent parentRoute
	// Routes to be matched, in order.
	routes []*Route
	// Routes by name for URL building.
	namedRoutes map[string]*Route
	// See Router.StrictSlash(). This defines the flag for new routes.
	strictSlash bool
}

// Match matches registered routes against the request.
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
	for _, route := range r.routes {
		if matched := route.Match(req, match); matched {
			return true
		}
	}
	return false
}

// ServeHTTP dispatches the handler registered in the matched route.
//
// When there is a match, the route variables can be retrieved calling
// mux.Vars(request).
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Clean path to canonical form and redirect.
	if p := cleanPath(req.URL.Path); p != req.URL.Path {
		w.Header().Set("Location", p)
		w.WriteHeader(http.StatusMovedPermanently)
		return
	}
	var match RouteMatch
	var handler http.Handler
	if matched := r.Match(req, &match); matched {
		handler = match.Handler
		setVars(req, match.Vars)
		setCurrentRoute(req, match.Route)
	}
	if handler == nil {
		if r.NotFoundHandler == nil {
			r.NotFoundHandler = http.NotFoundHandler()
		}
		handler = r.NotFoundHandler
	}
	defer context.Clear(req)
	handler.ServeHTTP(w, req)
}

// Get returns a route registered with the given name.
func (r *Router) Get(name string) *Route {
	return r.getNamedRoutes()[name]
}

// GetRoute returns a route registered with the given name. This method
// was renamed to Get() and remains here for backwards compatibility.
func (r *Router) GetRoute(name string) *Route {
	return r.getNamedRoutes()[name]
}

// StrictSlash defines the slash behavior for new routes.
//
// When true, if the route path is "/path/", accessing "/path" will redirect
// to the former and vice versa.
//
// Special case: when a route sets a path prefix, strict slash is
// automatically set to false for that route because the redirect behavior
// can't be determined for prefixes.
func (r *Router) StrictSlash(value bool) *Router {
	r.strictSlash = value
	return r
}

// ----------------------------------------------------------------------------
// parentRoute
// ----------------------------------------------------------------------------

// getNamedRoutes returns the map where named routes are registered.
func (r *Router) getNamedRoutes() map[string]*Route {
	if r.namedRoutes == nil {
		if r.parent != nil {
			r.namedRoutes = r.parent.getNamedRoutes()
		} else {
			r.namedRoutes = make(map[string]*Route)
		}
	}
	return r.namedRoutes
}

// getRegexpGroup returns regexp definitions from the parent route, if any.
func (r *Router) getRegexpGroup() *routeRegexpGroup {
	if r.parent != nil {
		return r.parent.getRegexpGroup()
	}
	return nil
}

// ----------------------------------------------------------------------------
// Route factories
// ----------------------------------------------------------------------------

// NewRoute registers an empty route.
func (r *Router) NewRoute() *Route {
	route := &Route{parent: r, strictSlash: r.strictSlash}
	r.routes = append(r.routes, route)
	return route
}

// Handle registers a new route with a matcher for the URL path.
// See Route.Path() and Route.Handler().
func (r *Router) Handle(path string, handler http.Handler) *Route {
	return r.NewRoute().Path(path).Handler(handler)
}

// HandleFunc registers a new route with a matcher for the URL path.
// See Route.Path() and Route.HandlerFunc().
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
	*http.Request)) *Route {
	return r.NewRoute().Path(path).HandlerFunc(f)
}

// Headers registers a new route with a matcher for request header values.
// See Route.Headers().
func (r *Router) Headers(pairs ...string) *Route {
	return r.NewRoute().Headers(pairs...)
}

// Host registers a new route with a matcher for the URL host.
// See Route.Host().
func (r *Router) Host(tpl string) *Route {
	return r.NewRoute().Host(tpl)
}

// MatcherFunc registers a new route with a custom matcher function.
// See Route.MatcherFunc().
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
	return r.NewRoute().MatcherFunc(f)
}

// Methods registers a new route with a matcher for HTTP methods.
// See Route.Methods().
func (r *Router) Methods(methods ...string) *Route {
	return r.NewRoute().Methods(methods...)
}

// Path registers a new route with a matcher for the URL path.
// See Route.Path().
func (r *Router) Path(tpl string) *Route {
	return r.NewRoute().Path(tpl)
}

// PathPrefix registers a new route with a matcher for the URL path prefix.
// See Route.PathPrefix().
func (r *Router) PathPrefix(tpl string) *Route {
	return r.NewRoute().PathPrefix(tpl)
}

// Queries registers a new route with a matcher for URL query values.
// See Route.Queries().
func (r *Router) Queries(pairs ...string) *Route {
	return r.NewRoute().Queries(pairs...)
}

// Schemes registers a new route with a matcher for URL schemes.
// See Route.Schemes().
func (r *Router) Schemes(schemes ...string) *Route {
	return r.NewRoute().Schemes(schemes...)
}

// ----------------------------------------------------------------------------
// Context
// ----------------------------------------------------------------------------

// RouteMatch stores information about a matched route.
type RouteMatch struct {
	Route   *Route
	Handler http.Handler
	Vars    map[string]string
}

type contextKey int

const (
	varsKey contextKey = iota
	routeKey
)

// Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string {
	if rv := context.Get(r, varsKey); rv != nil {
		return rv.(map[string]string)
	}
	return nil
}

// CurrentRoute returns the matched route for the current request, if any.
func CurrentRoute(r *http.Request) *Route {
	if rv := context.Get(r, routeKey); rv != nil {
		return rv.(*Route)
	}
	return nil
}

func setVars(r *http.Request, val interface{}) {
	context.Set(r, varsKey, val)
}

func setCurrentRoute(r *http.Request, val interface{}) {
	context.Set(r, routeKey, val)
}

// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------

// cleanPath returns the canonical path for p, eliminating . and .. elements.
// Borrowed from the net/http package.
func cleanPath(p string) string {
	if p == "" {
		return "/"
	}
	if p[0] != '/' {
		p = "/" + p
	}
	np := path.Clean(p)
	// path.Clean removes trailing slash except for root;
	// put the trailing slash back if necessary.
	if p[len(p)-1] == '/' && np != "/" {
		np += "/"
	}
	return np
}

// uniqueVars returns an error if two slices contain duplicated strings.
func uniqueVars(s1, s2 []string) error {
	for _, v1 := range s1 {
		for _, v2 := range s2 {
			if v1 == v2 {
				return fmt.Errorf("mux: duplicated route variable %q", v2)
			}
		}
	}
	return nil
}

// mapFromPairs converts variadic string parameters to a string map.
func mapFromPairs(pairs ...string) (map[string]string, error) {
	length := len(pairs)
	if length%2 != 0 {
		return nil, fmt.Errorf(
			"mux: number of parameters must be multiple of 2, got %v", pairs)
	}
	m := make(map[string]string, length/2)
	for i := 0; i < length; i += 2 {
		m[pairs[i]] = pairs[i+1]
	}
	return m, nil
}

// matchInArray returns true if the given string value is in the array.
func matchInArray(arr []string, value string) bool {
	for _, v := range arr {
		if v == value {
			return true
		}
	}
	return false
}

// matchMap returns true if the given key/value pairs exist in a given map.
func matchMap(toCheck map[string]string, toMatch map[string][]string,
	canonicalKey bool) bool {
	for k, v := range toCheck {
		// Check if key exists.
		if canonicalKey {
			k = http.CanonicalHeaderKey(k)
		}
		if values := toMatch[k]; values == nil {
			return false
		} else if v != "" {
			// If value was defined as an empty string we only check that the
			// key exists. Otherwise we also check for equality.
			valueExists := false
			for _, value := range values {
				if v == value {
					valueExists = true
					break
				}
			}
			if !valueExists {
				return false
			}
		}
	}
	return true
}
<commit_msg>Removed excess bool variable assignment in `if`s<commit_after>// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

import (
	"fmt"
	"net/http"
	"path"

	"github.com/gorilla/context"
)

// NewRouter returns a new router instance.
func NewRouter() *Router {
	return &Router{namedRoutes: make(map[string]*Route)}
}

// Router registers routes to be matched and dispatches a handler.
//
// It implements the http.Handler interface, so it can be registered to serve
// requests:
//
//     var router = mux.NewRouter()
//
//     func main() {
//         http.Handle("/", router)
//     }
//
// Or, for Google App Engine, register it in a init() function:
//
//     func init() {
//         http.Handle("/", router)
//     }
//
// This will send all incoming requests to the router.
type Router struct {
	// Configurable Handler to be used when no route matches.
	NotFoundHandler http.Handler
	// Parent route, if this is a subrouter.
	parent parentRoute
	// Routes to be matched, in order.
	routes []*Route
	// Routes by name for URL building.
	namedRoutes map[string]*Route
	// See Router.StrictSlash(). This defines the flag for new routes.
	strictSlash bool
}

// Match matches registered routes against the request.
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
	for _, route := range r.routes {
		if route.Match(req, match) {
			return true
		}
	}
	return false
}

// ServeHTTP dispatches the handler registered in the matched route.
//
// When there is a match, the route variables can be retrieved calling
// mux.Vars(request).
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Clean path to canonical form and redirect.
	if p := cleanPath(req.URL.Path); p != req.URL.Path {
		w.Header().Set("Location", p)
		w.WriteHeader(http.StatusMovedPermanently)
		return
	}
	var match RouteMatch
	var handler http.Handler
	if r.Match(req, &match) {
		handler = match.Handler
		setVars(req, match.Vars)
		setCurrentRoute(req, match.Route)
	}
	if handler == nil {
		if r.NotFoundHandler == nil {
			r.NotFoundHandler = http.NotFoundHandler()
		}
		handler = r.NotFoundHandler
	}
	defer context.Clear(req)
	handler.ServeHTTP(w, req)
}

// Get returns a route registered with the given name.
func (r *Router) Get(name string) *Route {
	return r.getNamedRoutes()[name]
}

// GetRoute returns a route registered with the given name. This method
// was renamed to Get() and remains here for backwards compatibility.
func (r *Router) GetRoute(name string) *Route {
	return r.getNamedRoutes()[name]
}

// StrictSlash defines the slash behavior for new routes.
//
// When true, if the route path is "/path/", accessing "/path" will redirect
// to the former and vice versa.
//
// Special case: when a route sets a path prefix, strict slash is
// automatically set to false for that route because the redirect behavior
// can't be determined for prefixes.
func (r *Router) StrictSlash(value bool) *Router {
	r.strictSlash = value
	return r
}

// ----------------------------------------------------------------------------
// parentRoute
// ----------------------------------------------------------------------------

// getNamedRoutes returns the map where named routes are registered.
func (r *Router) getNamedRoutes() map[string]*Route {
	if r.namedRoutes == nil {
		if r.parent != nil {
			r.namedRoutes = r.parent.getNamedRoutes()
		} else {
			r.namedRoutes = make(map[string]*Route)
		}
	}
	return r.namedRoutes
}

// getRegexpGroup returns regexp definitions from the parent route, if any.
func (r *Router) getRegexpGroup() *routeRegexpGroup {
	if r.parent != nil {
		return r.parent.getRegexpGroup()
	}
	return nil
}

// ----------------------------------------------------------------------------
// Route factories
// ----------------------------------------------------------------------------

// NewRoute registers an empty route.
func (r *Router) NewRoute() *Route {
	route := &Route{parent: r, strictSlash: r.strictSlash}
	r.routes = append(r.routes, route)
	return route
}

// Handle registers a new route with a matcher for the URL path.
// See Route.Path() and Route.Handler().
func (r *Router) Handle(path string, handler http.Handler) *Route {
	return r.NewRoute().Path(path).Handler(handler)
}

// HandleFunc registers a new route with a matcher for the URL path.
// See Route.Path() and Route.HandlerFunc().
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
	*http.Request)) *Route {
	return r.NewRoute().Path(path).HandlerFunc(f)
}

// Headers registers a new route with a matcher for request header values.
// See Route.Headers().
func (r *Router) Headers(pairs ...string) *Route {
	return r.NewRoute().Headers(pairs...)
}

// Host registers a new route with a matcher for the URL host.
// See Route.Host().
func (r *Router) Host(tpl string) *Route {
	return r.NewRoute().Host(tpl)
}

// MatcherFunc registers a new route with a custom matcher function.
// See Route.MatcherFunc().
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
	return r.NewRoute().MatcherFunc(f)
}

// Methods registers a new route with a matcher for HTTP methods.
// See Route.Methods().
func (r *Router) Methods(methods ...string) *Route {
	return r.NewRoute().Methods(methods...)
}

// Path registers a new route with a matcher for the URL path.
// See Route.Path().
func (r *Router) Path(tpl string) *Route {
	return r.NewRoute().Path(tpl)
}

// PathPrefix registers a new route with a matcher for the URL path prefix.
// See Route.PathPrefix().
func (r *Router) PathPrefix(tpl string) *Route {
	return r.NewRoute().PathPrefix(tpl)
}

// Queries registers a new route with a matcher for URL query values.
// See Route.Queries().
func (r *Router) Queries(pairs ...string) *Route {
	return r.NewRoute().Queries(pairs...)
}

// Schemes registers a new route with a matcher for URL schemes.
// See Route.Schemes().
func (r *Router) Schemes(schemes ...string) *Route {
	return r.NewRoute().Schemes(schemes...)
}

// ----------------------------------------------------------------------------
// Context
// ----------------------------------------------------------------------------

// RouteMatch stores information about a matched route.
type RouteMatch struct {
	Route   *Route
	Handler http.Handler
	Vars    map[string]string
}

type contextKey int

const (
	varsKey contextKey = iota
	routeKey
)

// Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string {
	if rv := context.Get(r, varsKey); rv != nil {
		return rv.(map[string]string)
	}
	return nil
}

// CurrentRoute returns the matched route for the current request, if any.
func CurrentRoute(r *http.Request) *Route {
	if rv := context.Get(r, routeKey); rv != nil {
		return rv.(*Route)
	}
	return nil
}

func setVars(r *http.Request, val interface{}) {
	context.Set(r, varsKey, val)
}

func setCurrentRoute(r *http.Request, val interface{}) {
	context.Set(r, routeKey, val)
}

// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------

// cleanPath returns the canonical path for p, eliminating . and .. elements.
// Borrowed from the net/http package.
func cleanPath(p string) string {
	if p == "" {
		return "/"
	}
	if p[0] != '/' {
		p = "/" + p
	}
	np := path.Clean(p)
	// path.Clean removes trailing slash except for root;
	// put the trailing slash back if necessary.
	if p[len(p)-1] == '/' && np != "/" {
		np += "/"
	}
	return np
}

// uniqueVars returns an error if two slices contain duplicated strings.
func uniqueVars(s1, s2 []string) error {
	for _, v1 := range s1 {
		for _, v2 := range s2 {
			if v1 == v2 {
				return fmt.Errorf("mux: duplicated route variable %q", v2)
			}
		}
	}
	return nil
}

// mapFromPairs converts variadic string parameters to a string map.
func mapFromPairs(pairs ...string) (map[string]string, error) {
	length := len(pairs)
	if length%2 != 0 {
		return nil, fmt.Errorf(
			"mux: number of parameters must be multiple of 2, got %v", pairs)
	}
	m := make(map[string]string, length/2)
	for i := 0; i < length; i += 2 {
		m[pairs[i]] = pairs[i+1]
	}
	return m, nil
}

// matchInArray returns true if the given string value is in the array.
func matchInArray(arr []string, value string) bool {
	for _, v := range arr {
		if v == value {
			return true
		}
	}
	return false
}

// matchMap returns true if the given key/value pairs exist in a given map.
func matchMap(toCheck map[string]string, toMatch map[string][]string,
	canonicalKey bool) bool {
	for k, v := range toCheck {
		// Check if key exists.
		if canonicalKey {
			k = http.CanonicalHeaderKey(k)
		}
		if values := toMatch[k]; values == nil {
			return false
		} else if v != "" {
			// If value was defined as an empty string we only check that the
			// key exists. Otherwise we also check for equality.
			valueExists := false
			for _, value := range values {
				if v == value {
					valueExists = true
					break
				}
			}
			if !valueExists {
				return false
			}
		}
	}
	return true
}
<|endoftext|>


<commit_before>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package oracle

import (
	"fmt"
	"go/token"
	"sort"

	"code.google.com/p/go.tools/go/callgraph"
	"code.google.com/p/go.tools/go/ssa"
	"code.google.com/p/go.tools/go/types"
	"code.google.com/p/go.tools/oracle/serial"
)

// doCallgraph displays the entire callgraph of the current program,
// or if a query -pos was provided, the query package.
func doCallgraph(o *Oracle, qpos *QueryPos) (queryResult, error) {
	buildSSA(o)

	// Run the pointer analysis and build the callgraph.
	o.ptaConfig.BuildCallGraph = true
	cg := ptrAnalysis(o).CallGraph
	cg.DeleteSyntheticNodes()

	var qpkg *types.Package
	var roots []*callgraph.Node
	if qpos == nil {
		// No -pos provided: show complete callgraph.
		roots = append(roots, cg.Root)

	} else {
		// A query -pos was provided: restrict result to
		// functions belonging to the query package.
		qpkg = qpos.info.Pkg
		isQueryPkg := func(fn *ssa.Function) bool {
			return fn.Pkg != nil && fn.Pkg.Object == qpkg
		}

		// First compute the nodes to keep and remove.
		var nodes, remove []*callgraph.Node
		for fn, cgn := range cg.Nodes {
			if isQueryPkg(fn) {
				nodes = append(nodes, cgn)
			} else {
				remove = append(remove, cgn)
			}
		}

		// Compact the Node.ID sequence of the remaining
		// nodes, preserving the original order.
		sort.Sort(nodesByID(nodes))
		for i, cgn := range nodes {
			cgn.ID = i
		}

		// Compute the set of roots:
		// in-package nodes with out-of-package callers.
		// For determinism, roots are ordered by original Node.ID.
		for _, cgn := range nodes {
			for _, e := range cgn.In {
				if !isQueryPkg(e.Caller.Func) {
					roots = append(roots, cgn)
					break
				}
			}
		}

		// Finally, discard all out-of-package nodes.
		for _, cgn := range remove {
			cg.DeleteNode(cgn)
		}
	}

	return &callgraphResult{qpkg, cg.Nodes, roots}, nil
}

type callgraphResult struct {
	qpkg  *types.Package
	nodes map[*ssa.Function]*callgraph.Node
	roots []*callgraph.Node
}

func (r *callgraphResult) display(printf printfFunc) {
	descr := "the entire program"
	if r.qpkg != nil {
		descr = fmt.Sprintf("package %s", r.qpkg.Path())
	}

	printf(nil, `
Below is a call graph of %s.
The numbered nodes form a spanning tree.
Non-numbered nodes indicate back- or cross-edges to the node whose
 number follows in parentheses.
`, descr)

	printed := make(map[*callgraph.Node]int)
	var print func(caller *callgraph.Node, indent int)
	print = func(caller *callgraph.Node, indent int) {
		if num, ok := printed[caller]; !ok {
			num = len(printed)
			printed[caller] = num

			// Sort the children into name order for deterministic* output.
			// (*mostly: anon funcs' names are not globally unique.)
			var funcs funcsByName
			for callee := range callgraph.CalleesOf(caller) {
				funcs = append(funcs, callee.Func)
			}
			sort.Sort(funcs)

			printf(caller.Func, "%d\t%*s%s", num, 4*indent, "", caller.Func.RelString(r.qpkg))
			for _, callee := range funcs {
				print(r.nodes[callee], indent+1)
			}
		} else {
			printf(caller.Func, "\t%*s%s (%d)", 4*indent, "", caller.Func.RelString(r.qpkg), num)
		}
	}
	for _, root := range r.roots {
		print(root, 0)
	}
}

type nodesByID []*callgraph.Node

func (s nodesByID) Len() int           { return len(s) }
func (s nodesByID) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s nodesByID) Less(i, j int) bool { return s[i].ID < s[j].ID }

type funcsByName []*ssa.Function

func (s funcsByName) Len() int           { return len(s) }
func (s funcsByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s funcsByName) Less(i, j int) bool { return s[i].String() < s[j].String() }

func (r *callgraphResult) toSerial(res *serial.Result, fset *token.FileSet) {
	cg := make([]serial.CallGraph, len(r.nodes))
	for _, n := range r.nodes {
		j := &cg[n.ID]
		fn := n.Func
		j.Name = fn.String()
		j.Pos = fset.Position(fn.Pos()).String()
		for callee := range callgraph.CalleesOf(n) {
			j.Children = append(j.Children, callee.ID)
		}
		sort.Ints(j.Children)
	}
	res.Callgraph = cg
}
<commit_msg>go.tools/oracle: callgraph: fix crash caused by sparse node numbering.<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package oracle

import (
	"fmt"
	"go/token"
	"sort"

	"code.google.com/p/go.tools/go/callgraph"
	"code.google.com/p/go.tools/go/ssa"
	"code.google.com/p/go.tools/go/types"
	"code.google.com/p/go.tools/oracle/serial"
)

// doCallgraph displays the entire callgraph of the current program,
// or if a query -pos was provided, the query package.
func doCallgraph(o *Oracle, qpos *QueryPos) (queryResult, error) {
	buildSSA(o)

	// Run the pointer analysis and build the callgraph.
	o.ptaConfig.BuildCallGraph = true
	cg := ptrAnalysis(o).CallGraph
	cg.DeleteSyntheticNodes()

	var qpkg *types.Package
	var isQueryPkg func(fn *ssa.Function) bool
	var keep, remove, roots []*callgraph.Node
	if qpos == nil {
		// No -pos provided: show complete callgraph.
		roots = append(roots, cg.Root)
		isQueryPkg = func(fn *ssa.Function) bool { return true }
	} else {
		// A query -pos was provided: restrict result to
		// functions belonging to the query package.
		qpkg = qpos.info.Pkg
		isQueryPkg = func(fn *ssa.Function) bool {
			return fn.Pkg != nil && fn.Pkg.Object == qpkg
		}
	}

	// First compute the nodes to keep and remove.
	for fn, cgn := range cg.Nodes {
		if isQueryPkg(fn) {
			keep = append(keep, cgn)
		} else {
			remove = append(remove, cgn)
		}
	}

	// Compact the Node.ID sequence of the kept nodes,
	// preserving the original order.
	sort.Sort(nodesByID(keep))
	for i, cgn := range keep {
		cgn.ID = i
	}

	// Compute the set of roots:
	// in-package nodes with out-of-package callers.
	// For determinism, roots are ordered by original Node.ID.
	for _, cgn := range keep {
		for _, e := range cgn.In {
			if !isQueryPkg(e.Caller.Func) {
				roots = append(roots, cgn)
				break
			}
		}
	}

	// Finally, discard all out-of-package nodes.
	for _, cgn := range remove {
		cg.DeleteNode(cgn)
	}

	return &callgraphResult{qpkg, cg.Nodes, roots}, nil
}

type callgraphResult struct {
	qpkg  *types.Package
	nodes map[*ssa.Function]*callgraph.Node
	roots []*callgraph.Node
}

func (r *callgraphResult) display(printf printfFunc) {
	descr := "the entire program"
	if r.qpkg != nil {
		descr = fmt.Sprintf("package %s", r.qpkg.Path())
	}

	printf(nil, `
Below is a call graph of %s.
The numbered nodes form a spanning tree.
Non-numbered nodes indicate back- or cross-edges to the node whose
 number follows in parentheses.
`, descr)

	printed := make(map[*callgraph.Node]int)
	var print func(caller *callgraph.Node, indent int)
	print = func(caller *callgraph.Node, indent int) {
		if num, ok := printed[caller]; !ok {
			num = len(printed)
			printed[caller] = num

			// Sort the children into name order for deterministic* output.
			// (*mostly: anon funcs' names are not globally unique.)
			var funcs funcsByName
			for callee := range callgraph.CalleesOf(caller) {
				funcs = append(funcs, callee.Func)
			}
			sort.Sort(funcs)

			printf(caller.Func, "%d\t%*s%s", num, 4*indent, "", caller.Func.RelString(r.qpkg))
			for _, callee := range funcs {
				print(r.nodes[callee], indent+1)
			}
		} else {
			printf(caller.Func, "\t%*s%s (%d)", 4*indent, "", caller.Func.RelString(r.qpkg), num)
		}
	}
	for _, root := range r.roots {
		print(root, 0)
	}
}

type nodesByID []*callgraph.Node

func (s nodesByID) Len() int           { return len(s) }
func (s nodesByID) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s nodesByID) Less(i, j int) bool { return s[i].ID < s[j].ID }

type funcsByName []*ssa.Function

func (s funcsByName) Len() int           { return len(s) }
func (s funcsByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s funcsByName) Less(i, j int) bool { return s[i].String() < s[j].String() }

func (r *callgraphResult) toSerial(res *serial.Result, fset *token.FileSet) {
	cg := make([]serial.CallGraph, len(r.nodes))
	for _, n := range r.nodes {
		j := &cg[n.ID]
		fn := n.Func
		j.Name = fn.String()
		j.Pos = fset.Position(fn.Pos()).String()
		for callee := range callgraph.CalleesOf(n) {
			j.Children = append(j.Children, callee.ID)
		}
		sort.Ints(j.Children)
	}
	res.Callgraph = cg
}
<|endoftext|>


<commit_before>package api_test

import (
	"cf"
	. "cf/api"
	"cf/configuration"
	"cf/net"
	"github.com/stretchr/testify/assert"
	"net/http"
	"net/http/httptest"
	"testhelpers"
	"testing"
)

var multipleDomainsResponse = testhelpers.TestResponse{Status: http.StatusOK, Body: `
{
  "total_results": 2,
  "total_pages": 1,
  "prev_url": null,
  "next_url": null,
  "resources": [
    {
      "metadata": {
        "guid": "domain1-guid"
      },
      "entity": {
        "name": "domain1.cf-app.com"
      }
    },
    {
      "metadata": {
        "guid": "domain2-guid"
      },
      "entity": {
        "name": "domain2.cf-app.com"
      }
    }
  ]
}`}

var multipleDomainsEndpoint = testhelpers.CreateEndpoint(
	"GET",
	"/v2/spaces/my-space-guid/domains",
	nil,
	multipleDomainsResponse,
)

func TestFindAllInCurrentSpace(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(multipleDomainsEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	domains, apiStatus := repo.FindAllInCurrentSpace()
	assert.False(t, apiStatus.NotSuccessful())
	assert.Equal(t, 2, len(domains))

	first := domains[0]
	assert.Equal(t, first.Name, "domain1.cf-app.com")
	assert.Equal(t, first.Guid, "domain1-guid")
	second := domains[1]
	assert.Equal(t, second.Name, "domain2.cf-app.com")
	assert.Equal(t, second.Guid, "domain2-guid")
}

var orgDomainsResponse = testhelpers.TestResponse{Status: http.StatusOK, Body: `
{
  "resources": [
    {
      "metadata": {
        "guid": "shared-domain-guid"
      },
      "entity": {
        "name": "shared-example.com",
        "owning_organization_guid": null,
        "wildcard": true,
        "spaces": [
          {
            "metadata": {
              "guid": "my-space-guid"
            },
            "entity": {
              "name": "my-space"
            }
          }
        ]
      }
    },
    {
      "metadata": {
        "guid": "my-domain-guid"
      },
      "entity": {
        "name": "example.com",
        "owning_organization_guid": "my-org-guid",
        "wildcard": true,
        "spaces": [
          {
            "metadata": {
              "guid": "my-space-guid"
            },
            "entity": {
              "name": "my-space"
            }
          }
        ]
      }
    }
  ]
}
`}

var orgDomainsEndpoint = testhelpers.CreateEndpoint(
	"GET",
	"/v2/organizations/my-org-guid/domains?inline-relations-depth=1",
	nil,
	orgDomainsResponse,
)

func TestFindAllByOrg(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(orgDomainsEndpoint))
	defer ts.Close()

	org := cf.Organization{Guid: "my-org-guid"}
	config := &configuration.Configuration{
		AccessToken:  "BEARER my_access_token",
		Target:       ts.URL,
		Organization: org,
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	domains, apiStatus := repo.FindAllByOrg(org)

	assert.False(t, apiStatus.NotSuccessful())
	assert.Equal(t, 2, len(domains))

	domain := domains[0]
	assert.True(t, domain.Shared)

	domain = domains[1]
	assert.Equal(t, domain.Name, "example.com")
	assert.Equal(t, domain.Guid, "my-domain-guid")
	assert.False(t, domain.Shared)
	assert.Equal(t, domain.Spaces[0].Name, "my-space")
}

func TestFindByNameInCurrentSpaceReturnsTheDomainMatchingTheName(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(multipleDomainsEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	domain, apiStatus := repo.FindByNameInCurrentSpace("domain2.cf-app.com")
	assert.False(t, apiStatus.NotSuccessful())

	assert.Equal(t, domain.Name, "domain2.cf-app.com")
	assert.Equal(t, domain.Guid, "domain2-guid")
}

var noDomainsResponse = testhelpers.TestResponse{Status: http.StatusOK, Body: `
{
  "resources": []
}
`}

var noDomainsEndpoint = testhelpers.CreateEndpoint(
	"GET",
	"/v2/spaces/my-space-guid/domains",
	nil,
	noDomainsResponse,
)

func TestFindByNameInCurrentSpaceReturnsTheFirstDomainIfNameEmpty(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(multipleDomainsEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	_, apiStatus := repo.FindByNameInCurrentSpace("")
	assert.False(t, apiStatus.NotSuccessful())
}

func TestFindByNameInCurrentSpaceReturnsNotFoundIfNameEmptyAndNoDomains(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(noDomainsEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	_, apiStatus := repo.FindByNameInCurrentSpace("")
	assert.False(t, apiStatus.IsError())
	assert.True(t, apiStatus.IsNotFound())
}

func TestFindByNameInCurrentSpaceWhenTheDomainIsNotFound(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(multipleDomainsEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	_, apiStatus := repo.FindByNameInCurrentSpace("domain3.cf-app.com")
	assert.False(t, apiStatus.IsError())
	assert.True(t, apiStatus.IsNotFound())
}

var createDomainResponse = `
{
    "metadata": {
        "guid": "abc-123"
    },
    "entity": {
        "name": "example.com"
    }
}`

var createDomainEndpoint = testhelpers.CreateEndpoint(
	"POST",
	"/v2/domains",
	testhelpers.RequestBodyMatcher(`{"name":"example.com","wildcard":true,"owning_organization_guid":"domain1-guid"}`),
	testhelpers.TestResponse{Status: http.StatusCreated, Body: createDomainResponse},
)

func TestReserveDomain(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(createDomainEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}

	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	domainToCreate := cf.Domain{Name: "example.com"}
	owningOrg := cf.Organization{Guid: "domain1-guid"}
	createdDomain, apiStatus := repo.Create(domainToCreate, owningOrg)
	assert.False(t, apiStatus.NotSuccessful())
	assert.Equal(t, createdDomain.Guid, "abc-123")
}

func TestFindByNameInOrgWhenDomainExists(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(orgDomainsEndpoint))
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway()

	repo := NewCloudControllerDomainRepository(&config, gateway)

	domainName := "example.com"
	org := cf.Organization{Name: "my-org", Guid: "my-org-guid"}
	domain, apiStatus := repo.FindByNameInOrg(domainName, org)

	assert.Equal(t, domain.Name, domainName)
	assert.Equal(t, domain.Guid, "my-domain-guid")
	assert.False(t, apiStatus.NotSuccessful())
}

func mapDomainEndpoint(statusCode int) (hf http.HandlerFunc, status *testhelpers.RequestStatus) {
	status = &testhelpers.RequestStatus{}
	hf = testhelpers.CreateEndpoint(
		"PUT",
		"/v2/spaces/my-space-guid/domains/my-domain-guid",
		testhelpers.EndpointCalledMatcher(status),
		testhelpers.TestResponse{Status: statusCode},
	)
	return
}

func TestMapDomainSuccess(t *testing.T) {
	hf, responseStatus := mapDomainEndpoint(http.StatusOK)
	ts := httptest.NewTLSServer(hf)
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway()

	repo := NewCloudControllerDomainRepository(&config, gateway)

	space := cf.Space{Name: "my-space", Guid: "my-space-guid"}
	domain := cf.Domain{Name: "example.com", Guid: "my-domain-guid"}

	apiStatus := repo.MapDomain(domain, space)

	assert.True(t, responseStatus.Called)
	assert.False(t, apiStatus.NotSuccessful())
}

func TestMapDomainWhenServerError(t *testing.T) {
	hf, responseStatus := mapDomainEndpoint(http.StatusBadRequest)
	ts := httptest.NewTLSServer(hf)
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway()

	repo := NewCloudControllerDomainRepository(&config, gateway)

	space := cf.Space{Name: "my-space", Guid: "my-space-guid"}
	domain := cf.Domain{Name: "example.com", Guid: "my-domain-guid"}

	apiStatus := repo.MapDomain(domain, space)

	assert.True(t, responseStatus.Called)
	assert.True(t, apiStatus.NotSuccessful())
}

func unmapDomainEndpoint(statusCode int) (hf http.HandlerFunc, status *testhelpers.RequestStatus) {
	status = &testhelpers.RequestStatus{}
	hf = testhelpers.CreateEndpoint(
		"DELETE",
		"/v2/spaces/my-space-guid/domains/my-domain-guid",
		testhelpers.EndpointCalledMatcher(status),
		testhelpers.TestResponse{Status: statusCode},
	)
	return
}

func TestUnmapDomainSuccess(t *testing.T) {
	hf, responseStatus := unmapDomainEndpoint(http.StatusOK)
	ts := httptest.NewTLSServer(hf)
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway()

	repo := NewCloudControllerDomainRepository(&config, gateway)

	space := cf.Space{Name: "my-space", Guid: "my-space-guid"}
	domain := cf.Domain{Name: "example.com", Guid: "my-domain-guid"}

	apiStatus := repo.UnmapDomain(domain, space)

	assert.True(t, responseStatus.Called)
	assert.False(t, apiStatus.NotSuccessful())
}

func deleteDomainEndpoint(statusCode int) (hf http.HandlerFunc, status *testhelpers.RequestStatus) {
	status = &testhelpers.RequestStatus{}
	hf = testhelpers.CreateEndpoint(
		"DELETE",
		"/v2/domains/my-domain-guid?recursive=true",
		testhelpers.EndpointCalledMatcher(status),
		testhelpers.TestResponse{Status: statusCode},
	)
	return
}

func TestDeleteDomainSuccess(t *testing.T) {
	hf, responseStatus := deleteDomainEndpoint(http.StatusOK)
	ts := httptest.NewTLSServer(hf)
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway(&testhelpers.FakeAuthenticationRepository{})

	repo := NewCloudControllerDomainRepository(&config, gateway)

	domain := cf.Domain{Name: "example.com", Guid: "my-domain-guid"}

	apiStatus := repo.DeleteDomain(domain)

	assert.True(t, responseStatus.Called)
	assert.False(t, apiStatus.NotSuccessful())
}

func TestDeleteDomainFailure(t *testing.T) {
	hf, responseStatus := deleteDomainEndpoint(http.StatusBadRequest)
	ts := httptest.NewTLSServer(hf)
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway(&testhelpers.FakeAuthenticationRepository{})

	repo := NewCloudControllerDomainRepository(&config, gateway)

	domain := cf.Domain{Name: "example.com", Guid: "my-domain-guid"}

	apiStatus := repo.DeleteDomain(domain)

	assert.True(t, responseStatus.Called)
	assert.True(t, apiStatus.NotSuccessful())
}
<commit_msg>fixing domain_test to use new NewCloudControllerGateway syntax<commit_after>package api_test

import (
	"cf"
	. "cf/api"
	"cf/configuration"
	"cf/net"
	"github.com/stretchr/testify/assert"
	"net/http"
	"net/http/httptest"
	"testhelpers"
	"testing"
)

var multipleDomainsResponse = testhelpers.TestResponse{Status: http.StatusOK, Body: `
{
  "total_results": 2,
  "total_pages": 1,
  "prev_url": null,
  "next_url": null,
  "resources": [
    {
      "metadata": {
        "guid": "domain1-guid"
      },
      "entity": {
        "name": "domain1.cf-app.com"
      }
    },
    {
      "metadata": {
        "guid": "domain2-guid"
      },
      "entity": {
        "name": "domain2.cf-app.com"
      }
    }
  ]
}`}

var multipleDomainsEndpoint = testhelpers.CreateEndpoint(
	"GET",
	"/v2/spaces/my-space-guid/domains",
	nil,
	multipleDomainsResponse,
)

func TestFindAllInCurrentSpace(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(multipleDomainsEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	domains, apiStatus := repo.FindAllInCurrentSpace()
	assert.False(t, apiStatus.NotSuccessful())
	assert.Equal(t, 2, len(domains))

	first := domains[0]
	assert.Equal(t, first.Name, "domain1.cf-app.com")
	assert.Equal(t, first.Guid, "domain1-guid")
	second := domains[1]
	assert.Equal(t, second.Name, "domain2.cf-app.com")
	assert.Equal(t, second.Guid, "domain2-guid")
}

var orgDomainsResponse = testhelpers.TestResponse{Status: http.StatusOK, Body: `
{
  "resources": [
    {
      "metadata": {
        "guid": "shared-domain-guid"
      },
      "entity": {
        "name": "shared-example.com",
        "owning_organization_guid": null,
        "wildcard": true,
        "spaces": [
          {
            "metadata": {
              "guid": "my-space-guid"
            },
            "entity": {
              "name": "my-space"
            }
          }
        ]
      }
    },
    {
      "metadata": {
        "guid": "my-domain-guid"
      },
      "entity": {
        "name": "example.com",
        "owning_organization_guid": "my-org-guid",
        "wildcard": true,
        "spaces": [
          {
            "metadata": {
              "guid": "my-space-guid"
            },
            "entity": {
              "name": "my-space"
            }
          }
        ]
      }
    }
  ]
}
`}

var orgDomainsEndpoint = testhelpers.CreateEndpoint(
	"GET",
	"/v2/organizations/my-org-guid/domains?inline-relations-depth=1",
	nil,
	orgDomainsResponse,
)

func TestFindAllByOrg(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(orgDomainsEndpoint))
	defer ts.Close()

	org := cf.Organization{Guid: "my-org-guid"}
	config := &configuration.Configuration{
		AccessToken:  "BEARER my_access_token",
		Target:       ts.URL,
		Organization: org,
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	domains, apiStatus := repo.FindAllByOrg(org)

	assert.False(t, apiStatus.NotSuccessful())
	assert.Equal(t, 2, len(domains))

	domain := domains[0]
	assert.True(t, domain.Shared)

	domain = domains[1]
	assert.Equal(t, domain.Name, "example.com")
	assert.Equal(t, domain.Guid, "my-domain-guid")
	assert.False(t, domain.Shared)
	assert.Equal(t, domain.Spaces[0].Name, "my-space")
}

func TestFindByNameInCurrentSpaceReturnsTheDomainMatchingTheName(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(multipleDomainsEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	domain, apiStatus := repo.FindByNameInCurrentSpace("domain2.cf-app.com")
	assert.False(t, apiStatus.NotSuccessful())

	assert.Equal(t, domain.Name, "domain2.cf-app.com")
	assert.Equal(t, domain.Guid, "domain2-guid")
}

var noDomainsResponse = testhelpers.TestResponse{Status: http.StatusOK, Body: `
{
  "resources": []
}
`}

var noDomainsEndpoint = testhelpers.CreateEndpoint(
	"GET",
	"/v2/spaces/my-space-guid/domains",
	nil,
	noDomainsResponse,
)

func TestFindByNameInCurrentSpaceReturnsTheFirstDomainIfNameEmpty(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(multipleDomainsEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	_, apiStatus := repo.FindByNameInCurrentSpace("")
	assert.False(t, apiStatus.NotSuccessful())
}

func TestFindByNameInCurrentSpaceReturnsNotFoundIfNameEmptyAndNoDomains(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(noDomainsEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	_, apiStatus := repo.FindByNameInCurrentSpace("")
	assert.False(t, apiStatus.IsError())
	assert.True(t, apiStatus.IsNotFound())
}

func TestFindByNameInCurrentSpaceWhenTheDomainIsNotFound(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(multipleDomainsEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}
	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	_, apiStatus := repo.FindByNameInCurrentSpace("domain3.cf-app.com")
	assert.False(t, apiStatus.IsError())
	assert.True(t, apiStatus.IsNotFound())
}

var createDomainResponse = `
{
    "metadata": {
        "guid": "abc-123"
    },
    "entity": {
        "name": "example.com"
    }
}`

var createDomainEndpoint = testhelpers.CreateEndpoint(
	"POST",
	"/v2/domains",
	testhelpers.RequestBodyMatcher(`{"name":"example.com","wildcard":true,"owning_organization_guid":"domain1-guid"}`),
	testhelpers.TestResponse{Status: http.StatusCreated, Body: createDomainResponse},
)

func TestReserveDomain(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(createDomainEndpoint))
	defer ts.Close()

	config := &configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
		Space:       cf.Space{Guid: "my-space-guid"},
	}

	gateway := net.NewCloudControllerGateway()
	repo := NewCloudControllerDomainRepository(config, gateway)

	domainToCreate := cf.Domain{Name: "example.com"}
	owningOrg := cf.Organization{Guid: "domain1-guid"}
	createdDomain, apiStatus := repo.Create(domainToCreate, owningOrg)
	assert.False(t, apiStatus.NotSuccessful())
	assert.Equal(t, createdDomain.Guid, "abc-123")
}

func TestFindByNameInOrgWhenDomainExists(t *testing.T) {
	ts := httptest.NewTLSServer(http.HandlerFunc(orgDomainsEndpoint))
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway()

	repo := NewCloudControllerDomainRepository(&config, gateway)

	domainName := "example.com"
	org := cf.Organization{Name: "my-org", Guid: "my-org-guid"}
	domain, apiStatus := repo.FindByNameInOrg(domainName, org)

	assert.Equal(t, domain.Name, domainName)
	assert.Equal(t, domain.Guid, "my-domain-guid")
	assert.False(t, apiStatus.NotSuccessful())
}

func mapDomainEndpoint(statusCode int) (hf http.HandlerFunc, status *testhelpers.RequestStatus) {
	status = &testhelpers.RequestStatus{}
	hf = testhelpers.CreateEndpoint(
		"PUT",
		"/v2/spaces/my-space-guid/domains/my-domain-guid",
		testhelpers.EndpointCalledMatcher(status),
		testhelpers.TestResponse{Status: statusCode},
	)
	return
}

func TestMapDomainSuccess(t *testing.T) {
	hf, responseStatus := mapDomainEndpoint(http.StatusOK)
	ts := httptest.NewTLSServer(hf)
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway()

	repo := NewCloudControllerDomainRepository(&config, gateway)

	space := cf.Space{Name: "my-space", Guid: "my-space-guid"}
	domain := cf.Domain{Name: "example.com", Guid: "my-domain-guid"}

	apiStatus := repo.MapDomain(domain, space)

	assert.True(t, responseStatus.Called)
	assert.False(t, apiStatus.NotSuccessful())
}

func TestMapDomainWhenServerError(t *testing.T) {
	hf, responseStatus := mapDomainEndpoint(http.StatusBadRequest)
	ts := httptest.NewTLSServer(hf)
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway()

	repo := NewCloudControllerDomainRepository(&config, gateway)

	space := cf.Space{Name: "my-space", Guid: "my-space-guid"}
	domain := cf.Domain{Name: "example.com", Guid: "my-domain-guid"}

	apiStatus := repo.MapDomain(domain, space)

	assert.True(t, responseStatus.Called)
	assert.True(t, apiStatus.NotSuccessful())
}

func unmapDomainEndpoint(statusCode int) (hf http.HandlerFunc, status *testhelpers.RequestStatus) {
	status = &testhelpers.RequestStatus{}
	hf = testhelpers.CreateEndpoint(
		"DELETE",
		"/v2/spaces/my-space-guid/domains/my-domain-guid",
		testhelpers.EndpointCalledMatcher(status),
		testhelpers.TestResponse{Status: statusCode},
	)
	return
}

func TestUnmapDomainSuccess(t *testing.T) {
	hf, responseStatus := unmapDomainEndpoint(http.StatusOK)
	ts := httptest.NewTLSServer(hf)
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway()

	repo := NewCloudControllerDomainRepository(&config, gateway)

	space := cf.Space{Name: "my-space", Guid: "my-space-guid"}
	domain := cf.Domain{Name: "example.com", Guid: "my-domain-guid"}

	apiStatus := repo.UnmapDomain(domain, space)

	assert.True(t, responseStatus.Called)
	assert.False(t, apiStatus.NotSuccessful())
}

func deleteDomainEndpoint(statusCode int) (hf http.HandlerFunc, status *testhelpers.RequestStatus) {
	status = &testhelpers.RequestStatus{}
	hf = testhelpers.CreateEndpoint(
		"DELETE",
		"/v2/domains/my-domain-guid?recursive=true",
		testhelpers.EndpointCalledMatcher(status),
		testhelpers.TestResponse{Status: statusCode},
	)
	return
}

func TestDeleteDomainSuccess(t *testing.T) {
	hf, responseStatus := deleteDomainEndpoint(http.StatusOK)
	ts := httptest.NewTLSServer(hf)
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway()

	repo := NewCloudControllerDomainRepository(&config, gateway)

	domain := cf.Domain{Name: "example.com", Guid: "my-domain-guid"}

	apiStatus := repo.DeleteDomain(domain)

	assert.True(t, responseStatus.Called)
	assert.False(t, apiStatus.NotSuccessful())
}

func TestDeleteDomainFailure(t *testing.T) {
	hf, responseStatus := deleteDomainEndpoint(http.StatusBadRequest)
	ts := httptest.NewTLSServer(hf)
	defer ts.Close()

	config := configuration.Configuration{
		AccessToken: "BEARER my_access_token",
		Target:      ts.URL,
	}
	gateway := net.NewCloudControllerGateway()

	repo := NewCloudControllerDomainRepository(&config, gateway)

	domain := cf.Domain{Name: "example.com", Guid: "my-domain-guid"}

	apiStatus := repo.DeleteDomain(domain)

	assert.True(t, responseStatus.Called)
	assert.True(t, apiStatus.NotSuccessful())
}
<|endoftext|>


<commit_before>package lmdbpool

import (
	"fmt"
	"log"
	"sync"
	"sync/atomic"

	"github.com/bmatsuo/lmdb-go/lmdb"
)

// UpdateHandling describes how a TxnPool handles existing lmdb.Readonly
// transactions when an environment update occurs.  Applications with a high
// rate of large updates may need to choose non-default settings to reduce
// their storage requirements at the cost of read throughput.
type UpdateHandling uint

const (
	// AbortOutstanding causes a TxnPool to abort any lmdb.Readonly
	// transactions that are being returned to the pool after an update.
	AbortOutstanding UpdateHandling = 1 << iota
	RenewOutstanding

	// AbortIdle causes a TxnPool to actively attempt aborting idle
	// transactions in the sync.Pool after an update has been committed.  There
	// is no guarantee when using AbortIdle that all idle readers will be
	// aborted.
	AbortIdle
	RenewOutstanding
)

// TxnPool is a pool for reusing transactions through their Reset and Renew
// methods.  However, even though TxnPool can only reuse lmdb.Readonly
// transactions it this way it should be used to create and terminate all Txns
// if it is used at all.  Update transactions can cause LMDB to use excessive
// numbers of pages when there are long-lived lmdb.Readonly transactions in a
// TxnPool.  Executing all transactions using the TxnPool allows it to track
// updates and prevent long-lived updates from causing excessive disk
// utilization.
type TxnPool struct {
	env    *lmdb.Env
	lastid uintptr
	pool   sync.Pool
}

// NewTxnPool initializes returns a new TxnPool.
func NewTxnPool(env *lmdb.Env) *TxnPool {
	p := &TxnPool{
		env: env,
	}
	return p
}

// Close flushes the pool of transactions and aborts them to free resources so
// that the pool Env may be closed.
func (p *TxnPool) Close() {
	txn, ok := (*lmdb.Txn)(nil), true
	for ok {
		txn, ok = p.pool.Get().(*lmdb.Txn)
		if ok {
			txn.Abort()
		}
	}
}

// BeginTxn is analogous to the BeginTxn method on lmdb.Env but may only be
// used to create lmdb.Readonly transactions.  Any call to BeginTxn that does
// not include lmdb.Readonly will return an error
func (p *TxnPool) BeginTxn(flags uint) (*lmdb.Txn, error) {
	// We can only re-use transactions with exactly the same flags.  So
	// instead of masking flags with lmdb.Readonly an equality comparison
	// is necessary.
	if flags != lmdb.Readonly {
		return nil, fmt.Errorf("flag lmdb.Readonly not provided")
	}

	return p.beginReadonly()
}

func (p *TxnPool) beginReadonly() (*lmdb.Txn, error) {
	txn, ok := p.pool.Get().(*lmdb.Txn)
	if !ok {
		return p.env.BeginTxn(nil, lmdb.Readonly)
	}

	// If txn was holding stale pages the call to txn.Renew() should release
	// them when txn aquires a new lock (this is an implication made by the
	// LMDB documentation as of 0.9.19).
	err := txn.Renew()
	if err != nil {
		// Nothing we can do with txn now other than destroy it.
		txn.Abort()

		// TODO:
		// When this is integrated directly in the lmdb package this can use
		// the same logging functionality that the Txn finalizer uses.
		log.Printf("lmdbpool: failed to renew transaction: %v", err)

		// For now it's not clear what better handling of a renew error would
		// entail so we just try to create a new transaction.  It is assumed
		// that it will fail with the same error... But maybe not?
		return p.env.BeginTxn(nil, lmdb.Readonly)
	}

	// Clear txn.Pooled to let a warning be emitted from the Txn finalizer
	// again.  And, make sure to clear RawRead to make the Txn appear like it
	// was just allocated.
	txn.RawRead = false
	txn.Pooled = false

	return txn, nil
}

func (p *TxnPool) abortReadonly(txn *lmdb.Txn) {
	if !returnTxnToPool {
		// If the pool is disabled from race detection then we just abort the
		// Txn instead of waiting for the finalizer.  See the files put.go and
		// putrace.go for more information.
		txn.Abort()
		return
	}

	if txn.ID() < p.getLastID() {
		// Continuing to use this transaction will potentially prevent LMDB
		// from freeing pages that have been overwritten.
		txn.Abort()
		return
	}

	// Don't waste cycles resetting RawRead here -- the cost be paid when the
	// Txn is reused (if at all).  All we need to do is set txn.Pooled to avoid
	// any warning emitted from the Txn finalizer.
	txn.Pooled = true
	txn.Reset()
	p.pool.Put(txn)
}

// getLastID safely retrieves the value of p.lastid so routines operating on
// the sync.Pool know if a transaction can continue to be used without bloating
// the database.
func (p *TxnPool) getLastID() uintptr {
	return atomic.LoadUintptr(&p.lastid)
}

// CommitID sets the TxnPool's last-known transaction id to invalidate
// previously created lmdb.Readonly transactions and prevent their reuse.
//
// CommitID should only be called if p is not used to create/commit update
// transactions.
func (p *TxnPool) CommitID(id uintptr) {
	// As long as we think we are holding a newer id than lastid we keep trying
	// to CAS until we see a newer id or the CAS succeeds.
	lastid := atomic.LoadUintptr(&p.lastid)
	for lastid < id && !atomic.CompareAndSwapUintptr(&p.lastid, lastid, id) {
		lastid = atomic.LoadUintptr(&p.lastid)
	}

	// TODO:
	// Presuming the CAS succeeded, do we now try to asynchronously terminate
	// transactions in the pool?  Alternative to terminating the transactions,
	// we could go through the pool and renew/reset any stale txns we find?
	//
	// In the case where a single transaction enters and exits the pool
	// repeatedly we are actually doing a disservice to the application because
	// it will need to allocate more Txns than it would otherwise if we were to
	// terminate them. Renewing them preemptively runs the risk of wasting
	// resources.
	//
	// The questions surrounding this require more benchmarks and real world
	// experimentation.
}

// Abort aborts the txn and allows it to be reused if possible.  Abort must
// only be passed lmdb.Txn objects which it returned from a call to BeginTxn.
// Aborting a transaction created through other means will have undefined
// results.
func (p *TxnPool) Abort(txn *lmdb.Txn) {
	p.abortReadonly(txn)
}

// Update is analogous to the Update method on lmdb.Env.
func (p *TxnPool) Update(fn lmdb.TxnOp) error {
	var id uintptr
	err := p.env.Update(func(txn *lmdb.Txn) (err error) {
		err = fn(txn)
		if err != nil {
			return err
		}

		// Save the transaction identifier once we know fn succeeded so that
		// the p.lastid field can be set appropriately once the txn has
		// committed successfully.
		id = txn.ID()

		return nil
	})
	if err != nil {
		return err
	}

	p.CommitID(id)

	return nil
}

// View is analogous to the View method on lmdb.Env.
func (p *TxnPool) View(fn lmdb.TxnOp) error {
	txn, err := p.beginReadonly()
	if err != nil {
		return err
	}
	defer p.abortReadonly(txn)
	return txn.RunOp(fn, false)
}
<commit_msg>lmdbpool: Add UpdateHandling type to tell TxnPool how to handle updates<commit_after>package lmdbpool

import (
	"fmt"
	"log"
	"sync"
	"sync/atomic"

	"github.com/bmatsuo/lmdb-go/lmdb"
)

// UpdateHandling describes how a TxnPool handles existing lmdb.Readonly
// transactions when an environment update occurs.  Applications with a high
// rate of large updates may need to choose non-default settings to reduce
// their storage requirements at the cost of read throughput.
type UpdateHandling uint

const (
	// HandleOutstanding causes a TxnPool to abort any lmdb.Readonly
	// transactions that are being returned to the pool after an update.
	HandleOutstanding UpdateHandling = 1 << iota

	// HandleIdle causes a TxnPool to actively attempt aborting idle
	// transactions in the sync.Pool after an update has been committed.  There
	// is no guarantee when using AbortIdle that all idle readers will be
	// aborted.
	HandleIdle

	// HandleRenew modifies how other UpdateHandling flags are interpretted and
	// causes a TxnPool to renew transactions and put them back in the pool
	// instead of aborting them.
	HandleRenew
)

// TxnPool is a pool for reusing transactions through their Reset and Renew
// methods.  However, even though TxnPool can only reuse lmdb.Readonly
// transactions it this way it should be used to create and terminate all Txns
// if it is used at all.  Update transactions can cause LMDB to use excessive
// numbers of pages when there are long-lived lmdb.Readonly transactions in a
// TxnPool.  Executing all transactions using the TxnPool allows it to track
// updates and prevent long-lived updates from causing excessive disk
// utilization.
type TxnPool struct {
	UpdateHandling UpdateHandling
	env            *lmdb.Env
	lastid         uintptr
	pool           sync.Pool
}

// NewTxnPool initializes returns a new TxnPool.
func NewTxnPool(env *lmdb.Env) *TxnPool {
	p := &TxnPool{
		env: env,
	}
	return p
}

// Close flushes the pool of transactions and aborts them to free resources so
// that the pool Env may be closed.
func (p *TxnPool) Close() {
	txn, ok := (*lmdb.Txn)(nil), true
	for ok {
		txn, ok = p.pool.Get().(*lmdb.Txn)
		if ok {
			txn.Abort()
		}
	}
}

// BeginTxn is analogous to the BeginTxn method on lmdb.Env but may only be
// used to create lmdb.Readonly transactions.  Any call to BeginTxn that does
// not include lmdb.Readonly will return an error
func (p *TxnPool) BeginTxn(flags uint) (*lmdb.Txn, error) {
	// We can only re-use transactions with exactly the same flags.  So
	// instead of masking flags with lmdb.Readonly an equality comparison
	// is necessary.
	if flags != lmdb.Readonly {
		return nil, fmt.Errorf("flag lmdb.Readonly not provided")
	}

	return p.beginReadonly()
}

func (p *TxnPool) beginReadonly() (*lmdb.Txn, error) {
	txn, ok := p.pool.Get().(*lmdb.Txn)
	if !ok {
		return p.env.BeginTxn(nil, lmdb.Readonly)
	}

	// If txn was holding stale pages the call to txn.Renew() should release
	// them when txn aquires a new lock (this is an implication made by the
	// LMDB documentation as of 0.9.19).
	err := txn.Renew()
	if err != nil {
		txn.renewError(err)

		// Nothing we can do with txn now other than destroy it.
		txn.Abort()

		// For now it's not clear what better handling of a renew error would
		// entail so we just try to create a new transaction.  It is assumed
		// that it will fail with the same error... But maybe not?
		return p.env.BeginTxn(nil, lmdb.Readonly)
	}

	// Clear txn.Pooled to let a warning be emitted from the Txn finalizer
	// again.  And, make sure to clear RawRead to make the Txn appear like it
	// was just allocated.
	txn.RawRead = false
	txn.Pooled = false

	return txn, nil
}

func (p *TxnPool) renewError(err error) {
	// TODO:
	// When this is integrated directly in the lmdb package this can use
	// the same logging functionality that the Txn finalizer uses.
	log.Printf("lmdbpool: failed to renew transaction: %v", err)
}

func (p *TxnPool) abortReadonly(txn *lmdb.Txn) {
	if !returnTxnToPool {
		// If the pool is disabled from race detection then we just abort the
		// Txn instead of waiting for the finalizer.  See the files put.go and
		// putrace.go for more information.
		txn.Abort()
		return
	}

	if txn.ID() < p.getLastID() {
		ok, err := p.handleReadonly(txn, HandleOutstanding)
		if err != nil {
			// We attempted to renew the transaction but failed and the
			// transaction was automatically aborted.
			p.renewError(err)
			return
		}
		if !ok {
			// The transaction was aborted instead of being renewed.
			return
		}
	}

	// Don't waste cycles resetting RawRead here -- the cost be paid when the
	// Txn is reused (if at all).  All we need to do is set txn.Pooled to avoid
	// any warning emitted from the Txn finalizer.
	txn.Pooled = true
	txn.Reset()
	p.pool.Put(txn)
}

func (p *TxnPool) handleReadonly(txn *lmdb.Txn, condition UpdateHandling) (renewed bool, err error) {
	if p.UpdateHandling&condition == 0 {
		return
	}

	if p.UpdateHandling&HandleRenew != 0 {
		err = txn.Renew()
		if err != nil {
			// There is not much to do with txn other than abort it.
			txn.Abort()
		}
		return true, err
	}
	txn.Abort()
	return false, nil
}

// getLastID safely retrieves the value of p.lastid so routines operating on
// the sync.Pool know if a transaction can continue to be used without bloating
// the database.
func (p *TxnPool) getLastID() uintptr {
	return atomic.LoadUintptr(&p.lastid)
}

// CommitID sets the TxnPool's last-known transaction id to invalidate
// previously created lmdb.Readonly transactions and prevent their reuse.
//
// CommitID should only be called if p is not used to create/commit update
// transactions.
//
// BUG:
// HandleIdle is not checked.
func (p *TxnPool) CommitID(id uintptr) {
	// As long as we think we are holding a newer id than lastid we keep trying
	// to CAS until we see a newer id or the CAS succeeds.
	lastid := atomic.LoadUintptr(&p.lastid)
	for lastid < id && !atomic.CompareAndSwapUintptr(&p.lastid, lastid, id) {
		lastid = atomic.LoadUintptr(&p.lastid)
	}

	// TODO:
	// Presuming the CAS succeeded, do we now try to asynchronously terminate
	// transactions in the pool?  Alternative to terminating the transactions,
	// we could go through the pool and renew/reset any stale txns we find?
	//
	// In the case where a single transaction enters and exits the pool
	// repeatedly we are actually doing a disservice to the application because
	// it will need to allocate more Txns than it would otherwise if we were to
	// terminate them. Renewing them preemptively runs the risk of wasting
	// resources.
	//
	// The questions surrounding this require more benchmarks and real world
	// experimentation.
}

// Abort aborts the txn and allows it to be reused if possible.  Abort must
// only be passed lmdb.Txn objects which it returned from a call to BeginTxn.
// Aborting a transaction created through other means will have undefined
// results.
func (p *TxnPool) Abort(txn *lmdb.Txn) {
	p.abortReadonly(txn)
}

// Update is analogous to the Update method on lmdb.Env.
func (p *TxnPool) Update(fn lmdb.TxnOp) error {
	var id uintptr
	err := p.env.Update(func(txn *lmdb.Txn) (err error) {
		err = fn(txn)
		if err != nil {
			return err
		}

		// Save the transaction identifier once we know fn succeeded so that
		// the p.lastid field can be set appropriately once the txn has
		// committed successfully.
		id = txn.ID()

		return nil
	})
	if err != nil {
		return err
	}

	p.CommitID(id)

	return nil
}

// View is analogous to the View method on lmdb.Env.
func (p *TxnPool) View(fn lmdb.TxnOp) error {
	txn, err := p.beginReadonly()
	if err != nil {
		return err
	}
	defer p.abortReadonly(txn)
	return txn.RunOp(fn, false)
}
<|endoftext|>


<commit_before>package expiration

import (
	"sync"
	"sync/atomic"
	"time"

	"code.cloudfoundry.org/clock"
	loggingclient "code.cloudfoundry.org/diego-logging-client"
	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/locket/db"
	"code.cloudfoundry.org/locket/models"
)

//go:generate counterfeiter . LockPick
type LockPick interface {
	RegisterTTL(logger lager.Logger, lock *db.Lock)
	ExpirationCounts() (uint32, uint32) // return lock and presence expirations, resp.
}

type lockPick struct {
	lockDB                db.LockDB
	clock                 clock.Clock
	metronClient          loggingclient.IngressClient
	lockTTLs              map[checkKey]chanAndIndex
	lockMutex             *sync.Mutex
	presencesExpiredCount *uint32
	locksExpiredCount     *uint32
}

type chanAndIndex struct {
	channel chan struct{}
	index   int64
}

type checkKey struct {
	key string
	id  string
}

func NewLockPick(lockDB db.LockDB, clock clock.Clock, metronClient loggingclient.IngressClient) lockPick {
	return lockPick{
		lockDB:                lockDB,
		clock:                 clock,
		metronClient:          metronClient,
		lockTTLs:              make(map[checkKey]chanAndIndex),
		lockMutex:             &sync.Mutex{},
		presencesExpiredCount: new(uint32),
		locksExpiredCount:     new(uint32),
	}
}

func (l lockPick) ExpirationCounts() (uint32, uint32) {
	return atomic.LoadUint32(l.locksExpiredCount), atomic.LoadUint32(l.presencesExpiredCount)
}

func (l lockPick) RegisterTTL(logger lager.Logger, lock *db.Lock) {
	logger = logger.Session("register-ttl", lager.Data{"key": lock.Key, "modified-index": lock.ModifiedIndex, "type": lock.Type})
	logger.Debug("starting")
	logger.Debug("completed")

	newChanIndex := chanAndIndex{
		channel: make(chan struct{}),
		index:   lock.ModifiedIndex,
	}
	l.lockMutex.Lock()
	defer l.lockMutex.Unlock()

	channelIndex, ok := l.lockTTLs[checkKeyFromLock(lock)]
	if ok && channelIndex.index >= newChanIndex.index {
		logger.Debug("found-expiration-goroutine-for-index", lager.Data{"index": channelIndex.index})
		return
	}

	if ok && channelIndex.index < newChanIndex.index {
		close(channelIndex.channel)
	}

	l.lockTTLs[checkKeyFromLock(lock)] = newChanIndex
	go l.checkExpiration(logger, lock, newChanIndex.channel)
}

func (l lockPick) checkExpiration(logger lager.Logger, lock *db.Lock, closeChan chan struct{}) {
	lockTimer := l.clock.NewTimer(time.Duration(lock.TtlInSeconds) * time.Second)

	for {
		select {
		case <-closeChan:
			logger.Debug("cancelling-old-check-goroutine")
			return
		case <-lockTimer.C():
			defer func() {
				l.lockMutex.Lock()
				chanIndex := l.lockTTLs[checkKeyFromLock(lock)]
				if chanIndex.index == lock.ModifiedIndex {
					delete(l.lockTTLs, checkKeyFromLock(lock))
				}
				l.lockMutex.Unlock()
			}()

			expired, err := l.lockDB.FetchAndRelease(logger, lock)
			if err != nil {
				logger.Error("failed-compare-and-release", err)
				return
			}

			if expired {
				logger.Info("lock-expired")
				counter := l.locksExpiredCount
				if lock.Type == models.PresenceType {
					counter = l.presencesExpiredCount
				}
				atomic.AddUint32(counter, 1)
			}
			return
		}
	}
}

func checkKeyFromLock(lock *db.Lock) checkKey {
	return checkKey{
		key: lock.Key,
		id:  lock.ModifiedId,
	}
}
<commit_msg>Remove unnecessary loop from expiration check func<commit_after>package expiration

import (
	"sync"
	"sync/atomic"
	"time"

	"code.cloudfoundry.org/clock"
	loggingclient "code.cloudfoundry.org/diego-logging-client"
	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/locket/db"
	"code.cloudfoundry.org/locket/models"
)

//go:generate counterfeiter . LockPick
type LockPick interface {
	RegisterTTL(logger lager.Logger, lock *db.Lock)
	ExpirationCounts() (uint32, uint32) // return lock and presence expirations, resp.
}

type lockPick struct {
	lockDB                db.LockDB
	clock                 clock.Clock
	metronClient          loggingclient.IngressClient
	lockTTLs              map[checkKey]chanAndIndex
	lockMutex             *sync.Mutex
	presencesExpiredCount *uint32
	locksExpiredCount     *uint32
}

type chanAndIndex struct {
	channel chan struct{}
	index   int64
}

type checkKey struct {
	key string
	id  string
}

func NewLockPick(lockDB db.LockDB, clock clock.Clock, metronClient loggingclient.IngressClient) lockPick {
	return lockPick{
		lockDB:                lockDB,
		clock:                 clock,
		metronClient:          metronClient,
		lockTTLs:              make(map[checkKey]chanAndIndex),
		lockMutex:             &sync.Mutex{},
		presencesExpiredCount: new(uint32),
		locksExpiredCount:     new(uint32),
	}
}

func (l lockPick) ExpirationCounts() (uint32, uint32) {
	return atomic.LoadUint32(l.locksExpiredCount), atomic.LoadUint32(l.presencesExpiredCount)
}

func (l lockPick) RegisterTTL(logger lager.Logger, lock *db.Lock) {
	logger = logger.Session("register-ttl", lager.Data{"key": lock.Key, "modified-index": lock.ModifiedIndex, "type": lock.Type})
	logger.Debug("starting")
	logger.Debug("completed")

	newChanIndex := chanAndIndex{
		channel: make(chan struct{}),
		index:   lock.ModifiedIndex,
	}
	l.lockMutex.Lock()
	defer l.lockMutex.Unlock()

	channelIndex, ok := l.lockTTLs[checkKeyFromLock(lock)]
	if ok && channelIndex.index >= newChanIndex.index {
		logger.Debug("found-expiration-goroutine-for-index", lager.Data{"index": channelIndex.index})
		return
	}

	if ok && channelIndex.index < newChanIndex.index {
		close(channelIndex.channel)
	}

	l.lockTTLs[checkKeyFromLock(lock)] = newChanIndex
	go l.checkExpiration(logger, lock, newChanIndex.channel)
}

func (l lockPick) checkExpiration(logger lager.Logger, lock *db.Lock, closeChan chan struct{}) {
	lockTimer := l.clock.NewTimer(time.Duration(lock.TtlInSeconds) * time.Second)

	select {
	case <-closeChan:
		logger.Debug("cancelling-old-check-goroutine")
		return
	case <-lockTimer.C():
		defer func() {
			l.lockMutex.Lock()
			chanIndex := l.lockTTLs[checkKeyFromLock(lock)]
			if chanIndex.index == lock.ModifiedIndex {
				delete(l.lockTTLs, checkKeyFromLock(lock))
			}
			l.lockMutex.Unlock()
		}()

		expired, err := l.lockDB.FetchAndRelease(logger, lock)
		if err != nil {
			logger.Error("failed-compare-and-release", err)
			return
		}

		if expired {
			logger.Info("lock-expired")
			counter := l.locksExpiredCount
			if lock.Type == models.PresenceType {
				counter = l.presencesExpiredCount
			}
			atomic.AddUint32(counter, 1)
		}
		return
	}
}

func checkKeyFromLock(lock *db.Lock) checkKey {
	return checkKey{
		key: lock.Key,
		id:  lock.ModifiedId,
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author:polaris	polaris@studygolang.com

package logic

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/polaris1119/config"
	"github.com/polaris1119/goutils"
	"github.com/polaris1119/logger"
	"github.com/polaris1119/times"
)

const (
	WsMsgNotify = iota // 通知消息
	WsMsgOnline        // 发送在线用户数（和需要时也发历史最高）
)

const MessageQueueLen = 5

type Message struct {
	Type int         `json:"type"`
	Body interface{} `json:"body"`
}

func NewMessage(msgType int, msgBody interface{}) *Message {
	return &Message{
		Type: msgType,
		Body: msgBody,
	}
}

type UserData struct {
	// 该用户收到的消息（key为serverId）
	serverMsgQueue map[int]chan *Message
	lastAccessTime time.Time
	onlineDuartion time.Duration

	rwMutex sync.RWMutex
}

func (this *UserData) Len() int {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.serverMsgQueue)
}

func (this *UserData) MessageQueue(serverId int) chan *Message {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return this.serverMsgQueue[serverId]
}

func (this *UserData) Remove(serverId int) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	delete(this.serverMsgQueue, serverId)
}

func (this *UserData) InitMessageQueue(serverId int) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	this.serverMsgQueue[serverId] = make(chan *Message, MessageQueueLen)
}

func (this *UserData) SendMessage(message *Message) {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()

	for serverId, messageQueue := range this.serverMsgQueue {
		// 有可能用户已经退出，导致 messageQueue 满，阻塞
		if len(messageQueue) < MessageQueueLen {
			messageQueue <- message
		} else {
			logger.Infoln("server_id:", serverId, "had close")

			delete(this.serverMsgQueue, serverId)
		}
	}
}

// 用于 expvar 统计信息
type LoginUser struct {
	Uid            int    `json:"uid"`
	LastAccessTime string `json:"last_access_time"`
	OnlineDuartion string `json:"online_duration"`
}
type LoginUserSlice []*LoginUser

func (self LoginUserSlice) String() string {
	b, err := json.Marshal(self)
	if err != nil {
		return "[]"
	}

	return string(b)
}

var Book = &book{users: make(map[int]*UserData), uids: make(map[int]struct{})}

type book struct {
	users map[int]*UserData
	// 登录用户
	uids    map[int]struct{}
	rwMutex sync.RWMutex
}

// 增加一个用户到book中（有可能是用户的另一个请求）
// user为UID或IP地址的int表示
func (this *book) AddUser(user, serverId int, isUid bool) *UserData {
	var userData *UserData
	var ok bool
	this.rwMutex.Lock()
	if userData, ok = this.users[user]; ok {
		this.rwMutex.Unlock()

		logger.Infoln("user:", user, "had enter")

		userData.InitMessageQueue(serverId)
		userData.onlineDuartion += time.Now().Sub(userData.lastAccessTime)
		userData.lastAccessTime = time.Now()
	} else {
		userData = &UserData{
			serverMsgQueue: map[int]chan *Message{serverId: make(chan *Message, MessageQueueLen)},
			lastAccessTime: time.Now(),
		}
		this.users[user] = userData
		if isUid {
			this.uids[user] = struct{}{}
		}
		length := len(this.users)

		this.rwMutex.Unlock()

		onlineInfo := map[string]int{"online": length}
		// 在线人数超过历史最高
		if length > MaxOnlineNum() {
			maxRwMu.Lock()
			maxOnlineNum = length
			onlineInfo["maxonline"] = maxOnlineNum
			maxRwMu.Unlock()
			saveMaxOnlineNum()
		}
		// 广播给其他人：有新用户进来，包括可能的新历史最高
		message := NewMessage(WsMsgOnline, onlineInfo)
		go this.BroadcastToOthersMessage(message, user)
	}

	return userData
}

// 删除用户
func (this *book) DelUser(user, serverId int, isUid bool) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()

	// 自己只有一个页面建立websocket连接
	if this.users[user].Len() == 1 {
		delete(this.users, user)
		if isUid {
			delete(this.uids, user)
		}
	} else {
		this.users[user].Remove(serverId)
	}
}

// 判断用户是否还在线（user 有可能是IP）
func (this *book) UserIsOnline(user int) bool {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	if _, ok := this.users[user]; ok {
		return true
	}
	return false
}

// 判断注册用户是否还在线（user 有可能是IP）
func (this *book) RegUserIsOnline(uid int) bool {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	if _, ok := this.uids[uid]; ok {
		return true
	}
	return false
}

// 在线用户数
func (this *book) Len() int {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.users)
}

// 在线注册会员数
func (this *book) LoginLen() int {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.uids)
}

func (this *book) LoginUserData() LoginUserSlice {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()

	loginUserData := LoginUserSlice(make([]*LoginUser, 0, len(this.uids)))
	for uid := range this.uids {
		user := this.users[uid]
		loginUserData = append(loginUserData, &LoginUser{
			Uid:            uid,
			LastAccessTime: times.Format("Y-m-d H:i:s", user.lastAccessTime),
			OnlineDuartion: user.onlineDuartion.String(),
		})
	}

	return loginUserData
}

// 给某个用户发送一条消息
func (this *book) PostMessage(uid int, message *Message) {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	if userData, ok := this.users[uid]; ok {
		logger.Infoln("post message to", uid, message)
		go userData.SendMessage(message)
	}
}

// 给所有用户广播消息
func (this *book) BroadcastAllUsersMessage(message *Message) {
	logger.Infoln("BroadcastAllUsersMessage message", message)

	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	for _, userData := range this.users {
		userData.SendMessage(message)
	}
}

// 给除了自己的其他用户广播消息
func (this *book) BroadcastToOthersMessage(message *Message, myself int) {
	logger.Infoln("BroadcastToOthersMessage message", message)

	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	for uid, userData := range this.users {
		if uid == myself {
			continue
		}
		userData.SendMessage(message)
	}
}

var (
	// 保存历史最大在线用户数
	maxOnlineNum int
	maxRwMu      sync.RWMutex
)

func initMaxOnlineNum() {
	maxRwMu.Lock()
	defer maxRwMu.Unlock()
	if maxOnlineNum == 0 {
		data, err := ioutil.ReadFile(getDataFile())
		if err != nil {
			logger.Errorln("read data file error:", err)
			return
		}
		maxOnlineNum = goutils.MustInt(strings.TrimSpace(string(data)))
	}
}

// 获得历史最高在线人数
func MaxOnlineNum() int {
	initMaxOnlineNum()
	maxRwMu.RLock()
	defer maxRwMu.RUnlock()
	return maxOnlineNum
}

func saveMaxOnlineNum() {
	data := []byte(strconv.Itoa(MaxOnlineNum()))
	err := ioutil.WriteFile(getDataFile(), data, 0777)
	if err != nil {
		logger.Errorln("write data file error:", err)
		return
	}
}

var dataFile string

func getDataFile() string {
	if dataFile != "" {
		return dataFile
	}
	dataFile = config.ConfigFile.MustValue("global", "data_path")
	if !filepath.IsAbs(dataFile) {
		dataFile = config.ROOT + "/" + dataFile
	}
	// 文件夹不存在，则创建
	dataPath := filepath.Dir(dataFile)
	if err := os.MkdirAll(dataPath, 0777); err != nil {
		logger.Errorln("MkdirAll error:", err)
	}
	return dataFile
}
<commit_msg>bugfix<commit_after>// Copyright 2016 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author:polaris	polaris@studygolang.com

package logic

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/polaris1119/config"
	"github.com/polaris1119/goutils"
	"github.com/polaris1119/logger"
	"github.com/polaris1119/times"
)

const (
	WsMsgNotify = iota // 通知消息
	WsMsgOnline        // 发送在线用户数（和需要时也发历史最高）
)

const MessageQueueLen = 5

type Message struct {
	Type int         `json:"type"`
	Body interface{} `json:"body"`
}

func NewMessage(msgType int, msgBody interface{}) *Message {
	return &Message{
		Type: msgType,
		Body: msgBody,
	}
}

type UserData struct {
	// 该用户收到的消息（key为serverId）
	serverMsgQueue map[int]chan *Message
	lastAccessTime time.Time
	onlineDuartion time.Duration

	rwMutex sync.RWMutex
}

func (this *UserData) Len() int {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.serverMsgQueue)
}

func (this *UserData) MessageQueue(serverId int) chan *Message {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return this.serverMsgQueue[serverId]
}

func (this *UserData) Remove(serverId int) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	delete(this.serverMsgQueue, serverId)
}

func (this *UserData) InitMessageQueue(serverId int) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	this.serverMsgQueue[serverId] = make(chan *Message, MessageQueueLen)
}

func (this *UserData) SendMessage(message *Message) {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()

	for serverId, messageQueue := range this.serverMsgQueue {
		// 有可能用户已经退出，导致 messageQueue 满，阻塞
		if len(messageQueue) < MessageQueueLen {
			messageQueue <- message
		} else {
			logger.Infoln("server_id:", serverId, "had close")

			delete(this.serverMsgQueue, serverId)
		}
	}
}

// 用于 expvar 统计信息
type LoginUser struct {
	Uid            int    `json:"uid"`
	LastAccessTime string `json:"last_access_time"`
	OnlineDuartion string `json:"online_duration"`
}
type LoginUserSlice []*LoginUser

func (self LoginUserSlice) String() string {
	b, err := json.Marshal(self)
	if err != nil {
		return "[]"
	}

	return string(b)
}

var Book = &book{users: make(map[int]*UserData), uids: make(map[int]struct{})}

type book struct {
	users map[int]*UserData
	// 登录用户
	uids    map[int]struct{}
	rwMutex sync.RWMutex
}

// 增加一个用户到book中（有可能是用户的另一个请求）
// user为UID或IP地址的int表示
func (this *book) AddUser(user, serverId int, isUid bool) *UserData {
	var userData *UserData
	var ok bool
	this.rwMutex.Lock()
	if userData, ok = this.users[user]; ok {
		this.rwMutex.Unlock()

		logger.Infoln("user:", user, "had enter")

		userData.InitMessageQueue(serverId)
		userData.onlineDuartion += time.Now().Sub(userData.lastAccessTime)
		userData.lastAccessTime = time.Now()
	} else {
		userData = &UserData{
			serverMsgQueue: map[int]chan *Message{serverId: make(chan *Message, MessageQueueLen)},
			lastAccessTime: time.Now(),
		}
		this.users[user] = userData
		if isUid {
			this.uids[user] = struct{}{}
		}
		length := len(this.users)

		this.rwMutex.Unlock()

		onlineInfo := map[string]int{"online": length}
		// 在线人数超过历史最高
		if length > MaxOnlineNum() {
			maxRwMu.Lock()
			maxOnlineNum = length
			onlineInfo["maxonline"] = maxOnlineNum
			maxRwMu.Unlock()
			saveMaxOnlineNum()
		}
		// 广播给其他人：有新用户进来，包括可能的新历史最高
		message := NewMessage(WsMsgOnline, onlineInfo)
		go this.BroadcastToOthersMessage(message, user)
	}

	return userData
}

// 删除用户
func (this *book) DelUser(user, serverId int, isUid bool) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()

	// 已经不存在了
	if _, ok := this.users[user]; !ok {
		if isUid {
			delete(this.uids, user)
		}
		return
	}

	// 自己只有一个页面建立websocket连接
	if this.users[user].Len() == 1 {
		delete(this.users, user)
		if isUid {
			delete(this.uids, user)
		}
	} else {
		this.users[user].Remove(serverId)
	}
}

// 判断用户是否还在线（user 有可能是IP）
func (this *book) UserIsOnline(user int) bool {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	if _, ok := this.users[user]; ok {
		return true
	}
	return false
}

// 判断注册用户是否还在线（user 有可能是IP）
func (this *book) RegUserIsOnline(uid int) bool {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	if _, ok := this.uids[uid]; ok {
		return true
	}
	return false
}

// 在线用户数
func (this *book) Len() int {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.users)
}

// 在线注册会员数
func (this *book) LoginLen() int {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	return len(this.uids)
}

func (this *book) LoginUserData() LoginUserSlice {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()

	loginUserData := LoginUserSlice(make([]*LoginUser, 0, len(this.uids)))
	for uid := range this.uids {
		user := this.users[uid]
		loginUserData = append(loginUserData, &LoginUser{
			Uid:            uid,
			LastAccessTime: times.Format("Y-m-d H:i:s", user.lastAccessTime),
			OnlineDuartion: user.onlineDuartion.String(),
		})
	}

	return loginUserData
}

// 给某个用户发送一条消息
func (this *book) PostMessage(uid int, message *Message) {
	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	if userData, ok := this.users[uid]; ok {
		logger.Infoln("post message to", uid, message)
		go userData.SendMessage(message)
	}
}

// 给所有用户广播消息
func (this *book) BroadcastAllUsersMessage(message *Message) {
	logger.Infoln("BroadcastAllUsersMessage message", message)

	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	for _, userData := range this.users {
		userData.SendMessage(message)
	}
}

// 给除了自己的其他用户广播消息
func (this *book) BroadcastToOthersMessage(message *Message, myself int) {
	logger.Infoln("BroadcastToOthersMessage message", message)

	this.rwMutex.RLock()
	defer this.rwMutex.RUnlock()
	for uid, userData := range this.users {
		if uid == myself {
			continue
		}
		userData.SendMessage(message)
	}
}

var (
	// 保存历史最大在线用户数
	maxOnlineNum int
	maxRwMu      sync.RWMutex
)

func initMaxOnlineNum() {
	maxRwMu.Lock()
	defer maxRwMu.Unlock()
	if maxOnlineNum == 0 {
		data, err := ioutil.ReadFile(getDataFile())
		if err != nil {
			logger.Errorln("read data file error:", err)
			return
		}
		maxOnlineNum = goutils.MustInt(strings.TrimSpace(string(data)))
	}
}

// 获得历史最高在线人数
func MaxOnlineNum() int {
	initMaxOnlineNum()
	maxRwMu.RLock()
	defer maxRwMu.RUnlock()
	return maxOnlineNum
}

func saveMaxOnlineNum() {
	data := []byte(strconv.Itoa(MaxOnlineNum()))
	err := ioutil.WriteFile(getDataFile(), data, 0777)
	if err != nil {
		logger.Errorln("write data file error:", err)
		return
	}
}

var dataFile string

func getDataFile() string {
	if dataFile != "" {
		return dataFile
	}
	dataFile = config.ConfigFile.MustValue("global", "data_path")
	if !filepath.IsAbs(dataFile) {
		dataFile = config.ROOT + "/" + dataFile
	}
	// 文件夹不存在，则创建
	dataPath := filepath.Dir(dataFile)
	if err := os.MkdirAll(dataPath, 0777); err != nil {
		logger.Errorln("MkdirAll error:", err)
	}
	return dataFile
}
<|endoftext|>


<commit_before>package gotop

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
)

var (
	netdir = "/sys/class/net"
	rxfile = "/sys/class/net/%s/statistics/rx_bytes"
	txfile = "/sys/class/net/%s/statistics/tx_bytes"
)

type Net struct {
	Rxrate float64
	Txrate float64
}

func connNames() ([]string, error) {
	file, err := os.Open(netdir)
	if err != nil {
		return make([]string, 0), err
	}
	fi, _ := file.Readdir(-1)
	conns := make([]string, len(fi))
	for index, i := range fi {
		conns[index] = i.Name()
	}
	return conns, nil
}

func numbytes(path string) (float64, error) {
	conns, err := connNames()
	if err != nil {
		return 0, err
	}
	var total int
	for _, conn := range conns {
		p := fmt.Sprintf(path, conn)
		val, err := readFile(p)
		if err != nil {
			return 0, err
		}
		val = strings.Trim(val, " \n")
		// Skip the lo interface. Its the loopback used by local processes
		if val == "lo" {
			continue
		}
		num, _ := strconv.Atoi(val)
		total = total + num
	}
	return float64(total), nil
}

func getrate(prevbytes, curbytes float64, prevtime, curtime int64) float64 {
	// Needs to be converted from nanoseconds to seconds
	timedelta := float64(curtime-prevtime) / 1000000000
	return (curbytes - prevbytes) / (timedelta * 1024)
}

func NetRate(done <-chan struct{}, delay time.Duration) (<-chan Net, <-chan error) {
	result := make(chan Net, 1)
	errc := make(chan error)
	var err error
	var net Net
	cleanup := func() {
		errc <- err
		close(errc)
		close(result)
	}
	go func() {
		defer cleanup()
		var prevrx, currx, prevtx, curtx float64
		var prevtime, curtime int64
		for {
			prevrx = currx
			currx, err = numbytes(rxfile)
			if err != nil {
				return
			}
			prevtx = curtx
			curtx, err = numbytes(txfile)
			if err != nil {
				return
			}
			prevtime = curtime
			curtime = time.Now().UnixNano()
			net.Rxrate = getrate(prevrx, currx, prevtime, curtime)
			net.Txrate = getrate(prevtx, curtx, prevtime, curtime)
			select {
			case result <- net:
			case <-done:
				return
			}
			time.Sleep(delay)
		}
	}()
	return result, errc
}
<commit_msg>Truncate value of net rate to 2 places after decimal<commit_after>package gotop

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
)

var (
	netdir = "/sys/class/net"
	rxfile = "/sys/class/net/%s/statistics/rx_bytes"
	txfile = "/sys/class/net/%s/statistics/tx_bytes"
)

type Net struct {
	Rxrate float64
	Txrate float64
}

func connNames() ([]string, error) {
	file, err := os.Open(netdir)
	if err != nil {
		return make([]string, 0), err
	}
	fi, _ := file.Readdir(-1)
	conns := make([]string, len(fi))
	for index, i := range fi {
		conns[index] = i.Name()
	}
	return conns, nil
}

func numbytes(path string) (float64, error) {
	conns, err := connNames()
	if err != nil {
		return 0, err
	}
	var total int
	for _, conn := range conns {
		p := fmt.Sprintf(path, conn)
		val, err := readFile(p)
		if err != nil {
			return 0, err
		}
		val = strings.Trim(val, " \n")
		// Skip the lo interface. Its the loopback used by local processes
		if val == "lo" {
			continue
		}
		num, _ := strconv.Atoi(val)
		total = total + num
	}
	return float64(total), nil
}

func getrate(prevbytes, curbytes float64, prevtime, curtime int64) float64 {
	// Needs to be converted from nanoseconds to seconds
	timedelta := float64(curtime-prevtime) / 1000000000
	rate := (curbytes - prevbytes) / (timedelta * 1024)
	// Return value is truncated to 2 places after decimal
	return float64(int(100*rate)) / 100
}

func NetRate(done <-chan struct{}, delay time.Duration) (<-chan Net, <-chan error) {
	result := make(chan Net, 1)
	errc := make(chan error)
	var err error
	var net Net
	cleanup := func() {
		errc <- err
		close(errc)
		close(result)
	}
	go func() {
		defer cleanup()
		var prevrx, currx, prevtx, curtx float64
		var prevtime, curtime int64
		for {
			prevrx = currx
			currx, err = numbytes(rxfile)
			if err != nil {
				return
			}
			prevtx = curtx
			curtx, err = numbytes(txfile)
			if err != nil {
				return
			}
			prevtime = curtime
			curtime = time.Now().UnixNano()
			net.Rxrate = getrate(prevrx, currx, prevtime, curtime)
			net.Txrate = getrate(prevtx, curtx, prevtime, curtime)
			select {
			case result <- net:
			case <-done:
				return
			}
			time.Sleep(delay)
		}
	}()
	return result, errc
}
<|endoftext|>