<commit_before>package kubernetes

import (
	"io"
	"strconv"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/extensions"
	"k8s.io/kubernetes/pkg/client/cache"
	"k8s.io/kubernetes/pkg/client/restclient"
	"k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
	clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
	"k8s.io/kubernetes/pkg/util/wait"
)

// Client keeps track of running kubernetes pods and services
type Client interface {
	Stop()
	WalkPods(f func(Pod) error) error
	WalkServices(f func(Service) error) error
	WalkDeployments(f func(Deployment) error) error
	WalkReplicaSets(f func(ReplicaSet) error) error
	WalkDaemonSets(f func(DaemonSet) error) error
	WalkReplicationControllers(f func(ReplicationController) error) error
	WalkNodes(f func(*api.Node) error) error

	WatchPods(f func(Event, Pod))

	GetLogs(namespaceID, podID string) (io.ReadCloser, error)
	DeletePod(namespaceID, podID string) error
	ScaleUp(resource, namespaceID, id string) error
	ScaleDown(resource, namespaceID, id string) error
}

type client struct {
	quit                       chan struct{}
	resyncPeriod               time.Duration
	client                     *unversioned.Client
	extensionsClient           *unversioned.ExtensionsClient
	podStore                   *cache.StoreToPodLister
	serviceStore               *cache.StoreToServiceLister
	deploymentStore            *cache.StoreToDeploymentLister
	replicaSetStore            *cache.StoreToReplicaSetLister
	daemonSetStore             *cache.StoreToDaemonSetLister
	replicationControllerStore *cache.StoreToReplicationControllerLister
	nodeStore                  *cache.StoreToNodeLister

	podWatchesMutex sync.Mutex
	podWatches      []func(Event, Pod)
}

// runReflectorUntil is equivalent to cache.Reflector.RunUntil, but it also logs
// errors, which cache.Reflector.RunUntil simply ignores
func runReflectorUntil(r *cache.Reflector, resyncPeriod time.Duration, stopCh <-chan struct{}) {
	loggingListAndWatch := func() {
		if err := r.ListAndWatch(stopCh); err != nil {
			log.Errorf("Kubernetes reflector: %v", err)
		}
	}
	go wait.Until(loggingListAndWatch, resyncPeriod, stopCh)
}

// ClientConfig establishes the configuration for the kubernetes client
type ClientConfig struct {
	Interval             time.Duration
	CertificateAuthority string
	ClientCertificate    string
	ClientKey            string
	Cluster              string
	Context              string
	Insecure             bool
	Kubeconfig           string
	Password             string
	Server               string
	Token                string
	User                 string
	Username             string
}

// NewClient returns a usable Client. Don't forget to Stop it.
func NewClient(config ClientConfig) (Client, error) {
	var restConfig *restclient.Config
	if config.Server == "" && config.Kubeconfig == "" {
		// If no API server address or kubeconfig was provided, assume we are running
		// inside a pod. Try to connect to the API server through its
		// Service environment variables, using the default Service
		// Account Token.
		var err error
		if restConfig, err = restclient.InClusterConfig(); err != nil {
			return nil, err
		}
	} else {
		var err error
		restConfig, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
			&clientcmd.ClientConfigLoadingRules{ExplicitPath: config.Kubeconfig},
			&clientcmd.ConfigOverrides{
				AuthInfo: clientcmdapi.AuthInfo{
					ClientCertificate: config.ClientCertificate,
					ClientKey:         config.ClientKey,
					Token:             config.Token,
					Username:          config.Username,
					Password:          config.Password,
				},
				ClusterInfo: clientcmdapi.Cluster{
					Server:                config.Server,
					InsecureSkipTLSVerify: config.Insecure,
					CertificateAuthority:  config.CertificateAuthority,
				},
				Context: clientcmdapi.Context{
					Cluster:  config.Cluster,
					AuthInfo: config.User,
				},
				CurrentContext: config.Context,
			},
		).ClientConfig()
		if err != nil {
			return nil, err
		}

	}
	log.Infof("kubernetes: targeting api server %s", restConfig.Host)

	c, err := unversioned.New(restConfig)
	if err != nil {
		return nil, err
	}

	ec, err := unversioned.NewExtensions(restConfig)
	if err != nil {
		return nil, err
	}

	result := &client{
		quit:             make(chan struct{}),
		resyncPeriod:     config.Interval,
		client:           c,
		extensionsClient: ec,
	}

	podStore := NewEventStore(result.triggerPodWatches, cache.MetaNamespaceKeyFunc)
	result.podStore = &cache.StoreToPodLister{Store: result.setupStore(c, "pods", &api.Pod{}, podStore)}
	result.serviceStore = &cache.StoreToServiceLister{Store: result.setupStore(c, "services", &api.Service{}, nil)}
	result.replicationControllerStore = &cache.StoreToReplicationControllerLister{Store: result.setupStore(c, "replicationcontrollers", &api.ReplicationController{}, nil)}
	result.nodeStore = &cache.StoreToNodeLister{Store: result.setupStore(c, "nodes", &api.Node{}, nil)}

	// We list deployments here to check if this version of kubernetes is >= 1.2.
	// We would use NegotiateVersion, but Kubernetes 1.1 "supports"
	// extensions/v1beta1, but not deployments or replicasets.
	if _, err := ec.Deployments(api.NamespaceAll).List(api.ListOptions{}); err != nil {
		log.Infof("Deployments and ReplicaSets are not supported by this Kubernetes version: %v", err)
	} else {
		result.deploymentStore = &cache.StoreToDeploymentLister{Store: result.setupStore(ec, "deployments", &extensions.Deployment{}, nil)}
		result.replicaSetStore = &cache.StoreToReplicaSetLister{Store: result.setupStore(ec, "replicasets", &extensions.ReplicaSet{}, nil)}
		result.daemonSetStore = &cache.StoreToDaemonSetLister{Store: result.setupStore(ec, "daemonsets", &extensions.DaemonSet{}, nil)}
	}

	return result, nil
}

func (c *client) setupStore(kclient cache.Getter, resource string, itemType interface{}, nonDefaultStore cache.Store) cache.Store {
	lw := cache.NewListWatchFromClient(kclient, resource, api.NamespaceAll, fields.Everything())
	store := nonDefaultStore
	if store == nil {
		store = cache.NewStore(cache.MetaNamespaceKeyFunc)
	}
	runReflectorUntil(cache.NewReflector(lw, itemType, store, c.resyncPeriod), c.resyncPeriod, c.quit)
	return store
}

func (c *client) WatchPods(f func(Event, Pod)) {
	c.podWatchesMutex.Lock()
	defer c.podWatchesMutex.Unlock()
	c.podWatches = append(c.podWatches, f)
}

func (c *client) triggerPodWatches(e Event, pod interface{}) {
	c.podWatchesMutex.Lock()
	defer c.podWatchesMutex.Unlock()
	for _, watch := range c.podWatches {
		watch(e, NewPod(pod.(*api.Pod)))
	}
}

func (c *client) WalkPods(f func(Pod) error) error {
	pods, err := c.podStore.List(labels.Everything())
	if err != nil {
		return err
	}
	for _, pod := range pods {
		if err := f(NewPod(pod)); err != nil {
			return err
		}
	}
	return nil
}

func (c *client) WalkServices(f func(Service) error) error {
	list, err := c.serviceStore.List()
	if err != nil {
		return err
	}
	for i := range list.Items {
		if err := f(NewService(&(list.Items[i]))); err != nil {
			return err
		}
	}
	return nil
}

func (c *client) WalkDeployments(f func(Deployment) error) error {
	if c.deploymentStore == nil {
		return nil
	}
	list, err := c.deploymentStore.List()
	if err != nil {
		return err
	}
	for i := range list {
		if err := f(NewDeployment(&(list[i]))); err != nil {
			return err
		}
	}
	return nil
}

// WalkReplicaSets calls f for each replica set
func (c *client) WalkReplicaSets(f func(ReplicaSet) error) error {
	if c.replicaSetStore == nil {
		return nil
	}
	list, err := c.replicaSetStore.List()
	if err != nil {
		return err
	}
	for i := range list {
		if err := f(NewReplicaSet(&(list[i]))); err != nil {
			return err
		}
	}
	return nil

}

// WalkReplicationcontrollers calls f for each replication controller
func (c *client) WalkReplicationControllers(f func(ReplicationController) error) error {
	list, err := c.replicationControllerStore.List()
	if err != nil {
		return err
	}
	for i := range list {
		if err := f(NewReplicationController(&(list[i]))); err != nil {
			return err
		}
	}
	return nil
}

// WalkDaemonSets calls f for each daemonset
func (c *client) WalkDaemonSets(f func(DaemonSet) error) error {
	list, err := c.daemonSetStore.List()
	if err != nil {
		return err
	}
	for i := range list.Items {
		if err := f(NewDaemonSet(&(list.Items[i]))); err != nil {
			return err
		}
	}
	return nil
}

func (c *client) WalkNodes(f func(*api.Node) error) error {
	list, err := c.nodeStore.List()
	if err != nil {
		return err
	}
	for i := range list.Items {
		if err := f(&(list.Items[i])); err != nil {
			return err
		}
	}
	return nil
}

func (c *client) GetLogs(namespaceID, podID string) (io.ReadCloser, error) {
	return c.client.RESTClient.Get().
		Namespace(namespaceID).
		Name(podID).
		Resource("pods").
		SubResource("log").
		Param("follow", strconv.FormatBool(true)).
		Param("previous", strconv.FormatBool(false)).
		Param("timestamps", strconv.FormatBool(true)).
		Stream()
}

func (c *client) DeletePod(namespaceID, podID string) error {
	return c.client.RESTClient.Delete().
		Namespace(namespaceID).
		Name(podID).
		Resource("pods").Do().Error()
}

func (c *client) ScaleUp(resource, namespaceID, id string) error {
	return c.modifyScale(resource, namespaceID, id, func(scale *extensions.Scale) {
		scale.Spec.Replicas++
	})
}

func (c *client) ScaleDown(resource, namespaceID, id string) error {
	return c.modifyScale(resource, namespaceID, id, func(scale *extensions.Scale) {
		scale.Spec.Replicas--
	})
}

func (c *client) modifyScale(resource, namespace, id string, f func(*extensions.Scale)) error {
	scaler := c.extensionsClient.Scales(namespace)
	scale, err := scaler.Get(resource, id)
	if err != nil {
		return err
	}
	f(scale)
	_, err = scaler.Update(resource, scale)
	return err
}

func (c *client) Stop() {
	close(c.quit)
}
<commit_msg>Guard against null DaemonSet store<commit_after>package kubernetes

import (
	"io"
	"strconv"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/extensions"
	"k8s.io/kubernetes/pkg/client/cache"
	"k8s.io/kubernetes/pkg/client/restclient"
	"k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
	clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
	"k8s.io/kubernetes/pkg/util/wait"
)

// Client keeps track of running kubernetes pods and services
type Client interface {
	Stop()
	WalkPods(f func(Pod) error) error
	WalkServices(f func(Service) error) error
	WalkDeployments(f func(Deployment) error) error
	WalkReplicaSets(f func(ReplicaSet) error) error
	WalkDaemonSets(f func(DaemonSet) error) error
	WalkReplicationControllers(f func(ReplicationController) error) error
	WalkNodes(f func(*api.Node) error) error

	WatchPods(f func(Event, Pod))

	GetLogs(namespaceID, podID string) (io.ReadCloser, error)
	DeletePod(namespaceID, podID string) error
	ScaleUp(resource, namespaceID, id string) error
	ScaleDown(resource, namespaceID, id string) error
}

type client struct {
	quit                       chan struct{}
	resyncPeriod               time.Duration
	client                     *unversioned.Client
	extensionsClient           *unversioned.ExtensionsClient
	podStore                   *cache.StoreToPodLister
	serviceStore               *cache.StoreToServiceLister
	deploymentStore            *cache.StoreToDeploymentLister
	replicaSetStore            *cache.StoreToReplicaSetLister
	daemonSetStore             *cache.StoreToDaemonSetLister
	replicationControllerStore *cache.StoreToReplicationControllerLister
	nodeStore                  *cache.StoreToNodeLister

	podWatchesMutex sync.Mutex
	podWatches      []func(Event, Pod)
}

// runReflectorUntil is equivalent to cache.Reflector.RunUntil, but it also logs
// errors, which cache.Reflector.RunUntil simply ignores
func runReflectorUntil(r *cache.Reflector, resyncPeriod time.Duration, stopCh <-chan struct{}) {
	loggingListAndWatch := func() {
		if err := r.ListAndWatch(stopCh); err != nil {
			log.Errorf("Kubernetes reflector: %v", err)
		}
	}
	go wait.Until(loggingListAndWatch, resyncPeriod, stopCh)
}

// ClientConfig establishes the configuration for the kubernetes client
type ClientConfig struct {
	Interval             time.Duration
	CertificateAuthority string
	ClientCertificate    string
	ClientKey            string
	Cluster              string
	Context              string
	Insecure             bool
	Kubeconfig           string
	Password             string
	Server               string
	Token                string
	User                 string
	Username             string
}

// NewClient returns a usable Client. Don't forget to Stop it.
func NewClient(config ClientConfig) (Client, error) {
	var restConfig *restclient.Config
	if config.Server == "" && config.Kubeconfig == "" {
		// If no API server address or kubeconfig was provided, assume we are running
		// inside a pod. Try to connect to the API server through its
		// Service environment variables, using the default Service
		// Account Token.
		var err error
		if restConfig, err = restclient.InClusterConfig(); err != nil {
			return nil, err
		}
	} else {
		var err error
		restConfig, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
			&clientcmd.ClientConfigLoadingRules{ExplicitPath: config.Kubeconfig},
			&clientcmd.ConfigOverrides{
				AuthInfo: clientcmdapi.AuthInfo{
					ClientCertificate: config.ClientCertificate,
					ClientKey:         config.ClientKey,
					Token:             config.Token,
					Username:          config.Username,
					Password:          config.Password,
				},
				ClusterInfo: clientcmdapi.Cluster{
					Server:                config.Server,
					InsecureSkipTLSVerify: config.Insecure,
					CertificateAuthority:  config.CertificateAuthority,
				},
				Context: clientcmdapi.Context{
					Cluster:  config.Cluster,
					AuthInfo: config.User,
				},
				CurrentContext: config.Context,
			},
		).ClientConfig()
		if err != nil {
			return nil, err
		}

	}
	log.Infof("kubernetes: targeting api server %s", restConfig.Host)

	c, err := unversioned.New(restConfig)
	if err != nil {
		return nil, err
	}

	ec, err := unversioned.NewExtensions(restConfig)
	if err != nil {
		return nil, err
	}

	result := &client{
		quit:             make(chan struct{}),
		resyncPeriod:     config.Interval,
		client:           c,
		extensionsClient: ec,
	}

	podStore := NewEventStore(result.triggerPodWatches, cache.MetaNamespaceKeyFunc)
	result.podStore = &cache.StoreToPodLister{Store: result.setupStore(c, "pods", &api.Pod{}, podStore)}
	result.serviceStore = &cache.StoreToServiceLister{Store: result.setupStore(c, "services", &api.Service{}, nil)}
	result.replicationControllerStore = &cache.StoreToReplicationControllerLister{Store: result.setupStore(c, "replicationcontrollers", &api.ReplicationController{}, nil)}
	result.nodeStore = &cache.StoreToNodeLister{Store: result.setupStore(c, "nodes", &api.Node{}, nil)}

	// We list deployments here to check if this version of kubernetes is >= 1.2.
	// We would use NegotiateVersion, but Kubernetes 1.1 "supports"
	// extensions/v1beta1, but not deployments, replicasets or daemonsets.
	if _, err := ec.Deployments(api.NamespaceAll).List(api.ListOptions{}); err != nil {
		log.Infof("Deployments, ReplicaSets and DaemonSets are not supported by this Kubernetes version: %v", err)
	} else {
		result.deploymentStore = &cache.StoreToDeploymentLister{Store: result.setupStore(ec, "deployments", &extensions.Deployment{}, nil)}
		result.replicaSetStore = &cache.StoreToReplicaSetLister{Store: result.setupStore(ec, "replicasets", &extensions.ReplicaSet{}, nil)}
		result.daemonSetStore = &cache.StoreToDaemonSetLister{Store: result.setupStore(ec, "daemonsets", &extensions.DaemonSet{}, nil)}
	}

	return result, nil
}

func (c *client) setupStore(kclient cache.Getter, resource string, itemType interface{}, nonDefaultStore cache.Store) cache.Store {
	lw := cache.NewListWatchFromClient(kclient, resource, api.NamespaceAll, fields.Everything())
	store := nonDefaultStore
	if store == nil {
		store = cache.NewStore(cache.MetaNamespaceKeyFunc)
	}
	runReflectorUntil(cache.NewReflector(lw, itemType, store, c.resyncPeriod), c.resyncPeriod, c.quit)
	return store
}

func (c *client) WatchPods(f func(Event, Pod)) {
	c.podWatchesMutex.Lock()
	defer c.podWatchesMutex.Unlock()
	c.podWatches = append(c.podWatches, f)
}

func (c *client) triggerPodWatches(e Event, pod interface{}) {
	c.podWatchesMutex.Lock()
	defer c.podWatchesMutex.Unlock()
	for _, watch := range c.podWatches {
		watch(e, NewPod(pod.(*api.Pod)))
	}
}

func (c *client) WalkPods(f func(Pod) error) error {
	pods, err := c.podStore.List(labels.Everything())
	if err != nil {
		return err
	}
	for _, pod := range pods {
		if err := f(NewPod(pod)); err != nil {
			return err
		}
	}
	return nil
}

func (c *client) WalkServices(f func(Service) error) error {
	list, err := c.serviceStore.List()
	if err != nil {
		return err
	}
	for i := range list.Items {
		if err := f(NewService(&(list.Items[i]))); err != nil {
			return err
		}
	}
	return nil
}

func (c *client) WalkDeployments(f func(Deployment) error) error {
	if c.deploymentStore == nil {
		return nil
	}
	list, err := c.deploymentStore.List()
	if err != nil {
		return err
	}
	for i := range list {
		if err := f(NewDeployment(&(list[i]))); err != nil {
			return err
		}
	}
	return nil
}

// WalkReplicaSets calls f for each replica set
func (c *client) WalkReplicaSets(f func(ReplicaSet) error) error {
	if c.replicaSetStore == nil {
		return nil
	}
	list, err := c.replicaSetStore.List()
	if err != nil {
		return err
	}
	for i := range list {
		if err := f(NewReplicaSet(&(list[i]))); err != nil {
			return err
		}
	}
	return nil

}

// WalkReplicationcontrollers calls f for each replication controller
func (c *client) WalkReplicationControllers(f func(ReplicationController) error) error {
	list, err := c.replicationControllerStore.List()
	if err != nil {
		return err
	}
	for i := range list {
		if err := f(NewReplicationController(&(list[i]))); err != nil {
			return err
		}
	}
	return nil
}

// WalkDaemonSets calls f for each daemonset
func (c *client) WalkDaemonSets(f func(DaemonSet) error) error {
	if c.daemonSetStore == nil {
		return nil
	}
	list, err := c.daemonSetStore.List()
	if err != nil {
		return err
	}
	for i := range list.Items {
		if err := f(NewDaemonSet(&(list.Items[i]))); err != nil {
			return err
		}
	}
	return nil
}

func (c *client) WalkNodes(f func(*api.Node) error) error {
	list, err := c.nodeStore.List()
	if err != nil {
		return err
	}
	for i := range list.Items {
		if err := f(&(list.Items[i])); err != nil {
			return err
		}
	}
	return nil
}

func (c *client) GetLogs(namespaceID, podID string) (io.ReadCloser, error) {
	return c.client.RESTClient.Get().
		Namespace(namespaceID).
		Name(podID).
		Resource("pods").
		SubResource("log").
		Param("follow", strconv.FormatBool(true)).
		Param("previous", strconv.FormatBool(false)).
		Param("timestamps", strconv.FormatBool(true)).
		Stream()
}

func (c *client) DeletePod(namespaceID, podID string) error {
	return c.client.RESTClient.Delete().
		Namespace(namespaceID).
		Name(podID).
		Resource("pods").Do().Error()
}

func (c *client) ScaleUp(resource, namespaceID, id string) error {
	return c.modifyScale(resource, namespaceID, id, func(scale *extensions.Scale) {
		scale.Spec.Replicas++
	})
}

func (c *client) ScaleDown(resource, namespaceID, id string) error {
	return c.modifyScale(resource, namespaceID, id, func(scale *extensions.Scale) {
		scale.Spec.Replicas--
	})
}

func (c *client) modifyScale(resource, namespace, id string, f func(*extensions.Scale)) error {
	scaler := c.extensionsClient.Scales(namespace)
	scale, err := scaler.Get(resource, id)
	if err != nil {
		return err
	}
	f(scale)
	_, err = scaler.Update(resource, scale)
	return err
}

func (c *client) Stop() {
	close(c.quit)
}
<|endoftext|>


<commit_before>package hub

import (
	"code.google.com/p/go.net/websocket"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"os"
	"sync"
	"time"
)

const (
	defaultBufferSize     = 1024
	defaultHeartbeat      = time.Second * 5
	defaultSessionTimeout = time.Second * 30
	idLength              = 16
	bufLength             = 4096
	defaultLoglevel       = 1
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

type Authorizer func(uri, token string, write bool) (authorized bool, err error)

func prettify(obj interface{}) string {
	b, err := json.MarshalIndent(obj, "", "  ")
	if err != nil {
		panic(err)
	}
	return string(b)
}

type Server struct {
	heartbeat      time.Duration
	sessionTimeout time.Duration
	sessions       map[string]*Session
	subscriptions  map[string]map[string]*Session
	subscribers    map[string]map[string]bool
	lock           *sync.RWMutex
	loglevel       int
	bufferSize     int
	logger         *log.Logger
	authorizer     Authorizer
}

func NewServer() *Server {
	return &Server{
		heartbeat:      defaultHeartbeat,
		bufferSize:     defaultBufferSize,
		sessionTimeout: defaultSessionTimeout,
		loglevel:       defaultLoglevel,
		sessions:       map[string]*Session{},
		subscriptions:  map[string]map[string]*Session{},
		subscribers:    map[string]map[string]bool{},
		lock:           &sync.RWMutex{},
		logger:         log.New(os.Stdout, "pusher: ", 0),
	}
}

func (self *Server) Loglevel(i int) *Server {
	self.loglevel = i
	return self
}

func (self *Server) Logger(l *log.Logger) *Server {
	self.logger = l
	return self
}

func (self *Server) Fatalf(fmt string, i ...interface{}) {
	self.logger.Printf(fmt, i...)
}

func (self *Server) Errorf(fmt string, i ...interface{}) {
	if self.loglevel > 0 {
		self.logger.Printf(fmt, i...)
	}
}

func (self *Server) Infof(fmt string, i ...interface{}) {
	if self.loglevel > 1 {
		self.logger.Printf(fmt, i...)
	}
}

func (self *Server) Debugf(fmt string, i ...interface{}) {
	if self.loglevel > 2 {
		self.logger.Printf(fmt, i...)
	}
}

func (self *Server) Authorizer(f Authorizer) *Server {
	self.authorizer = f
	return self
}

func (self *Server) addSubscription(sess *Session, uri string) {
	self.lock.Lock()
	defer self.lock.Unlock()

	if _, found := self.subscriptions[uri]; !found {
		self.subscriptions[uri] = map[string]*Session{}
	}
	self.subscriptions[uri][sess.id] = sess

	if _, found := self.subscribers[sess.id]; !found {
		self.subscribers[sess.id] = map[string]bool{}
	}
	self.subscribers[sess.id][uri] = true

	self.Infof("%v\t-\t%v\t%v\t%v\t[subscribe]", time.Now(), uri, sess.RemoteAddr, sess.id)
}

func (self *Server) Emit(message Message) {
	self.lock.RLock()
	defer self.lock.RUnlock()

	sent := 0
	for _, sess := range self.subscriptions[message.URI] {
		sess.send(message)
		sent++
	}
	self.Debugf("%v\t%v\t[emitted] x %v", time.Now(), message.URI, sent)
}

func (self *Server) removeSubscription(id, uri string, withLocking bool) {
	if withLocking {
		self.lock.Lock()
		defer self.lock.Unlock()
	}

	delete(self.subscriptions[uri], id)
	if len(self.subscriptions[uri]) == 0 {
		delete(self.subscriptions, uri)
	}

	delete(self.subscribers[id], uri)
	if len(self.subscribers[id]) == 0 {
		delete(self.subscribers, id)
	}
	self.Infof("%v\t-\t%v\t%v\t-\t[unsubscribe]", time.Now(), uri, id)
}

func (self *Server) randomId() string {
	buf := make([]byte, idLength)
	for index, _ := range buf {
		buf[index] = byte(rand.Int31())
	}
	return (base64.URLEncoding.EncodeToString(buf))
}

func (self *Server) removeSession(id string) {
	self.lock.Lock()
	defer self.lock.Unlock()

	delete(self.sessions, id)

	for uri, _ := range self.subscribers[id] {
		self.removeSubscription(id, uri, false)
	}
	self.Infof("%v\t-\t[cleanup]\t%v", time.Now(), id)
}

func (self *Server) GetSession(id string) (result *Session) {
	self.lock.Lock()
	defer self.lock.Unlock()

	// if the id is not found (because the server restarted, or the client gave the initial empty id) we just create a new id and insert a new session
	if result = self.sessions[id]; result == nil {
		result = &Session{
			output:         make(chan Message, self.bufferSize),
			id:             self.randomId(),
			server:         self,
			authorizations: map[string]bool{},
			lock:           &sync.RWMutex{},
		}
		result.cleanupTimer = &time.Timer{}
		self.sessions[result.id] = result
	}
	return
}

func (self *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	self.Infof("%v\t%v\t%v\t%v", time.Now(), r.Method, r.URL, r.RemoteAddr)
	websocket.Handler(self.GetSession(r.URL.Query().Get("session_id")).handleWS).ServeHTTP(w, r)
}

type MessageType string
type ErrorType string

const (
	TypeError       = "Error"
	TypeHeartbeat   = "Heartbeat"
	TypeWelcome     = "Welcome"
	TypeSubscribe   = "Subscribe"
	TypeUnsubscribe = "Unsubscribe"
	TypeMessage     = "Message"
	TypeAuthorize   = "Authorize"
	TypeAck         = "Ack"
)

const (
	TypeJSONError          = "JSONError"
	TypeAuthorizationError = "AuthorizationError"
	TypeSyntaxError        = "SyntaxError"
)

type Welcome struct {
	Heartbeat      time.Duration
	SessionTimeout time.Duration
	Id             string
}

type Error struct {
	Message string
	Type    ErrorType
}

type Message struct {
	Type    MessageType
	Id      string      `json:",omitempty"`
	Welcome *Welcome    `json:",omitempty"`
	Error   *Error      `json:",omitempty"`
	Data    interface{} `json:",omitempty"`
	URI     string      `json:",omitempty"`
	Token   string      `json:",omitempty"`
	Write   bool        `json:",omitempty"`
}

type Session struct {
	ws             io.ReadWriteCloser
	id             string
	RemoteAddr     string
	input          chan Message
	output         chan Message
	closing        chan struct{}
	server         *Server
	cleanupTimer   *time.Timer
	authorizations map[string]bool
	lock           *sync.RWMutex
}

func (self *Session) parseMessage(b []byte) (result Message, err error) {
	err = json.Unmarshal(b, &result)
	return
}

func (self *Session) readLoop() {
	defer self.terminate()
	buf := make([]byte, bufLength)
	n, err := self.ws.Read(buf)
	for err == nil {
		if message, err := self.parseMessage(buf[:n]); err == nil {
			self.input <- message
			self.server.Debugf("%v\t%v\t%v\t%v\t[received from socket]", time.Now(), message.URI, self.RemoteAddr, self.id)
		} else {
			self.send(Message{
				Type: TypeError,
				Error: &Error{
					Message: err.Error(),
					Type:    TypeJSONError,
				},
				Data: string(buf[:n]),
			})
		}
		n, err = self.ws.Read(buf)
	}
}

func (self *Session) writeLoop() {
	defer self.terminate()
	var message Message
	var err error
	var n int
	var encoded []byte
	for {
		select {
		case message = <-self.output:
			if encoded, err = json.Marshal(message); err == nil {
				if n, err = self.ws.Write(encoded); err != nil {
					self.server.Fatalf("Error sending %s on %+v: %v", encoded, self.ws, err)
					return
				} else if n != len(encoded) {
					self.server.Fatalf("Unable to send all of %s on %+v: only sent %v bytes", encoded, self.ws, n)
					return
				}
			} else {
				self.server.Fatalf("Unable to JSON marshal %+v: %v", message, err)
				return
			}
			self.server.Debugf("%v\t%v\t%v\t%v\t[sent to socket]", time.Now(), message.URI, self.RemoteAddr, self.id)
		case <-self.closing:
			return
		}
	}
}

func (self *Session) heartbeatLoop() {
	for {
		select {
		case <-self.closing:
			return
		case <-time.After(self.server.heartbeat / 2):
			self.send(Message{Type: TypeHeartbeat})
		}
	}
}

func (self *Session) authorized(uri string, wantWrite bool) bool {
	if self.server.authorizer == nil {
		return true
	}
	self.lock.RLock()
	defer self.lock.RUnlock()
	hasWrite, found := self.authorizations[uri]
	if !found {
		return false
	}
	return !wantWrite || hasWrite
}

func (self *Session) send(message Message) {
	select {
	case self.output <- message:
	default:
		self.server.Errorf("Unable to send %+v to %+v, output buffer full", message, self)
	}
}

func (self *Session) handleMessage(message Message) {
	switch message.Type {
	case TypeHeartbeat:
	case TypeMessage:
		if !self.authorized(message.URI, true) {
			self.send(Message{
				Type: TypeError,
				Id:   message.Id,
				Error: &Error{
					Message: fmt.Sprintf("%v not authorized for writing to %v", self.id, message.URI),
					Type:    TypeAuthorizationError,
				},
				Data: message,
			})
			return
		}
		self.server.Emit(message)
	case TypeUnsubscribe:
		self.server.removeSubscription(self.id, message.URI, true)
	case TypeSubscribe:
		if !self.authorized(message.URI, false) {
			self.send(Message{
				Type: TypeError,
				Id:   message.Id,
				Error: &Error{
					Message: fmt.Sprintf("%v not authorized for subscribing to %v", self.id, message.URI),
					Type:    TypeAuthorizationError,
				},
				Data: message,
			})
			return
		}
		self.server.addSubscription(self, message.URI)
	case TypeAuthorize:
		if self.server.authorizer != nil {
			ok, err := self.server.authorizer(message.URI, message.Token, message.Write)
			if err != nil {
				self.send(Message{
					Type: TypeError,
					Id:   message.Id,
					Error: &Error{
						Message: err.Error(),
						Type:    TypeAuthorizationError,
					}, Data: message,
				})
				return
			}
			if !ok {
				self.send(Message{
					Type: TypeError,
					Id:   message.Id,
					Error: &Error{
						Message: fmt.Sprintf("%v does not provide authorization for %v", message.Token, message.URI),
						Type:    TypeAuthorizationError,
					}, Data: message,
				})
				return
			}
		}
		self.lock.Lock()
		defer self.lock.Unlock()
		self.authorizations[message.URI] = (self.authorizations[message.URI] || message.Write)
		self.server.Infof("%v\t-\t[authorize]\t%v\t%v\t%v", time.Now(), self.RemoteAddr, self.id, message.URI)
	default:
		self.send(Message{
			Type: TypeError,
			Id:   message.Id,
			Error: &Error{
				Message: fmt.Sprintf("Unknown message type %#v", message.Type),
				Type:    TypeSyntaxError,
			},
			Data: message,
		})
		return
	}
	if message.Id != "" {
		self.send(Message{
			Type: TypeAck,
			Id:   message.Id,
		})
	}

}

func (self *Session) remove() {
	self.server.removeSession(self.id)
}

func (self *Session) terminate() {
	self.ws.Close()
	select {
	case _ = <-self.closing:
	default:
		close(self.closing)
	}
	self.server.Infof("%v\t-\t[disconnect]\t%v\t%v", time.Now(), self.RemoteAddr, self.id)
	self.lock.Lock()
	defer self.lock.Unlock()
	self.cleanupTimer.Stop()
	self.cleanupTimer = time.AfterFunc(self.server.sessionTimeout, self.remove)
}

func (self *Session) handleWS(ws *websocket.Conn) {
	self.RemoteAddr = ws.Request().RemoteAddr
	self.Handle(ws)
}

/*
 * This function will work as the session main-loop listening on events
 * on a websocket or other source that interfaces io.ReadWriteCloser.
 */
func (self *Session) Handle(ws io.ReadWriteCloser) {
	self.server.Infof("%v\t-\t[connect]\t%v\t%v", time.Now(), self.RemoteAddr, self.id)

	defer self.terminate()

	self.ws = ws
	self.cleanupTimer.Stop()
	self.input = make(chan Message)
	self.closing = make(chan struct{})

	go self.readLoop()
	go self.writeLoop()
	go self.heartbeatLoop()

	self.send(Message{
		Type: TypeWelcome,
		Welcome: &Welcome{
			Heartbeat:      self.server.heartbeat / time.Millisecond,
			SessionTimeout: self.server.sessionTimeout / time.Millisecond,
			Id:             self.id,
		},
	})

	var message Message
	for {
		select {
		case _ = <-self.closing:
			return
		case message = <-self.input:
			self.handleMessage(message)
		case <-time.After(self.server.heartbeat):
			return
		}
	}
}
<commit_msg>made all hub.Session not explode if multiple sockets connect to it: one input channel stays for the entire session lifetime. the ws is only stored in the connection context (Session#Handle), as is the closing channel. added an atomic counter that stops the server from throwing out sessions just because ONE of the connections disconnected. added a log message for when the server fails to listen. NOTE: there is still no SUPPORT for multiple sockets to the same session, just a sort of graceful error state (the connections will likely share the messages among themselves).<commit_after>package hub

import (
	"code.google.com/p/go.net/websocket"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"os"
	"sync"
	"sync/atomic"
	"time"
)

const (
	defaultBufferSize     = 1024
	defaultHeartbeat      = time.Second * 5
	defaultSessionTimeout = time.Second * 30
	idLength              = 16
	bufLength             = 4096
	defaultLoglevel       = 1
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

type Authorizer func(uri, token string, write bool) (authorized bool, err error)

func prettify(obj interface{}) string {
	b, err := json.MarshalIndent(obj, "", "  ")
	if err != nil {
		panic(err)
	}
	return string(b)
}

type Server struct {
	heartbeat      time.Duration
	sessionTimeout time.Duration
	sessions       map[string]*Session
	subscriptions  map[string]map[string]*Session
	subscribers    map[string]map[string]bool
	lock           *sync.RWMutex
	loglevel       int
	bufferSize     int
	logger         *log.Logger
	authorizer     Authorizer
}

func NewServer() *Server {
	return &Server{
		heartbeat:      defaultHeartbeat,
		bufferSize:     defaultBufferSize,
		sessionTimeout: defaultSessionTimeout,
		loglevel:       defaultLoglevel,
		sessions:       map[string]*Session{},
		subscriptions:  map[string]map[string]*Session{},
		subscribers:    map[string]map[string]bool{},
		lock:           &sync.RWMutex{},
		logger:         log.New(os.Stdout, "pusher: ", 0),
	}
}

func (self *Server) Loglevel(i int) *Server {
	self.loglevel = i
	return self
}

func (self *Server) Logger(l *log.Logger) *Server {
	self.logger = l
	return self
}

func (self *Server) Fatalf(fmt string, i ...interface{}) {
	self.logger.Printf(fmt, i...)
}

func (self *Server) Errorf(fmt string, i ...interface{}) {
	if self.loglevel > 0 {
		self.logger.Printf(fmt, i...)
	}
}

func (self *Server) Infof(fmt string, i ...interface{}) {
	if self.loglevel > 1 {
		self.logger.Printf(fmt, i...)
	}
}

func (self *Server) Debugf(fmt string, i ...interface{}) {
	if self.loglevel > 2 {
		self.logger.Printf(fmt, i...)
	}
}

func (self *Server) Authorizer(f Authorizer) *Server {
	self.authorizer = f
	return self
}

func (self *Server) addSubscription(sess *Session, uri string) {
	self.lock.Lock()
	defer self.lock.Unlock()

	if _, found := self.subscriptions[uri]; !found {
		self.subscriptions[uri] = map[string]*Session{}
	}
	self.subscriptions[uri][sess.id] = sess

	if _, found := self.subscribers[sess.id]; !found {
		self.subscribers[sess.id] = map[string]bool{}
	}
	self.subscribers[sess.id][uri] = true

	self.Infof("%v\t-\t%v\t%v\t%v\t[subscribe]", time.Now(), uri, sess.RemoteAddr, sess.id)
}

func (self *Server) Emit(message Message) {
	self.lock.RLock()
	defer self.lock.RUnlock()

	sent := 0
	for _, sess := range self.subscriptions[message.URI] {
		sess.send(message)
		sent++
	}
	self.Debugf("%v\t%v\t[emitted] x %v", time.Now(), message.URI, sent)
}

func (self *Server) removeSubscription(id, uri string, withLocking bool) {
	if withLocking {
		self.lock.Lock()
		defer self.lock.Unlock()
	}

	delete(self.subscriptions[uri], id)
	if len(self.subscriptions[uri]) == 0 {
		delete(self.subscriptions, uri)
	}

	delete(self.subscribers[id], uri)
	if len(self.subscribers[id]) == 0 {
		delete(self.subscribers, id)
	}
	self.Infof("%v\t-\t%v\t%v\t-\t[unsubscribe]", time.Now(), uri, id)
}

func (self *Server) randomId() string {
	buf := make([]byte, idLength)
	for index, _ := range buf {
		buf[index] = byte(rand.Int31())
	}
	return (base64.URLEncoding.EncodeToString(buf))
}

func (self *Server) removeSession(id string) {
	self.lock.Lock()
	defer self.lock.Unlock()

	delete(self.sessions, id)

	for uri, _ := range self.subscribers[id] {
		self.removeSubscription(id, uri, false)
	}
	self.Infof("%v\t-\t[cleanup]\t%v", time.Now(), id)
}

func (self *Server) GetSession(id string) (result *Session) {
	self.lock.Lock()
	defer self.lock.Unlock()

	// if the id is not found (because the server restarted, or the client gave the initial empty id) we just create a new id and insert a new session
	if result = self.sessions[id]; result == nil {
		result = &Session{
			output:         make(chan Message, self.bufferSize),
			input:          make(chan Message),
			id:             self.randomId(),
			server:         self,
			authorizations: map[string]bool{},
			lock:           &sync.RWMutex{},
		}
		result.cleanupTimer = &time.Timer{}
		self.sessions[result.id] = result
	}
	return
}

func (self *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	self.Infof("%v\t%v\t%v\t%v", time.Now(), r.Method, r.URL, r.RemoteAddr)
	websocket.Handler(self.GetSession(r.URL.Query().Get("session_id")).handleWS).ServeHTTP(w, r)
}

type MessageType string
type ErrorType string

const (
	TypeError       = "Error"
	TypeHeartbeat   = "Heartbeat"
	TypeWelcome     = "Welcome"
	TypeSubscribe   = "Subscribe"
	TypeUnsubscribe = "Unsubscribe"
	TypeMessage     = "Message"
	TypeAuthorize   = "Authorize"
	TypeAck         = "Ack"
)

const (
	TypeJSONError          = "JSONError"
	TypeAuthorizationError = "AuthorizationError"
	TypeSyntaxError        = "SyntaxError"
)

type Welcome struct {
	Heartbeat      time.Duration
	SessionTimeout time.Duration
	Id             string
}

type Error struct {
	Message string
	Type    ErrorType
}

type Message struct {
	Type    MessageType
	Id      string      `json:",omitempty"`
	Welcome *Welcome    `json:",omitempty"`
	Error   *Error      `json:",omitempty"`
	Data    interface{} `json:",omitempty"`
	URI     string      `json:",omitempty"`
	Token   string      `json:",omitempty"`
	Write   bool        `json:",omitempty"`
}

type Session struct {
	id             string
	RemoteAddr     string
	input          chan Message
	output         chan Message
	server         *Server
	connections    int32
	cleanupTimer   *time.Timer
	authorizations map[string]bool
	lock           *sync.RWMutex
}

func (self *Session) parseMessage(b []byte) (result Message, err error) {
	err = json.Unmarshal(b, &result)
	return
}

func (self *Session) readLoop(closing chan struct{}, ws io.ReadWriteCloser) {
	defer self.terminate(closing, ws)
	buf := make([]byte, bufLength)
	n, err := ws.Read(buf)
	for err == nil {
		if message, err := self.parseMessage(buf[:n]); err == nil {
			self.input <- message
			self.server.Debugf("%v\t%v\t%v\t%v\t[received from socket]", time.Now(), message.URI, self.RemoteAddr, self.id)
		} else {
			self.send(Message{
				Type: TypeError,
				Error: &Error{
					Message: err.Error(),
					Type:    TypeJSONError,
				},
				Data: string(buf[:n]),
			})
		}
		n, err = ws.Read(buf)
	}
}

func (self *Session) writeLoop(closing chan struct{}, ws io.ReadWriteCloser) {
	defer self.terminate(closing, ws)
	var message Message
	var err error
	var n int
	var encoded []byte
	for {
		select {
		case message = <-self.output:
			if encoded, err = json.Marshal(message); err == nil {
				if n, err = ws.Write(encoded); err != nil {
					self.server.Fatalf("Error sending %s on %+v: %v", encoded, ws, err)
					return
				} else if n != len(encoded) {
					self.server.Fatalf("Unable to send all of %s on %+v: only sent %v bytes", encoded, ws, n)
					return
				}
			} else {
				self.server.Fatalf("Unable to JSON marshal %+v: %v", message, err)
				return
			}
			self.server.Debugf("%v\t%v\t%v\t%v\t[sent to socket]", time.Now(), message.URI, self.RemoteAddr, self.id)
		case <-closing:
			return
		}
	}
}

func (self *Session) heartbeatLoop(closing chan struct{}) {
	for {
		select {
		case <-closing:
			return
		case <-time.After(self.server.heartbeat / 2):
			self.send(Message{Type: TypeHeartbeat})
		}
	}
}

func (self *Session) authorized(uri string, wantWrite bool) bool {
	if self.server.authorizer == nil {
		return true
	}
	self.lock.RLock()
	defer self.lock.RUnlock()
	hasWrite, found := self.authorizations[uri]
	if !found {
		return false
	}
	return !wantWrite || hasWrite
}

func (self *Session) send(message Message) {
	select {
	case self.output <- message:
	default:
		self.server.Errorf("Unable to send %+v to %+v, output buffer full", message, self)
	}
}

func (self *Session) handleMessage(message Message) {
	switch message.Type {
	case TypeHeartbeat:
	case TypeMessage:
		if !self.authorized(message.URI, true) {
			self.send(Message{
				Type: TypeError,
				Id:   message.Id,
				Error: &Error{
					Message: fmt.Sprintf("%v not authorized for writing to %v", self.id, message.URI),
					Type:    TypeAuthorizationError,
				},
				Data: message,
			})
			return
		}
		self.server.Emit(message)
	case TypeUnsubscribe:
		self.server.removeSubscription(self.id, message.URI, true)
	case TypeSubscribe:
		if !self.authorized(message.URI, false) {
			self.send(Message{
				Type: TypeError,
				Id:   message.Id,
				Error: &Error{
					Message: fmt.Sprintf("%v not authorized for subscribing to %v", self.id, message.URI),
					Type:    TypeAuthorizationError,
				},
				Data: message,
			})
			return
		}
		self.server.addSubscription(self, message.URI)
	case TypeAuthorize:
		if self.server.authorizer != nil {
			ok, err := self.server.authorizer(message.URI, message.Token, message.Write)
			if err != nil {
				self.send(Message{
					Type: TypeError,
					Id:   message.Id,
					Error: &Error{
						Message: err.Error(),
						Type:    TypeAuthorizationError,
					}, Data: message,
				})
				return
			}
			if !ok {
				self.send(Message{
					Type: TypeError,
					Id:   message.Id,
					Error: &Error{
						Message: fmt.Sprintf("%v does not provide authorization for %v", message.Token, message.URI),
						Type:    TypeAuthorizationError,
					}, Data: message,
				})
				return
			}
		}
		self.lock.Lock()
		defer self.lock.Unlock()
		self.authorizations[message.URI] = (self.authorizations[message.URI] || message.Write)
		self.server.Infof("%v\t-\t[authorize]\t%v\t%v\t%v", time.Now(), self.RemoteAddr, self.id, message.URI)
	default:
		self.send(Message{
			Type: TypeError,
			Id:   message.Id,
			Error: &Error{
				Message: fmt.Sprintf("Unknown message type %#v", message.Type),
				Type:    TypeSyntaxError,
			},
			Data: message,
		})
		return
	}
	if message.Id != "" {
		self.send(Message{
			Type: TypeAck,
			Id:   message.Id,
		})
	}

}

func (self *Session) remove() {
	self.server.removeSession(self.id)
}

func (self *Session) terminate(closing chan struct{}, ws io.ReadWriteCloser) {
	self.lock.Lock()
	defer self.lock.Unlock()

	select {
	case _ = <-closing:
	default:
		close(closing)
	}

	self.server.Infof("%v\t-\t[disconnect]\t%v\t%v", time.Now(), self.RemoteAddr, self.id)

	ws.Close()
	self.cleanupTimer.Stop()
	if atomic.AddInt32(&self.connections, -1) == 0 {
		self.cleanupTimer = time.AfterFunc(self.server.sessionTimeout, self.remove)
	}
}

func (self *Session) handleWS(ws *websocket.Conn) {
	self.RemoteAddr = ws.Request().RemoteAddr
	self.Handle(ws)
}

/*
 * Handle works as the session main-loop listening on events
 * on a websocket or other source that implements io.ReadWriteCloser.
 */
func (self *Session) Handle(ws io.ReadWriteCloser) {
	self.server.Infof("%v\t-\t[connect]\t%v\t%v", time.Now(), self.RemoteAddr, self.id)

	closing := make(chan struct{})
	defer self.terminate(closing, ws)
	atomic.AddInt32(&self.connections, 1)

	self.cleanupTimer.Stop()

	go self.readLoop(closing, ws)
	go self.writeLoop(closing, ws)
	go self.heartbeatLoop(closing)

	self.send(Message{
		Type: TypeWelcome,
		Welcome: &Welcome{
			Heartbeat:      self.server.heartbeat / time.Millisecond,
			SessionTimeout: self.server.sessionTimeout / time.Millisecond,
			Id:             self.id,
		},
	})

	var message Message
	for {
		select {
		case _ = <-closing:
			return
		case message = <-self.input:
			self.handleMessage(message)
		case <-time.After(self.server.heartbeat):
			return
		}
	}
}
<|endoftext|>


<commit_before>package hue

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"runtime"
	"strings"
	"time"

	"github.com/eikeon/hue"
	"github.com/nogiushi/marvin/nog"
)

var Root = ""

func init() {
	_, filename, _, _ := runtime.Caller(0)
	Root = path.Dir(filename)
}

type Hue struct {
	Hue         hue.Hue
	Nouns       map[string]string
	States      map[string]interface{}
	Transitions map[string]struct {
		Switch   map[string]bool
		Commands []struct {
			Address string
			State   string
		}
	}
}

func (h *Hue) Run(in <-chan nog.Message, out chan<- nog.Message) {
	options := nog.BitOptions{Name: "Lights", Required: false}
	if what, err := json.Marshal(&options); err == nil {
		out <- nog.NewMessage("Lights", string(what), "register")
	} else {
		log.Println("StateChanged err:", err)
	}

	var createUserChan <-chan time.Time

	name := "hue.html"
	if j, err := os.OpenFile(path.Join(Root, name), os.O_RDONLY, 0666); err == nil {
		if b, err := ioutil.ReadAll(j); err == nil {
			out <- nog.NewMessage("Marvin", string(b), "template")
		} else {
			log.Println("ERROR reading:", err)
		}
	} else {
		log.Println("WARNING: could not open ", name, err)
	}

	for {
		select {
		case <-createUserChan:
			if err := h.Hue.CreateUser(h.Hue.Username, "Marvin"); err == nil {
				createUserChan.Stop()
			} else {
				out <- nog.NewMessage("Marvin", fmt.Sprintf("%s: press hue link button to authenticate", err), "Lights")
			}
		case m := <-in:
			if m.Why == "statechanged" {
				dec := json.NewDecoder(strings.NewReader(m.What))
				if err := dec.Decode(h); err != nil {
					log.Println("hue decode err:", err)
				}
				if createUserChan == nil {
					if err := h.Hue.GetState(); err != nil {
						createUserChan = time.NewTicker(1 * time.Second).C
					} else {
						// TODO:
					}
				}
			}
			const SETHUE = "set hue address "
			if strings.HasPrefix(m.What, SETHUE) {
				words := strings.Split(m.What[len(SETHUE):], " ")
				if len(words) == 3 {
					address := words[0]
					state := words[2]
					var s interface{}
					dec := json.NewDecoder(strings.NewReader(state))
					if err := dec.Decode(&s); err != nil {
						log.Println("json decode err:", err)
					} else {
						h.Hue.Set(address, s)
						err := h.Hue.GetState()
						if err != nil {
							log.Println("ERROR:", err)
						}
						if what, err := json.Marshal(h); err == nil {
							out <- nog.NewMessage("Marvin", string(what), "statechanged")
						} else {
							log.Println("StateChanged err:", err)
						}
					}
				} else {
					log.Println("unexpected number of words in:", m)
				}
			}
			const SET = "set light "
			if strings.HasPrefix(m.What, SET) {
				e := strings.SplitN(m.What[len(SET):], " to ", 2)
				if len(e) == 2 {
					address := h.Nouns[e[0]]
					state := h.States[e[1]]
					if strings.Contains(address, "/light") {
						address += "/state"
					} else {
						address += "/action"
					}
					h.Hue.Set(address, state)
					err := h.Hue.GetState()
					if err != nil {
						log.Println("ERROR:", err)
					}
					if what, err := json.Marshal(h); err == nil {
						out <- nog.NewMessage("Marvin", string(what), "statechanged")
					} else {
						log.Println("StateChanged err:", err)
					}
				} else {
					log.Println("unexpected number of words in:", m)
				}
			}
		}
	}
}
<commit_msg>Only kick off createUser once.<commit_after>package hue

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"runtime"
	"strings"
	"time"

	"github.com/eikeon/hue"
	"github.com/nogiushi/marvin/nog"
)

var Root = ""

func init() {
	_, filename, _, _ := runtime.Caller(0)
	Root = path.Dir(filename)
}

type Hue struct {
	Hue         hue.Hue
	Nouns       map[string]string
	States      map[string]interface{}
	Transitions map[string]struct {
		Switch   map[string]bool
		Commands []struct {
			Address string
			State   string
		}
	}
}

func (h *Hue) Run(in <-chan nog.Message, out chan<- nog.Message) {
	options := nog.BitOptions{Name: "Lights", Required: false}
	if what, err := json.Marshal(&options); err == nil {
		out <- nog.NewMessage("Lights", string(what), "register")
	} else {
		log.Println("StateChanged err:", err)
	}

	var createUserChan <-chan time.Time

	name := "hue.html"
	if j, err := os.OpenFile(path.Join(Root, name), os.O_RDONLY, 0666); err == nil {
		if b, err := ioutil.ReadAll(j); err == nil {
			out <- nog.NewMessage("Marvin", string(b), "template")
		} else {
			log.Println("ERROR reading:", err)
		}
	} else {
		log.Println("WARNING: could not open ", name, err)
	}

	first := true
	for {
		select {
		case <-createUserChan:
			if err := h.Hue.CreateUser(h.Hue.Username, "Marvin"); err == nil {
				createUserChan = nil
			} else {
				out <- nog.NewMessage("Marvin", fmt.Sprintf("%s: press hue link button to authenticate", err), "Lights")
			}
		case m := <-in:
			if m.Why == "statechanged" {
				dec := json.NewDecoder(strings.NewReader(m.What))
				if err := dec.Decode(h); err != nil {
					log.Println("hue decode err:", err)
				}
				if first {
					first = false
					if err := h.Hue.GetState(); err != nil {
						createUserChan = time.NewTicker(1 * time.Second).C
					} else {
						// TODO:
					}
				}
			}
			const SETHUE = "set hue address "
			if strings.HasPrefix(m.What, SETHUE) {
				words := strings.Split(m.What[len(SETHUE):], " ")
				if len(words) == 3 {
					address := words[0]
					state := words[2]
					var s interface{}
					dec := json.NewDecoder(strings.NewReader(state))
					if err := dec.Decode(&s); err != nil {
						log.Println("json decode err:", err)
					} else {
						h.Hue.Set(address, s)
						err := h.Hue.GetState()
						if err != nil {
							log.Println("ERROR:", err)
						}
						if what, err := json.Marshal(h); err == nil {
							out <- nog.NewMessage("Marvin", string(what), "statechanged")
						} else {
							log.Println("StateChanged err:", err)
						}
					}
				} else {
					log.Println("unexpected number of words in:", m)
				}
			}
			const SET = "set light "
			if strings.HasPrefix(m.What, SET) {
				e := strings.SplitN(m.What[len(SET):], " to ", 2)
				if len(e) == 2 {
					address := h.Nouns[e[0]]
					state := h.States[e[1]]
					if strings.Contains(address, "/light") {
						address += "/state"
					} else {
						address += "/action"
					}
					h.Hue.Set(address, state)
					err := h.Hue.GetState()
					if err != nil {
						log.Println("ERROR:", err)
					}
					if what, err := json.Marshal(h); err == nil {
						out <- nog.NewMessage("Marvin", string(what), "statechanged")
					} else {
						log.Println("StateChanged err:", err)
					}
				} else {
					log.Println("unexpected number of words in:", m)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package log

import "math"

func Log2(x float64) int  {

answer := math.Log2(x)
var intAnswer int = int(answer)

return intAnswer

}

func Log10(x float64) int {

  answer := math.Log10(x)
  var intAnswer int = int(answer)

  return intAnswer
}
<commit_msg>Removed the int converting and changed return in log to float64 for irational numbers.<commit_after>package log

import "math"

func Log2(x float64) float64  {

answer := math.Log2(x)
//var intAnswer int = int(answer)

return answer

}

func Log10(x float64) float64 {

  answer := math.Log10(x)
  //var intAnswer int = int(answer)

  return answer
}
<|endoftext|>


<commit_before>package mysql

import (
	"crypto/md5"
	"fmt"
	"strings"
	"testing"

	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/mpool"
)

var intern = mpool.NewIntern()

func kafkaTopicWithStrConcat(m *mysqlStore, appid string, topic string, ver string) string {
	return appid + "." + topic + "." + ver
}

func kafkaTopicWithSprintf(m *mysqlStore, appid string, topic string, ver string) string {
	return fmt.Sprintf("%s.%s.%s", appid, topic, ver)
}

func kafkaTopicWithStringsJoin(m *mysqlStore, appid string, topic string, ver string) string {
	return strings.Join([]string{appid, topic, ver}, ".")
}

func kafkaTopicWithIntern(appid string, topic string, ver string) string {
	return intern.String(appid + "." + topic + "." + ver)
}

// 456 ns/op	      64 B/op	       4 allocs/op
func BenchmarkKafkaTopicWithStringsJoin(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		kafkaTopicWithStringsJoin(m, "appid", "topic", "ver")
	}
}

// 456 ns/op	      64 B/op	       4 allocs/op
func BenchmarkKafkaTopicWithSprintf(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		kafkaTopicWithSprintf(m, "appid", "topic", "ver")
	}
}

// 145 ns/op	      16 B/op	       1 allocs/op
func BenchmarkKafkaTopicWithMpool(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		m.KafkaTopic("appid", "topic", "v1")
	}
}

// 145 ns/op	      16 B/op	       1 allocs/op
func BenchmarkKafkaTopicObfuscationWithMpool(b *testing.B) {
	ctx.LoadFromHome()
	m := New(DefaultConfig("local"))
	for i := 0; i < b.N; i++ {
		m.KafkaTopic("appid", "topic", "v10")
	}
}

// 322 ns/op
func BenchmarkMd5Sum(b *testing.B) {
	m := md5.New()
	app := "app1"
	topic := "asdfasdfasfa"
	for i := 0; i < b.N; i++ {
		m.Sum([]byte(app + topic))
	}
}

func BenchmarkKafkaTopicWithIntern(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = kafkaTopicWithIntern("appid", "topic", "ver")
	}
}

// 74.4 ns/op	       0 B/op	       0 allocs/op
func BenchmarkKafkaTopicWithStrConcat(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		_ = kafkaTopicWithStrConcat(m, "appid", "topic", "ver")
	}
}

func BenchmarkKafkaTopic(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		m.KafkaTopic("appid", "topic", "v1")
	}
}

func BenchmarkKafkaTopicWithObfuscation(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		m.KafkaTopic("appid", "topic", "v10")
	}
}

// 46.1 ns/op
func BenchmarkValidateGroupName(b *testing.B) {
	m := mysqlStore{}
	for i := 0; i < b.N; i++ {
		m.ValidateGroupName(nil, "asdfasdf-1")
	}
}

// 837 ns/op
func BenchmarkValidateTopicName(b *testing.B) {
	m := mysqlStore{}
	for i := 0; i < b.N; i++ {
		m.ValidateTopicName("asdfasdf-1")
	}
}
<commit_msg>benchmark of strings.Join<commit_after>package mysql

import (
	"crypto/md5"
	"fmt"
	"strings"
	"testing"

	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/mpool"
)

var intern = mpool.NewIntern()

func kafkaTopicWithStrConcat(m *mysqlStore, appid string, topic string, ver string) string {
	return appid + "." + topic + "." + ver
}

func kafkaTopicWithSprintf(m *mysqlStore, appid string, topic string, ver string) string {
	return fmt.Sprintf("%s.%s.%s", appid, topic, ver)
}

func kafkaTopicWithStringsJoin(m *mysqlStore, appid string, topic string, ver string) string {
	return strings.Join([]string{appid, topic, ver}, ".")
}

func kafkaTopicWithIntern(appid string, topic string, ver string) string {
	return intern.String(appid + "." + topic + "." + ver)
}

func kafkaTopicWithJoin(appid string, topic string, ver string) string {
	const dot = "."
	return strings.Join([]string{appid, dot, topic, dot, ver}, "")
}

// 456 ns/op	      64 B/op	       4 allocs/op
func BenchmarkKafkaTopicWithStringsJoin(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		kafkaTopicWithStringsJoin(m, "appid", "topic", "ver")
	}
}

// 456 ns/op	      64 B/op	       4 allocs/op
func BenchmarkKafkaTopicWithSprintf(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		kafkaTopicWithSprintf(m, "appid", "topic", "ver")
	}
}

// 145 ns/op	      16 B/op	       1 allocs/op
func BenchmarkKafkaTopicWithMpool(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		m.KafkaTopic("appid", "topic", "v1")
	}
}

//
func BenchmarkKafkaTopicWithJoin(b *testing.B) {
	for i := 0; i < b.N; i++ {
		kafkaTopicWithJoin("appid", "topic", "ver")
	}
}

// 145 ns/op	      16 B/op	       1 allocs/op
func BenchmarkKafkaTopicObfuscationWithMpool(b *testing.B) {
	ctx.LoadFromHome()
	m := New(DefaultConfig("local"))
	for i := 0; i < b.N; i++ {
		m.KafkaTopic("appid", "topic", "v10")
	}
}

// 322 ns/op
func BenchmarkMd5Sum(b *testing.B) {
	m := md5.New()
	app := "app1"
	topic := "asdfasdfasfa"
	for i := 0; i < b.N; i++ {
		m.Sum([]byte(app + topic))
	}
}

func BenchmarkKafkaTopicWithIntern(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = kafkaTopicWithIntern("appid", "topic", "ver")
	}
}

// 74.4 ns/op	       0 B/op	       0 allocs/op
func BenchmarkKafkaTopicWithStrConcat(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		x := kafkaTopicWithStrConcat(m, "appid", "topic", "ver")
		if len(x) < 5 {
		}
	}
}

func BenchmarkKafkaTopic(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		m.KafkaTopic("appid", "topic", "v1")
	}
}

func BenchmarkKafkaTopicWithObfuscation(b *testing.B) {
	m := &mysqlStore{}
	for i := 0; i < b.N; i++ {
		m.KafkaTopic("appid", "topic", "v10")
	}
}

// 46.1 ns/op
func BenchmarkValidateGroupName(b *testing.B) {
	m := mysqlStore{}
	for i := 0; i < b.N; i++ {
		m.ValidateGroupName(nil, "asdfasdf-1")
	}
}

// 837 ns/op
func BenchmarkValidateTopicName(b *testing.B) {
	m := mysqlStore{}
	for i := 0; i < b.N; i++ {
		m.ValidateTopicName("asdfasdf-1")
	}
}
<|endoftext|>


<commit_before>// vagrant implements the packer.PostProcessor interface and adds a
// post-processor that turns artifacts of known builders into Vagrant
// boxes.
package vagrant

import (
	"fmt"
	"github.com/mitchellh/mapstructure"
	"github.com/mitchellh/packer/packer"
	"log"
)

var builtins = map[string]string{
	"mitchellh.amazonebs": "aws",
}

type Config struct {
	OutputPath string `mapstructure:"output"`

}

type PostProcessor struct {
	config Config
	premade map[string]packer.PostProcessor
}

func (p *PostProcessor) Configure(raw interface{}) error {
	err := mapstructure.Decode(raw, &p.config)
	if err != nil {
		return err
	}

	if p.config.OutputPath == "" {
		return fmt.Errorf("`output` must be specified.")
	}

	mapConfig, ok := raw.(map[string]interface{})
	if !ok {
		panic("Raw configuration not a map")
	}

	errors := make([]error, 0)
	for k, raw := range mapConfig {
		pp := keyToPostProcessor(k)
		if pp == nil {
			continue
		}

		if err := pp.Configure(raw); err != nil {
			errors = append(errors, err)
		}

		p.premade[k] = pp
	}

	if len(errors) > 0 {
		return &packer.MultiError{errors}
	}

	return nil
}

func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
	ppName, ok := builtins[artifact.BuilderId()]
	if !ok {
		return nil, fmt.Errorf("Unknown artifact type, can't build box: %s", artifact.BuilderId())
	}

	// Use the premade PostProcessor if we have one. Otherwise, we
	// create it and configure it here.
	pp, ok := p.premade[ppName]
	if !ok {
		log.Printf("Premade post-processor for '%s' not found. Creating.", ppName)
		pp = keyToPostProcessor(ppName)
		if pp == nil {
			return nil, fmt.Errorf("Vagrant box post-processor not found: %s", ppName)
		}

		config := map[string]string{"output": p.config.OutputPath}
		if err := pp.Configure(config); err != nil {
			return nil, err
		}
	}

	return pp.PostProcess(ui, artifact)
}

func keyToPostProcessor(key string) packer.PostProcessor {
	switch key {
	case "aws":
		return new(AWSBoxPostProcessor)
	default:
		return nil
	}
}
<commit_msg>post-processor/vagrant: more Ui output<commit_after>// vagrant implements the packer.PostProcessor interface and adds a
// post-processor that turns artifacts of known builders into Vagrant
// boxes.
package vagrant

import (
	"fmt"
	"github.com/mitchellh/mapstructure"
	"github.com/mitchellh/packer/packer"
	"log"
)

var builtins = map[string]string{
	"mitchellh.amazonebs": "aws",
}

type Config struct {
	OutputPath string `mapstructure:"output"`

}

type PostProcessor struct {
	config Config
	premade map[string]packer.PostProcessor
}

func (p *PostProcessor) Configure(raw interface{}) error {
	err := mapstructure.Decode(raw, &p.config)
	if err != nil {
		return err
	}

	if p.config.OutputPath == "" {
		return fmt.Errorf("`output` must be specified.")
	}

	mapConfig, ok := raw.(map[string]interface{})
	if !ok {
		panic("Raw configuration not a map")
	}

	errors := make([]error, 0)
	for k, raw := range mapConfig {
		pp := keyToPostProcessor(k)
		if pp == nil {
			continue
		}

		if err := pp.Configure(raw); err != nil {
			errors = append(errors, err)
		}

		p.premade[k] = pp
	}

	if len(errors) > 0 {
		return &packer.MultiError{errors}
	}

	return nil
}

func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
	ppName, ok := builtins[artifact.BuilderId()]
	if !ok {
		return nil, fmt.Errorf("Unknown artifact type, can't build box: %s", artifact.BuilderId())
	}

	// Use the premade PostProcessor if we have one. Otherwise, we
	// create it and configure it here.
	pp, ok := p.premade[ppName]
	if !ok {
		log.Printf("Premade post-processor for '%s' not found. Creating.", ppName)
		pp = keyToPostProcessor(ppName)
		if pp == nil {
			return nil, fmt.Errorf("Vagrant box post-processor not found: %s", ppName)
		}

		config := map[string]string{"output": p.config.OutputPath}
		if err := pp.Configure(config); err != nil {
			return nil, err
		}
	}

	ui.Say(fmt.Sprintf("Creating Vagrant box for '%s' provider", ppName))
	return pp.PostProcess(ui, artifact)
}

func keyToPostProcessor(key string) packer.PostProcessor {
	switch key {
	case "aws":
		return new(AWSBoxPostProcessor)
	default:
		return nil
	}
}
<|endoftext|>


<commit_before>package collectors

import (
	"bufio"
	"os"
	"strings"
	"sync"
	"time"
	"unicode"
	"unicode/utf8"

	"github.com/bosun-monitor/scollector/metadata"
	"github.com/bosun-monitor/scollector/opentsdb"
	"github.com/bosun-monitor/scollector/util"
)

var collectors []Collector

type Collector interface {
	Run(chan<- *opentsdb.DataPoint)
	Name() string
	Init()
}

const (
	osCPU              = "os.cpu"
	osDiskFree         = "os.disk.fs.space_free"
	osDiskPctFree      = "os.disk.fs.percent_free"
	osDiskTotal        = "os.disk.fs.space_total"
	osDiskUsed         = "os.disk.fs.space_used"
	osMemFree          = "os.mem.free"
	osMemFreeDesc      = "The percent of free memory. In Linux free memory includes memory used by buffers and cache."
	osMemPctFree       = "os.mem.percent_free"
	osMemTotal         = "os.mem.total"
	osMemUsed          = "os.mem.used"
	osMemUsedDesc      = "The amount of used memory. In Linux this excludes memory used by buffers and cache."
	osNetBroadcast     = "os.net.packets_broadcast"
	osNetBytes         = "os.net.bytes"
	osNetBytesDesc     = "The rate at which bytes are sent or received over each network adapter."
	osNetDropped       = "os.net.dropped"
	osNetDroppedDesc   = "The number of packets that were chosen to be discarded even though no errors had been detected to prevent transmission."
	osNetErrors        = "os.net.errs"
	osNetErrorsDesc    = "The number of packets that could not be transmitted because of errors."
	osNetPackets       = "os.net.packets"
	osNetPacketsDesc   = "The rate at which packets are sent or received on the network interface."
	osNetUnicast       = "os.net.packets_unicast"
	osNetMulticast     = "os.net.packets_multicast"
	osSystemUptime     = "os.system.uptime"
	osSystemUptimeDesc = "Seconds since last reboot."
)

var (
	// DefaultFreq is the duration between collection intervals if none is
	// specified.
	DefaultFreq = time.Second * 15

	timestamp = time.Now().Unix()
	tlock     sync.Mutex
)

func init() {
	go func() {
		for t := range time.Tick(time.Second) {
			tlock.Lock()
			timestamp = t.Unix()
			tlock.Unlock()
		}
	}()
}

func now() (t int64) {
	tlock.Lock()
	t = timestamp
	tlock.Unlock()
	return
}

// Search returns all collectors matching the pattern s.
func Search(s string) []Collector {
	var r []Collector
	for _, c := range collectors {
		if strings.Contains(c.Name(), s) {
			r = append(r, c)
		}
	}
	return r
}

// Runs specified collectors. Use nil for all collectors.
func Run(cs []Collector) chan *opentsdb.DataPoint {
	if cs == nil {
		cs = collectors
	}
	ch := make(chan *opentsdb.DataPoint)
	for _, c := range cs {
		go c.Run(ch)
	}
	return ch
}

// AddTS is the same as Add but lets you specify the timestamp
func AddTS(md *opentsdb.MultiDataPoint, name string, ts int64, value interface{}, t opentsdb.TagSet, rate metadata.RateType, unit metadata.Unit, desc string) {
	tags := make(opentsdb.TagSet)
	for k, v := range t {
		tags[k] = v
	}
	if host, present := tags["host"]; !present {
		tags["host"] = util.Hostname
	} else if host == "" {
		delete(tags, "host")
	}
	d := opentsdb.DataPoint{
		Metric:    name,
		Timestamp: ts,
		Value:     value,
		Tags:      tags,
	}
	*md = append(*md, &d)
	if rate != metadata.Unknown {
		metadata.AddMeta(name, nil, "rate", rate, false)
	}
	if unit != metadata.None {
		metadata.AddMeta(name, nil, "unit", unit, false)
	}
	if desc != "" {
		metadata.AddMeta(name, tags, "desc", desc, false)
	}
}

// Add appends a new data point with given metric name, value, and tags. Tags
// may be nil. If tags is nil or does not contain a host key, it will be
// automatically added. If the value of the host key is the empty string, it
// will be removed (use this to prevent the normal auto-adding of the host tag).
func Add(md *opentsdb.MultiDataPoint, name string, value interface{}, t opentsdb.TagSet, rate metadata.RateType, unit metadata.Unit, desc string) {
	AddTS(md, name, now(), value, t, rate, unit, desc)
}

func readLine(fname string, line func(string) error) error {
	f, err := os.Open(fname)
	if err != nil {
		return err
	}
	defer f.Close()
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		if err := line(scanner.Text()); err != nil {
			return err
		}
	}
	return scanner.Err()
}

// IsDigit returns true if s consists of decimal digits.
func IsDigit(s string) bool {
	r := strings.NewReader(s)
	for {
		ch, _, err := r.ReadRune()
		if ch == 0 || err != nil {
			break
		} else if ch == utf8.RuneError {
			return false
		} else if !unicode.IsDigit(ch) {
			return false
		}
	}
	return true
}

// IsAlNum returns true if s is alphanumeric.
func IsAlNum(s string) bool {
	r := strings.NewReader(s)
	for {
		ch, _, err := r.ReadRune()
		if ch == 0 || err != nil {
			break
		} else if ch == utf8.RuneError {
			return false
		} else if !unicode.IsDigit(ch) && !unicode.IsLetter(ch) {
			return false
		}
	}
	return true
}

func TSys100NStoEpoch(nsec uint64) int64 {
	nsec -= 116444736000000000
	seconds := nsec / 1e7
	return int64(seconds)
}
<commit_msg>cmd/scollector: Set description metadata before host tag is added<commit_after>package collectors

import (
	"bufio"
	"os"
	"strings"
	"sync"
	"time"
	"unicode"
	"unicode/utf8"

	"github.com/bosun-monitor/scollector/metadata"
	"github.com/bosun-monitor/scollector/opentsdb"
	"github.com/bosun-monitor/scollector/util"
)

var collectors []Collector

type Collector interface {
	Run(chan<- *opentsdb.DataPoint)
	Name() string
	Init()
}

const (
	osCPU              = "os.cpu"
	osDiskFree         = "os.disk.fs.space_free"
	osDiskPctFree      = "os.disk.fs.percent_free"
	osDiskTotal        = "os.disk.fs.space_total"
	osDiskUsed         = "os.disk.fs.space_used"
	osMemFree          = "os.mem.free"
	osMemFreeDesc      = "The percent of free memory. In Linux free memory includes memory used by buffers and cache."
	osMemPctFree       = "os.mem.percent_free"
	osMemTotal         = "os.mem.total"
	osMemUsed          = "os.mem.used"
	osMemUsedDesc      = "The amount of used memory. In Linux this excludes memory used by buffers and cache."
	osNetBroadcast     = "os.net.packets_broadcast"
	osNetBytes         = "os.net.bytes"
	osNetBytesDesc     = "The rate at which bytes are sent or received over each network adapter."
	osNetDropped       = "os.net.dropped"
	osNetDroppedDesc   = "The number of packets that were chosen to be discarded even though no errors had been detected to prevent transmission."
	osNetErrors        = "os.net.errs"
	osNetErrorsDesc    = "The number of packets that could not be transmitted because of errors."
	osNetPackets       = "os.net.packets"
	osNetPacketsDesc   = "The rate at which packets are sent or received on the network interface."
	osNetUnicast       = "os.net.packets_unicast"
	osNetMulticast     = "os.net.packets_multicast"
	osSystemUptime     = "os.system.uptime"
	osSystemUptimeDesc = "Seconds since last reboot."
)

var (
	// DefaultFreq is the duration between collection intervals if none is
	// specified.
	DefaultFreq = time.Second * 15

	timestamp = time.Now().Unix()
	tlock     sync.Mutex
)

func init() {
	go func() {
		for t := range time.Tick(time.Second) {
			tlock.Lock()
			timestamp = t.Unix()
			tlock.Unlock()
		}
	}()
}

func now() (t int64) {
	tlock.Lock()
	t = timestamp
	tlock.Unlock()
	return
}

// Search returns all collectors matching the pattern s.
func Search(s string) []Collector {
	var r []Collector
	for _, c := range collectors {
		if strings.Contains(c.Name(), s) {
			r = append(r, c)
		}
	}
	return r
}

// Runs specified collectors. Use nil for all collectors.
func Run(cs []Collector) chan *opentsdb.DataPoint {
	if cs == nil {
		cs = collectors
	}
	ch := make(chan *opentsdb.DataPoint)
	for _, c := range cs {
		go c.Run(ch)
	}
	return ch
}

// AddTS is the same as Add but lets you specify the timestamp
func AddTS(md *opentsdb.MultiDataPoint, name string, ts int64, value interface{}, t opentsdb.TagSet, rate metadata.RateType, unit metadata.Unit, desc string) {
	tags := t.Copy()
	if rate != metadata.Unknown {
		metadata.AddMeta(name, nil, "rate", rate, false)
	}
	if unit != metadata.None {
		metadata.AddMeta(name, nil, "unit", unit, false)
	}
	if desc != "" {
		metadata.AddMeta(name, tags, "desc", desc, false)
	}
	if host, present := tags["host"]; !present {
		tags["host"] = util.Hostname
	} else if host == "" {
		delete(tags, "host")
	}
	d := opentsdb.DataPoint{
		Metric:    name,
		Timestamp: ts,
		Value:     value,
		Tags:      tags,
	}
	*md = append(*md, &d)
}

// Add appends a new data point with given metric name, value, and tags. Tags
// may be nil. If tags is nil or does not contain a host key, it will be
// automatically added. If the value of the host key is the empty string, it
// will be removed (use this to prevent the normal auto-adding of the host tag).
func Add(md *opentsdb.MultiDataPoint, name string, value interface{}, t opentsdb.TagSet, rate metadata.RateType, unit metadata.Unit, desc string) {
	AddTS(md, name, now(), value, t, rate, unit, desc)
}

func readLine(fname string, line func(string) error) error {
	f, err := os.Open(fname)
	if err != nil {
		return err
	}
	defer f.Close()
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		if err := line(scanner.Text()); err != nil {
			return err
		}
	}
	return scanner.Err()
}

// IsDigit returns true if s consists of decimal digits.
func IsDigit(s string) bool {
	r := strings.NewReader(s)
	for {
		ch, _, err := r.ReadRune()
		if ch == 0 || err != nil {
			break
		} else if ch == utf8.RuneError {
			return false
		} else if !unicode.IsDigit(ch) {
			return false
		}
	}
	return true
}

// IsAlNum returns true if s is alphanumeric.
func IsAlNum(s string) bool {
	r := strings.NewReader(s)
	for {
		ch, _, err := r.ReadRune()
		if ch == 0 || err != nil {
			break
		} else if ch == utf8.RuneError {
			return false
		} else if !unicode.IsDigit(ch) && !unicode.IsLetter(ch) {
			return false
		}
	}
	return true
}

func TSys100NStoEpoch(nsec uint64) int64 {
	nsec -= 116444736000000000
	seconds := nsec / 1e7
	return int64(seconds)
}
<|endoftext|>


<commit_before>package failures

import (
	"context"
	"fmt"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	swarmingv1 "go.chromium.org/luci/common/api/swarming/swarming/v1"
	httpmock "gopkg.in/jarcoal/httpmock.v1"

	"go.skia.org/infra/go/ds/testutil"
	"go.skia.org/infra/go/git"
	git_testutils "go.skia.org/infra/go/git/testutils"
	"go.skia.org/infra/go/swarming"
	"go.skia.org/infra/go/testutils"
	"go.skia.org/infra/predict/go/dsconst"
)

var (
	hash1 = ""
	hash2 = ""
	hash3 = ""
)

func badbot(botname string, ts time.Time) bool {
	return botname == "bot-bad"
}

func taskListProvider(since time.Duration) ([]*swarmingv1.SwarmingRpcsTaskRequestMetadata, error) {
	now := time.Now().UTC()
	return []*swarmingv1.SwarmingRpcsTaskRequestMetadata{
		&swarmingv1.SwarmingRpcsTaskRequestMetadata{
			TaskResult: &swarmingv1.SwarmingRpcsTaskResult{
				Tags: []string{
					"sk_issue_server:https://skia-review.googlesource.com",
					"sk_issue:82041",
					"sk_patchset:1",
					"sk_name:Test-Win10",
					"sk_repo:https://skia.googlesource.com/skia.git",
				},
				StartedTs: now.Add(-10 * time.Minute).Format(swarming.TIMESTAMP_FORMAT),
			},
		},
		&swarmingv1.SwarmingRpcsTaskRequestMetadata{
			TaskResult: &swarmingv1.SwarmingRpcsTaskResult{
				Tags: []string{
					fmt.Sprintf("sk_revision:%s", hash1),
					"sk_name:Test-Linux",
					"sk_repo:https://skia.googlesource.com/skia.git",
				},
				StartedTs: now.Add(-1 * time.Minute).Format(swarming.TIMESTAMP_FORMAT),
			},
		},
		// The following should be ignored.
		&swarmingv1.SwarmingRpcsTaskRequestMetadata{
			TaskResult: &swarmingv1.SwarmingRpcsTaskResult{
				Tags: []string{
					"sk_revision:blahblahblah", // Unknown git hash.
					"sk_name:Test-Linux",
					"sk_repo:https://skia.googlesource.com/skia.git",
				},
				StartedTs: now.Add(-1 * time.Minute).Format(swarming.TIMESTAMP_FORMAT),
			},
		},
		&swarmingv1.SwarmingRpcsTaskRequestMetadata{
			TaskResult: &swarmingv1.SwarmingRpcsTaskResult{
				Tags: []string{
					fmt.Sprintf("sk_revision:%s", hash1),
					"sk_name:bot-bad", // Should be filtered out by badbot().
					"sk_repo:https://skia.googlesource.com/skia.git",
				},
				StartedTs: now.Add(-1 * time.Minute).Format(swarming.TIMESTAMP_FORMAT),
			},
		},
		&swarmingv1.SwarmingRpcsTaskRequestMetadata{
			TaskResult: &swarmingv1.SwarmingRpcsTaskResult{
				Tags: []string{
					fmt.Sprintf("sk_revision:%s", hash1),
					"sk_name:Upload-Some-Test-Results", // Should be filtered out since it's an upload task.
					"sk_repo:https://skia.googlesource.com/skia.git",
				},
				StartedTs: now.Add(-1 * time.Minute).Format(swarming.TIMESTAMP_FORMAT),
			},
		},
	}, nil
}

func TestStore(t *testing.T) {
	testutils.MediumTest(t)

	now := time.Now()

	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder("GET", "https://skia-review.googlesource.com/changes/82041/revisions/1/files/",
		httpmock.NewStringResponder(200, `)]}' {"somefile.txt":{}}`))

	cleanup := testutil.InitDatastore(t, dsconst.FLAKY_RANGES)
	defer cleanup()

	ctx := context.Background()
	g := git_testutils.GitInit(t, ctx)
	defer g.Cleanup()

	hash1 = g.CommitGen(ctx, "README.md")
	hash2 = g.CommitGen(ctx, "README.md")
	hash3 = g.CommitGen(ctx, "INSTALL.md")
	git := &git.Checkout{
		GitDir: git.GitDir(g.Dir()),
	}
	fs := New(badbot, taskListProvider, git, http.DefaultClient, "https://skia.googlesource.com/skia.git")
	f, err := fs.List(ctx, now.Add(-1*time.Hour), now)
	assert.NoError(t, err)
	assert.Len(t, f, 0)

	err = fs.Update(ctx, time.Hour)
	assert.NoError(t, err)

	f, err = fs.List(ctx, now.Add(-1*time.Hour), now)
	assert.NoError(t, err)
	assert.Len(t, f, 2)
	assert.Equal(t, "Test-Win10", f[0].BotName)
	assert.Equal(t, []string{"somefile.txt"}, f[0].Files)

	assert.Equal(t, "Test-Linux", f[1].BotName)
	assert.Equal(t, []string{"README.md"}, f[1].Files)
}
<commit_msg>[predict] Follow on CL from last review.<commit_after>package failures

import (
	"context"
	"fmt"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	swarmingv1 "go.chromium.org/luci/common/api/swarming/swarming/v1"
	httpmock "gopkg.in/jarcoal/httpmock.v1"

	"go.skia.org/infra/go/ds/testutil"
	"go.skia.org/infra/go/git"
	git_testutils "go.skia.org/infra/go/git/testutils"
	"go.skia.org/infra/go/swarming"
	"go.skia.org/infra/go/testutils"
	"go.skia.org/infra/predict/go/dsconst"
)

var (
	hash1 = ""
	hash2 = ""
	hash3 = ""
)

func badbot(botname string, ts time.Time) bool {
	return botname == "bot-bad"
}

func taskListProvider(since time.Duration) ([]*swarmingv1.SwarmingRpcsTaskRequestMetadata, error) {
	now := time.Now().UTC()
	return []*swarmingv1.SwarmingRpcsTaskRequestMetadata{
		&swarmingv1.SwarmingRpcsTaskRequestMetadata{
			TaskResult: &swarmingv1.SwarmingRpcsTaskResult{
				Tags: []string{
					"sk_issue_server:https://skia-review.googlesource.com",
					"sk_issue:82041",
					"sk_patchset:1",
					"sk_name:Test-Win10",
					"sk_repo:https://skia.googlesource.com/skia.git",
				},
				StartedTs: now.Add(-10 * time.Minute).Format(swarming.TIMESTAMP_FORMAT),
			},
		},
		&swarmingv1.SwarmingRpcsTaskRequestMetadata{
			TaskResult: &swarmingv1.SwarmingRpcsTaskResult{
				Tags: []string{
					fmt.Sprintf("sk_revision:%s", hash1),
					"sk_name:Test-Linux",
					"sk_repo:https://skia.googlesource.com/skia.git",
				},
				StartedTs: now.Add(-1 * time.Minute).Format(swarming.TIMESTAMP_FORMAT),
			},
		},
		// The following should be ignored.
		&swarmingv1.SwarmingRpcsTaskRequestMetadata{
			TaskResult: &swarmingv1.SwarmingRpcsTaskResult{
				Tags: []string{
					"sk_revision:blahblahblah", // Unknown git hash.
					"sk_name:Test-Linux",
					"sk_repo:https://skia.googlesource.com/skia.git",
				},
				StartedTs: now.Add(-1 * time.Minute).Format(swarming.TIMESTAMP_FORMAT),
			},
		},
		&swarmingv1.SwarmingRpcsTaskRequestMetadata{
			TaskResult: &swarmingv1.SwarmingRpcsTaskResult{
				Tags: []string{
					fmt.Sprintf("sk_revision:%s", hash1),
					"sk_name:bot-bad", // Should be filtered out by badbot().
					"sk_repo:https://skia.googlesource.com/skia.git",
				},
				StartedTs: now.Add(-1 * time.Minute).Format(swarming.TIMESTAMP_FORMAT),
			},
		},
		&swarmingv1.SwarmingRpcsTaskRequestMetadata{
			TaskResult: &swarmingv1.SwarmingRpcsTaskResult{
				Tags: []string{
					fmt.Sprintf("sk_revision:%s", hash1),
					"sk_name:Upload-Some-Test-Results", // Should be filtered out since it's an upload task.
					"sk_repo:https://skia.googlesource.com/skia.git",
				},
				StartedTs: now.Add(-1 * time.Minute).Format(swarming.TIMESTAMP_FORMAT),
			},
		},
	}, nil
}

func TestStore(t *testing.T) {
	testutils.MediumTest(t)

	now := time.Now()

	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder("GET", "https://skia-review.googlesource.com/changes/82041/revisions/1/files/",
		httpmock.NewStringResponder(200, `)]}' {"somefile.txt":{}}`))

	cleanup := testutil.InitDatastore(t, dsconst.FAILURES)
	defer cleanup()

	ctx := context.Background()
	g := git_testutils.GitInit(t, ctx)
	defer g.Cleanup()

	hash1 = g.CommitGen(ctx, "README.md")
	hash2 = g.CommitGen(ctx, "README.md")
	hash3 = g.CommitGen(ctx, "INSTALL.md")
	git := &git.Checkout{
		GitDir: git.GitDir(g.Dir()),
	}
	fs := New(badbot, taskListProvider, git, http.DefaultClient, "https://skia.googlesource.com/skia.git")
	f, err := fs.List(ctx, now.Add(-1*time.Hour), now)
	assert.NoError(t, err)
	assert.Len(t, f, 0)

	err = fs.Update(ctx, time.Hour)
	assert.NoError(t, err)

	f, err = fs.List(ctx, now.Add(-1*time.Hour), now)
	assert.NoError(t, err)
	assert.Len(t, f, 2)
	assert.Equal(t, "Test-Win10", f[0].BotName)
	assert.Equal(t, []string{"somefile.txt"}, f[0].Files)

	assert.Equal(t, "Test-Linux", f[1].BotName)
	assert.Equal(t, []string{"README.md"}, f[1].Files)
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/coreos/go-etcd/etcd"

	"github.com/deis/deis/logger/syslogd"
)

const (
	timeout time.Duration = 10 * time.Second
	ttl     time.Duration = timeout * 2
)

func main() {
	host := getopt("HOST", "127.0.0.1")

	etcdPort := getopt("ETCD_PORT", "4001")
	etcdPath := getopt("ETCD_PATH", "/deis/logs")

	externalPort := getopt("EXTERNAL_PORT", "514")

	client := etcd.NewClient([]string{"http://" + host + ":" + etcdPort})

	// Wait for terminating signal
	exitChan := make(chan os.Signal, 2)
	cleanupChan := make(chan bool)
	signal.Notify(exitChan, syscall.SIGTERM, syscall.SIGINT)

	go syslogd.Listen(exitChan, cleanupChan)

	go publishService(client, host, etcdPath, externalPort, uint64(ttl.Seconds()))

	// Wait for the proper shutdown of the syslog server before exit
	<-cleanupChan
}

func publishService(client *etcd.Client, host string, etcdPath string, externalPort string, ttl uint64) {
	for {
		setEtcd(client, etcdPath+"/host", host, ttl)
		setEtcd(client, etcdPath+"/port", externalPort, ttl)
		time.Sleep(timeout)
	}
}

func setEtcd(client *etcd.Client, key, value string, ttl uint64) {
	_, err := client.Set(key, value, ttl)
	if err != nil {
		log.Println(err)
	}
}

func getopt(name, dfault string) string {
	value := os.Getenv(name)
	if value == "" {
		value = dfault
	}
	return value
}
<commit_msg>ref(logger): turn publish settings into flags<commit_after>package main

import (
	"flag"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/deis/deis/logger/syslogd"
)

var (
	publishHost   string
	publishPath   string
	publishPort   string
	publishInterval int
	publishTTL      int
)

func init() {
	flag.IntVar(&publishInterval, "publish-interval", 10, "publish interval in seconds")
	flag.StringVar(&publishHost, "publish-host", getopt("HOST", "127.0.0.1"), "service discovery hostname")
	flag.StringVar(&publishPath, "publish-path", getopt("ETCD_PATH", "/deis/logs"), "path to publish host/port information")
	flag.StringVar(&publishPort, "publish-port", getopt("ETCD_PORT", "4001"), "service discovery port")
	flag.IntVar(&publishTTL, "publish-ttl", publishInterval*2, "publish TTL in seconds")
}

func main() {
	flag.Parse()

	externalPort := getopt("EXTERNAL_PORT", "514")

	client := etcd.NewClient([]string{"http://" + publishHost + ":" + publishPort})

	// Wait for terminating signal
	exitChan := make(chan os.Signal, 2)
	cleanupChan := make(chan bool)
	signal.Notify(exitChan, syscall.SIGTERM, syscall.SIGINT)

	go syslogd.Listen(exitChan, cleanupChan)

	go publishService(client, publishHost, publishPath, externalPort, uint64(time.Duration(publishTTL).Seconds()))

	// Wait for the proper shutdown of the syslog server before exit
	<-cleanupChan
}

func publishService(client *etcd.Client, host string, etcdPath string, externalPort string, ttl uint64) {
	for {
		setEtcd(client, etcdPath+"/host", host, ttl)
		setEtcd(client, etcdPath+"/port", externalPort, ttl)
		time.Sleep(time.Duration(publishInterval))
	}
}

func setEtcd(client *etcd.Client, key, value string, ttl uint64) {
	_, err := client.Set(key, value, ttl)
	if err != nil {
		log.Println(err)
	}
}

func getopt(name, dfault string) string {
	value := os.Getenv(name)
	if value == "" {
		value = dfault
	}
	return value
}
<|endoftext|>


<commit_before>// Copyright 2015 The Tango 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 tango

import (
	"bytes"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/lunny/log"
)

func TestLogger1(t *testing.T) {
	buff := bytes.NewBufferString("")
	recorder := httptest.NewRecorder()

	n := NewWithLog(log.New(buff, "[tango] ", 0))
	n.Use(Logging())
	n.UseHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		rw.WriteHeader(http.StatusNotFound)
	}))

	req, err := http.NewRequest("GET", "http://localhost:3000/foobar", nil)
	if err != nil {
		t.Error(err)
	}

	n.ServeHTTP(recorder, req)
	expect(t, recorder.Code, http.StatusNotFound)
	refute(t, len(buff.String()), 0)
}

type LoggerAction struct {
	Log
}

func (l *LoggerAction) Get() string {
	return "log"
}

func TestLogger2(t *testing.T) {
	buff := bytes.NewBufferString("")
	recorder := httptest.NewRecorder()
	recorder.Body = buff

	n := Classic()
	n.Get("/", new(LoggerAction))

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err)
	}

	n.ServeHTTP(recorder, req)
	expect(t, recorder.Code, http.StatusOK)
	refute(t, len(buff.String()), 0)
	expect(t, buff.String(), "log")
}

func TestLogger3(t *testing.T) {
	buff := bytes.NewBufferString("")
	recorder := httptest.NewRecorder()
	recorder.Body = buff

	logger := NewCompositeLogger(log.Std, log.New(log.NewFileWriter(log.FileOptions{
		Dir:    "./",
		ByType: log.ByDay,
	}), "file", log.Ldefault()))

	n := Classic(logger)
	n.Get("/", new(LoggerAction))

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err)
	}

	n.ServeHTTP(recorder, req)
	expect(t, recorder.Code, http.StatusOK)
	refute(t, len(buff.String()), 0)
	expect(t, buff.String(), "log")
}
<commit_msg>add some tests<commit_after>// Copyright 2015 The Tango 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 tango

import (
	"bytes"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/lunny/log"
)

func TestLogger1(t *testing.T) {
	buff := bytes.NewBufferString("")
	recorder := httptest.NewRecorder()

	n := NewWithLog(log.New(buff, "[tango] ", 0))
	n.Use(Logging())
	n.UseHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		rw.WriteHeader(http.StatusNotFound)
	}))

	req, err := http.NewRequest("GET", "http://localhost:3000/foobar", nil)
	if err != nil {
		t.Error(err)
	}

	n.ServeHTTP(recorder, req)
	expect(t, recorder.Code, http.StatusNotFound)
	refute(t, len(buff.String()), 0)
}

type LoggerAction struct {
	Log
}

func (l *LoggerAction) Get() string {
	l.Warn("this is a warn")
	l.Warnf("This is a %s", "warnf")
	l.Error("this is an error")
	l.Errorf("This is a %s", "errorf")
	l.Infof("This is a %s", "infof")
	l.Debugf("This is a %s", "debuf")
	return "log"
}

func TestLogger2(t *testing.T) {
	buff := bytes.NewBufferString("")
	recorder := httptest.NewRecorder()
	recorder.Body = buff

	n := Classic()
	n.Get("/", new(LoggerAction))

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err)
	}

	n.ServeHTTP(recorder, req)
	expect(t, recorder.Code, http.StatusOK)
	refute(t, len(buff.String()), 0)
	expect(t, buff.String(), "log")
}

func TestLogger3(t *testing.T) {
	buff := bytes.NewBufferString("")
	recorder := httptest.NewRecorder()
	recorder.Body = buff

	logger := NewCompositeLogger(log.Std, log.New(log.NewFileWriter(log.FileOptions{
		Dir:    "./",
		ByType: log.ByDay,
	}), "file", log.Ldefault()))

	n := Classic(logger)
	n.Get("/", new(LoggerAction))

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err)
	}

	n.ServeHTTP(recorder, req)
	expect(t, recorder.Code, http.StatusOK)
	refute(t, len(buff.String()), 0)
	expect(t, buff.String(), "log")
}

type Logger4Action struct {
}

func (l *Logger4Action) Get() {
}

func TestLogger4(t *testing.T) {
	buff := bytes.NewBufferString("")
	recorder := httptest.NewRecorder()
	recorder.Body = buff

	n := Classic()
	n.Get("/", new(Logger4Action))

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err)
	}

	n.ServeHTTP(recorder, req)
	expect(t, recorder.Code, http.StatusOK)
}
<|endoftext|>