<commit_before>// gzvis is a tool to assist you visualising the operation of
// the go runtime garbage collector.
//
// usage:
//
//     gcvis program [arguments]...
package main

import (
	"bufio"
	"fmt"
	"html/template"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/pkg/browser"
)

func startSubprocess(w io.Writer) {
	env := os.Environ()
	env = append(env, "GODEBUG=gctrace=1")
	args := os.Args[1:]
	cmd := exec.Command(args[0], args[1:]...)
	cmd.Env = env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = w

	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
}

type gctrace struct {
	NumGC       int
	Nproc       int
	t1          int
	t2          int
	t3          int
	t4          int
	Heap0       int // heap size before, in megabytes
	Heap1       int // heap size after, in megabytes
	Obj         int
	NMalloc     int
	NFree       int
	NSpan       int
	NBGSweep    int
	NPauseSweep int
	NHandoff    int
	NHandoffCnt int
	NSteal      int
	NStealCnt   int
	NProcYield  int
	NOsYield    int
	NSleep      int
}

type scvgtrace struct {
	inuse    int
	idle     int
	sys      int
	released int
	consumed int
}

type graphPoints [2]int

var heapuse, scvginuse, scvgidle, scvgsys, scvgreleased, scvgconsumed []graphPoints

var mu sync.RWMutex

func index(w http.ResponseWriter, req *http.Request) {
	mu.RLock()
	defer mu.RUnlock()
	visTmpl.Execute(w, struct {
		HeapUse, ScvgInuse, ScvgIdle, ScvgSys, ScvgReleased, ScvgConsumed []graphPoints
		Title                                                             string
	}{
		HeapUse:      heapuse,
		ScvgInuse:    scvginuse,
		ScvgIdle:     scvgidle,
		ScvgSys:      scvgsys,
		ScvgReleased: scvgreleased,
		ScvgConsumed: scvgconsumed,
		Title:        strings.Join(os.Args[1:], " "),
	})

}

var visTmpl = template.Must(template.New("vis").Parse(`

<title>gcvis - {{ .Title }}</title>

{{ .Title }}

The smaller plot is linked to the main plot, so it acts as an overview. Try dragging a selection on either plot, and watch the behavior of the other.

Legend

gc.heapinuse: heap in use after gc
scvg.inuse: virtual memory considered in use by the scavenger
scvg.idle: virtual memory considered unused by the scavenger
scvg.sys: virtual memory requested from the operating system (should aproximate VSS)
scvg.released: virtual memory returned to the operating system by the scavenger
scvg.consumed: virtual memory in use (should roughly match process RSS)

`))

var (
	gcre   = regexp.MustCompile(`gc\d+\(\d+\): \d+\+\d+\+\d+\+\d+ us, \d+ -> \d+ MB, \d+ \(\d+-\d+\) objects, \d+\/\d+\/\d+ sweeps, \d+\(\d+\) handoff, \d+\(\d+\) steal, \d+\/\d+\/\d+ yields`)
	scvgre = regexp.MustCompile(`scvg\d+: inuse: \d+, idle: \d+, sys: \d+, released: \d+, consumed: \d+ \(MB\)`)
)

func startParser(r io.Reader, gcc chan *gctrace, scvgc chan *scvgtrace) {
	sc := bufio.NewScanner(r)
	for sc.Scan() {
		line := sc.Text()
		// try to parse as a gc trace line
		if gcre.MatchString(line) {
			var gc gctrace
			_, err := fmt.Sscanf(line, "gc%d(%d): %d+%d+%d+%d us, %d -> %d MB, %d (%d-%d) objects, %d/%d/%d sweeps, %d(%d) handoff, %d(%d) steal, %d/%d/%d yields\n",
				&gc.NumGC, &gc.Nproc, &gc.t1, &gc.t2, &gc.t3, &gc.t4, &gc.Heap0, &gc.Heap1, &gc.Obj, &gc.NMalloc, &gc.NFree,
				&gc.NSpan, &gc.NBGSweep, &gc.NPauseSweep, &gc.NHandoff, &gc.NHandoffCnt, &gc.NSteal, &gc.NStealCnt, &gc.NProcYield, &gc.NOsYield, &gc.NSleep)
			if err != nil {
				log.Printf("corrupt gctrace: %v: %s", err, line)
				continue
			}
			gcc <- &gc
			continue
		}
		// try to parse as a scavenger line
		if scvgre.MatchString(line) {
			var scvg scvgtrace
			var n int
			_, err := fmt.Sscanf(line, "scvg%d: inuse: %d, idle: %d, sys: %d, released: %d, consumed: %d (MB)\n",
				&n, &scvg.inuse, &scvg.idle, &scvg.sys, &scvg.released, &scvg.consumed)
			if err != nil {
				log.Printf("corrupt scvgtrace: %v: %s", err, line)
				continue
			}
			scvgc <- &scvg
			continue
		}
		// nope ? oh well, print it out
		fmt.Println(line)
	}
	if err := sc.Err(); err != nil {
		log.Fatal(err)
	}
}

func main() {
	if len(os.Args) < 2 {
		log.Fatalf("usage: %s command <args>...", os.Args[0])
	}
	pr, pw, _ := os.Pipe()
	gc := make(chan *gctrace, 1)
	scvg := make(chan *scvgtrace, 1)

	go startSubprocess(pw)
	go startParser(pr, gc, scvg)

	l, err := net.Listen("tcp4", "127.0.0.1:0")
	if err != nil {
		log.Fatal(err)
	}
	http.HandleFunc("/", index)
	go http.Serve(l, nil)

	url := fmt.Sprintf("http://%s/", l.Addr())
	log.Printf("opening browser window, if this fails, navigate to %s", url)
	browser.OpenURL(url)

	for {
		select {
		case gc := <-gc:
			mu.Lock()
			ts := int(time.Now().UnixNano() / 1e6)
			heapuse = append(heapuse, graphPoints{ts, gc.Heap1})
			mu.Unlock()
		case scvg := <-scvg:
			mu.Lock()
			ts := int(time.Now().UnixNano() / 1e6)
			scvginuse = append(scvginuse, graphPoints{ts, scvg.inuse})
			scvgidle = append(scvgidle, graphPoints{ts, scvg.idle})
			scvgsys = append(scvgsys, graphPoints{ts, scvg.sys})
			scvgreleased = append(scvgreleased, graphPoints{ts, scvg.released})
			scvgconsumed = append(scvgconsumed, graphPoints{ts, scvg.consumed})
			mu.Unlock()
		}
	}
}
<commit_msg>hide legend on overview graph<commit_after>// gzvis is a tool to assist you visualising the operation of
// the go runtime garbage collector.
//
// usage:
//
//     gcvis program [arguments]...
package main

import (
	"bufio"
	"fmt"
	"html/template"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/pkg/browser"
)

func startSubprocess(w io.Writer) {
	env := os.Environ()
	env = append(env, "GODEBUG=gctrace=1")
	args := os.Args[1:]
	cmd := exec.Command(args[0], args[1:]...)
	cmd.Env = env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = w

	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
}

type gctrace struct {
	NumGC       int
	Nproc       int
	t1          int
	t2          int
	t3          int
	t4          int
	Heap0       int // heap size before, in megabytes
	Heap1       int // heap size after, in megabytes
	Obj         int
	NMalloc     int
	NFree       int
	NSpan       int
	NBGSweep    int
	NPauseSweep int
	NHandoff    int
	NHandoffCnt int
	NSteal      int
	NStealCnt   int
	NProcYield  int
	NOsYield    int
	NSleep      int
}

type scvgtrace struct {
	inuse    int
	idle     int
	sys      int
	released int
	consumed int
}

type graphPoints [2]int

var heapuse, scvginuse, scvgidle, scvgsys, scvgreleased, scvgconsumed []graphPoints

var mu sync.RWMutex

func index(w http.ResponseWriter, req *http.Request) {
	mu.RLock()
	defer mu.RUnlock()
	visTmpl.Execute(w, struct {
		HeapUse, ScvgInuse, ScvgIdle, ScvgSys, ScvgReleased, ScvgConsumed []graphPoints
		Title                                                             string
	}{
		HeapUse:      heapuse,
		ScvgInuse:    scvginuse,
		ScvgIdle:     scvgidle,
		ScvgSys:      scvgsys,
		ScvgReleased: scvgreleased,
		ScvgConsumed: scvgconsumed,
		Title:        strings.Join(os.Args[1:], " "),
	})

}

var visTmpl = template.Must(template.New("vis").Parse(`

<title>gcvis - {{ .Title }}</title>

{{ .Title }}

The smaller plot is linked to the main plot, so it acts as an overview. Try dragging a selection on either plot, and watch the behavior of the other.

Legend

gc.heapinuse: heap in use after gc
scvg.inuse: virtual memory considered in use by the scavenger
scvg.idle: virtual memory considered unused by the scavenger
scvg.sys: virtual memory requested from the operating system (should aproximate VSS)
scvg.released: virtual memory returned to the operating system by the scavenger
scvg.consumed: virtual memory in use (should roughly match process RSS)

`))

var (
	gcre   = regexp.MustCompile(`gc\d+\(\d+\): \d+\+\d+\+\d+\+\d+ us, \d+ -> \d+ MB, \d+ \(\d+-\d+\) objects, \d+\/\d+\/\d+ sweeps, \d+\(\d+\) handoff, \d+\(\d+\) steal, \d+\/\d+\/\d+ yields`)
	scvgre = regexp.MustCompile(`scvg\d+: inuse: \d+, idle: \d+, sys: \d+, released: \d+, consumed: \d+ \(MB\)`)
)

func startParser(r io.Reader, gcc chan *gctrace, scvgc chan *scvgtrace) {
	sc := bufio.NewScanner(r)
	for sc.Scan() {
		line := sc.Text()
		// try to parse as a gc trace line
		if gcre.MatchString(line) {
			var gc gctrace
			_, err := fmt.Sscanf(line, "gc%d(%d): %d+%d+%d+%d us, %d -> %d MB, %d (%d-%d) objects, %d/%d/%d sweeps, %d(%d) handoff, %d(%d) steal, %d/%d/%d yields\n",
				&gc.NumGC, &gc.Nproc, &gc.t1, &gc.t2, &gc.t3, &gc.t4, &gc.Heap0, &gc.Heap1, &gc.Obj, &gc.NMalloc, &gc.NFree,
				&gc.NSpan, &gc.NBGSweep, &gc.NPauseSweep, &gc.NHandoff, &gc.NHandoffCnt, &gc.NSteal, &gc.NStealCnt, &gc.NProcYield, &gc.NOsYield, &gc.NSleep)
			if err != nil {
				log.Printf("corrupt gctrace: %v: %s", err, line)
				continue
			}
			gcc <- &gc
			continue
		}
		// try to parse as a scavenger line
		if scvgre.MatchString(line) {
			var scvg scvgtrace
			var n int
			_, err := fmt.Sscanf(line, "scvg%d: inuse: %d, idle: %d, sys: %d, released: %d, consumed: %d (MB)\n",
				&n, &scvg.inuse, &scvg.idle, &scvg.sys, &scvg.released, &scvg.consumed)
			if err != nil {
				log.Printf("corrupt scvgtrace: %v: %s", err, line)
				continue
			}
			scvgc <- &scvg
			continue
		}
		// nope ? oh well, print it out
		fmt.Println(line)
	}
	if err := sc.Err(); err != nil {
		log.Fatal(err)
	}
}

func main() {
	if len(os.Args) < 2 {
		log.Fatalf("usage: %s command <args>...", os.Args[0])
	}
	pr, pw, _ := os.Pipe()
	gc := make(chan *gctrace, 1)
	scvg := make(chan *scvgtrace, 1)

	go startSubprocess(pw)
	go startParser(pr, gc, scvg)

	l, err := net.Listen("tcp4", "127.0.0.1:0")
	if err != nil {
		log.Fatal(err)
	}
	http.HandleFunc("/", index)
	go http.Serve(l, nil)

	url := fmt.Sprintf("http://%s/", l.Addr())
	log.Printf("opening browser window, if this fails, navigate to %s", url)
	browser.OpenURL(url)

	for {
		select {
		case gc := <-gc:
			mu.Lock()
			ts := int(time.Now().UnixNano() / 1e6)
			heapuse = append(heapuse, graphPoints{ts, gc.Heap1})
			mu.Unlock()
		case scvg := <-scvg:
			mu.Lock()
			ts := int(time.Now().UnixNano() / 1e6)
			scvginuse = append(scvginuse, graphPoints{ts, scvg.inuse})
			scvgidle = append(scvgidle, graphPoints{ts, scvg.idle})
			scvgsys = append(scvgsys, graphPoints{ts, scvg.sys})
			scvgreleased = append(scvgreleased, graphPoints{ts, scvg.released})
			scvgconsumed = append(scvgconsumed, graphPoints{ts, scvg.consumed})
			mu.Unlock()
		}
	}
}
<|endoftext|>


<commit_before>package main

import "fmt"

import "github.com/jessevdk/go-flags"
import "github.com/tj/go-debug"
import "github.com/peterh/liner"

const VERSION = "0.5.0"

func main() {
	var opts struct {
		ConfigFile   string `short:"c" long:"config" description:"Path to config file"`
		PrintVersion bool   `short:"v" long:"version" description:"Print version number and exit"`
	}

	debug := debug.Debug("oktad:main")
	args, err := flags.Parse(&opts)

	if err != nil {
		fmt.Println(err)
		return
	}

	if opts.PrintVersion {
		fmt.Printf("oktad v%s\n", VERSION)
		return
	}

	debug("loading configuration data")
	// try to load configuration
	oktaCfg, err := parseConfig(opts.ConfigFile)

	if err != nil {
		fmt.Println("Error reading config file!")
		debug("cfg read err: %s", err)
		return
	}

	if len(args) <= 0 {
		fmt.Println("You must supply a profile name, sorry.")
		return
	}

	awsProfile := args[0]
	maybeCreds, err := loadCreds(awsProfile)
	if err == nil {
		debug("found cached credentials, going to use them")
		// if we could load creds, use them!
		err := prepAndLaunch(args, maybeCreds)
		if err != nil {
			fmt.Println("Error launching program: ", err)
		}
		return
	}

	debug("cred load err %s", err)

	user, pass, err := readUserPass()
	if err != nil {
		// if we got an error here, the user bailed on us
		debug("control-c caught in liner, probably")
		return
	}

	if user == "" || pass == "" {
		fmt.Println("Must supply a username and password!")
		return
	}

	ores, err := login(oktaCfg, user, pass)
	if err != nil {
		fmt.Println("Error authenticating with Okta! Maybe your username or password are wrong.")
		debug("login err %s", err)
		return
	}

	if ores.Status != "MFA_REQUIRED" {
		fmt.Println("MFA required to use this tool.")
		return
	}

	factor, err := extractTokenFactor(ores)

	if err != nil {
		fmt.Println("Error processing okta response!")
		debug("err from extractTokenFactor was %s", err)
		return
	}

	mfaToken, err := readMfaToken()
	if err != nil {
		debug("control-c caught in liner, probably")
		return
	}

	sessionToken, err := doMfa(ores, factor, mfaToken)
	if err != nil {
		fmt.Println("Error performing MFA auth!")
		debug("error from doMfa was %s", err)
		return
	}

	saml, err := getSaml(oktaCfg, sessionToken)
	debug("got saml: \n%s", saml.raw)

	if err != nil {
		fmt.Println("Error preparing to AssumeRole!")
		debug("getSaml err was %s", err)
		return
	}

	acfg, err := readAwsProfile(
		fmt.Sprintf("profile %s", awsProfile),
	)

	if err != nil {
		fmt.Println("Error reading your AWS profile!")
		debug("error was... %s", err)
	}

	mainCreds, _, err := assumeFirstRole(acfg, saml)
	if err != nil {
		fmt.Println("Error assuming first role!")
		debug("error was %s", err)
		return
	}

	finalCreds, fExp, err := assumeDestinationRole(acfg, mainCreds)
	if err != nil {
		fmt.Println("Error assuming second role!")
		debug("error was %s", err)
		return
	}

	// all was good, so let's save credentials...
	err = storeCreds(awsProfile, finalCreds, fExp)
	if err != nil {
		debug("err storing credentials, %s", err)
	}

	fmt.Println("Everything looks good; launching your program...")
	err = prepAndLaunch(args, finalCreds)
	if err != nil {
		fmt.Println("Error launching program: ", err)
	}
}

// reads the username and password from the command line
// returns user, then pass, then an error
func readUserPass() (user string, pass string, err error) {
	li := liner.NewLiner()

	// remember to close or weird stuff happens
	defer li.Close()

	li.SetCtrlCAborts(true)
	user, err = li.Prompt("Username: ")
	if err != nil {
		return
	}

	pass, err = li.PasswordPrompt("Password: ")
	if err != nil {
		return
	}

	return
}

// reads and returns an mfa token
func readMfaToken() (string, error) {
	li := liner.NewLiner()
	defer li.Close()
	li.SetCtrlCAborts(true)
	fmt.Println("Your account requires MFA; please enter a token.")
	return li.Prompt("MFA token: ")
}
<commit_msg>update version to 0.5.1<commit_after>package main

import "fmt"

import "github.com/jessevdk/go-flags"
import "github.com/tj/go-debug"
import "github.com/peterh/liner"

const VERSION = "0.5.1"

func main() {
	var opts struct {
		ConfigFile   string `short:"c" long:"config" description:"Path to config file"`
		PrintVersion bool   `short:"v" long:"version" description:"Print version number and exit"`
	}

	debug := debug.Debug("oktad:main")
	args, err := flags.Parse(&opts)

	if err != nil {
		fmt.Println(err)
		return
	}

	if opts.PrintVersion {
		fmt.Printf("oktad v%s\n", VERSION)
		return
	}

	debug("loading configuration data")
	// try to load configuration
	oktaCfg, err := parseConfig(opts.ConfigFile)

	if err != nil {
		fmt.Println("Error reading config file!")
		debug("cfg read err: %s", err)
		return
	}

	if len(args) <= 0 {
		fmt.Println("You must supply a profile name, sorry.")
		return
	}

	awsProfile := args[0]
	maybeCreds, err := loadCreds(awsProfile)
	if err == nil {
		debug("found cached credentials, going to use them")
		// if we could load creds, use them!
		err := prepAndLaunch(args, maybeCreds)
		if err != nil {
			fmt.Println("Error launching program: ", err)
		}
		return
	}

	debug("cred load err %s", err)

	user, pass, err := readUserPass()
	if err != nil {
		// if we got an error here, the user bailed on us
		debug("control-c caught in liner, probably")
		return
	}

	if user == "" || pass == "" {
		fmt.Println("Must supply a username and password!")
		return
	}

	ores, err := login(oktaCfg, user, pass)
	if err != nil {
		fmt.Println("Error authenticating with Okta! Maybe your username or password are wrong.")
		debug("login err %s", err)
		return
	}

	if ores.Status != "MFA_REQUIRED" {
		fmt.Println("MFA required to use this tool.")
		return
	}

	factor, err := extractTokenFactor(ores)

	if err != nil {
		fmt.Println("Error processing okta response!")
		debug("err from extractTokenFactor was %s", err)
		return
	}

	mfaToken, err := readMfaToken()
	if err != nil {
		debug("control-c caught in liner, probably")
		return
	}

	sessionToken, err := doMfa(ores, factor, mfaToken)
	if err != nil {
		fmt.Println("Error performing MFA auth!")
		debug("error from doMfa was %s", err)
		return
	}

	saml, err := getSaml(oktaCfg, sessionToken)
	debug("got saml: \n%s", saml.raw)

	if err != nil {
		fmt.Println("Error preparing to AssumeRole!")
		debug("getSaml err was %s", err)
		return
	}

	acfg, err := readAwsProfile(
		fmt.Sprintf("profile %s", awsProfile),
	)

	if err != nil {
		fmt.Println("Error reading your AWS profile!")
		debug("error was... %s", err)
	}

	mainCreds, _, err := assumeFirstRole(acfg, saml)
	if err != nil {
		fmt.Println("Error assuming first role!")
		debug("error was %s", err)
		return
	}

	finalCreds, fExp, err := assumeDestinationRole(acfg, mainCreds)
	if err != nil {
		fmt.Println("Error assuming second role!")
		debug("error was %s", err)
		return
	}

	// all was good, so let's save credentials...
	err = storeCreds(awsProfile, finalCreds, fExp)
	if err != nil {
		debug("err storing credentials, %s", err)
	}

	fmt.Println("Everything looks good; launching your program...")
	err = prepAndLaunch(args, finalCreds)
	if err != nil {
		fmt.Println("Error launching program: ", err)
	}
}

// reads the username and password from the command line
// returns user, then pass, then an error
func readUserPass() (user string, pass string, err error) {
	li := liner.NewLiner()

	// remember to close or weird stuff happens
	defer li.Close()

	li.SetCtrlCAborts(true)
	user, err = li.Prompt("Username: ")
	if err != nil {
		return
	}

	pass, err = li.PasswordPrompt("Password: ")
	if err != nil {
		return
	}

	return
}

// reads and returns an mfa token
func readMfaToken() (string, error) {
	li := liner.NewLiner()
	defer li.Close()
	li.SetCtrlCAborts(true)
	fmt.Println("Your account requires MFA; please enter a token.")
	return li.Prompt("MFA token: ")
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"

	"code.cloudfoundry.org/cli/plugin"
	"code.cloudfoundry.org/cli/plugin/models"
	"github.com/bluemixgaragelondon/cf-blue-green-deploy/manifest"
	"strings"
)

var PluginVersion string

type CfPlugin struct {
	Connection plugin.CliConnection
	Deployer   BlueGreenDeployer
}

func (p *CfPlugin) Run(cliConnection plugin.CliConnection, args []string) {
	if len(args) > 0 && args[0] == "CLI-MESSAGE-UNINSTALL" {
		return
	}

	argsStruct := NewArgs(args)

	p.Connection = cliConnection

	defaultCfDomain, err := p.DefaultCfDomain()
	if err != nil {
		log.Fatalf("Failed to get default shared domain: %v", err)
	}

	p.Deployer.Setup(cliConnection)

	if argsStruct.AppName == "" {
		log.Fatal("App name was empty, must be provided.")
	}

	if !p.Deploy(defaultCfDomain, &manifest.FileManifestReader{}, argsStruct) {
		log.Fatal("Smoke tests failed")
	}
}

func (p *CfPlugin) Deploy(defaultCfDomain string, manifestReader manifest.ManifestReader, args Args) bool {
	appName := args.AppName

	p.Deployer.DeleteAllAppsExceptLiveApp(appName)
	liveAppName, liveAppRoutes := p.Deployer.LiveApp(appName)

	newAppName := appName + "-new"

	// Add route so that we can run the smoke tests
	tempRoute := plugin_models.GetApp_RouteSummary{Host: newAppName, Domain: plugin_models.GetApp_DomainFields{Name: defaultCfDomain}}
	// If deploy is unsuccessful, p.ErrorFunc will be called which exits.
	p.Deployer.PushNewApp(newAppName, tempRoute, args.ManifestPath)

	promoteNewApp := true
	smokeTestScript := args.SmokeTestPath
	if smokeTestScript != "" {
		promoteNewApp = p.Deployer.RunSmokeTests(smokeTestScript, FQDN(tempRoute))
	}

	// TODO We're overloading 'new' here for both the staging app and the 'finished' app, which is confusing
	newAppRoutes := p.GetNewAppRoutes(args.AppName, defaultCfDomain, manifestReader, liveAppRoutes)

	p.Deployer.UnmapRoutesFromApp(newAppName, tempRoute)

	if promoteNewApp {
		// If there is a live app, we want to disassociate the routes with the old version of the app
		// and instead update the routes to use the new version.
		if liveAppName != "" {
			p.Deployer.MapRoutesToApp(newAppName, newAppRoutes...)
			p.Deployer.RenameApp(liveAppName, appName+"-old")
			p.Deployer.RenameApp(newAppName, appName)
			p.Deployer.UnmapRoutesFromApp(appName+"-old", liveAppRoutes...)
		} else {
			// If there is no live app, we only need to add our new routes.
			p.Deployer.MapRoutesToApp(newAppName, newAppRoutes...)
			p.Deployer.RenameApp(newAppName, appName)
		}
		return true
	} else {
		// We don't want to promote. Instead mark it as failed.
		p.Deployer.RenameApp(newAppName, appName+"-failed")
		return false
	}
}

func (p *CfPlugin) GetNewAppRoutes(appName string, defaultCfDomain string, manifestReader manifest.ManifestReader, liveAppRoutes []plugin_models.GetApp_RouteSummary) []plugin_models.GetApp_RouteSummary {
	newAppRoutes := []plugin_models.GetApp_RouteSummary{}

	// TODO: Ignoring the error is no worse than we were doing before
	manifest, _ := manifestReader.Read()

	if manifest != nil {
		if appParams := manifest.GetAppParams(appName, defaultCfDomain); appParams != nil && appParams.Routes != nil {
			newAppRoutes = appParams.Routes
		}
	}

	uniqueRoutes := p.UnionRouteLists(newAppRoutes, liveAppRoutes)

	if len(uniqueRoutes) == 0 {
		uniqueRoutes = append(uniqueRoutes, plugin_models.GetApp_RouteSummary{Host: appName, Domain: plugin_models.GetApp_DomainFields{Name: defaultCfDomain}})
	}
	return uniqueRoutes
}

func (p *CfPlugin) UnionRouteLists(listA []plugin_models.GetApp_RouteSummary, listB []plugin_models.GetApp_RouteSummary) []plugin_models.GetApp_RouteSummary {
	duplicateList := append(listA, listB...)

	routesSet := make(map[plugin_models.GetApp_RouteSummary]struct{})

	for _, route := range duplicateList {
		routesSet[route] = struct{}{}
	}

	uniqueRoutes := []plugin_models.GetApp_RouteSummary{}
	for route := range routesSet {
		uniqueRoutes = append(uniqueRoutes, route)
	}
	return uniqueRoutes
}

func (p *CfPlugin) GetMetadata() plugin.PluginMetadata {
	var major, minor, build int
	fmt.Sscanf(PluginVersion, "%d.%d.%d", &major, &minor, &build)

	return plugin.PluginMetadata{
		Name: "blue-green-deploy",
		Version: plugin.VersionType{
			Major: major,
			Minor: minor,
			Build: build,
		},
		Commands: []plugin.Command{
			{
				Name:     "blue-green-deploy",
				Alias:    "bgd",
				HelpText: "Zero-downtime deploys with smoke tests",
				UsageDetails: plugin.Usage{
					// TODO for manifests with multiple apps, a different smoke test is needed. The approach below would not work.
					// Perhaps we could name the smoke test in the manifest?
					Usage: "blue-green-deploy APP_NAME [--smoke-test TEST_SCRIPT] [-f MANIFEST_FILE]",
					Options: map[string]string{
						"smoke-test": "The test script to run.",
						"f":          "Path to manifest",
					},
				},
			},
		},
	}
}

func (p *CfPlugin) DefaultCfDomain() (domain string, err error) {
	var res []string
	if res, err = p.Connection.CliCommandWithoutTerminalOutput("curl", "/v2/shared_domains"); err != nil {
		return
	}

	response := struct {
		Resources []struct {
			Entity struct {
				Name string
			}
		}
	}{}

	var json_string string
	json_string = strings.Join(res, "\n")

	if err = json.Unmarshal([]byte(json_string), &response); err != nil {
		return
	}

	domain = response.Resources[0].Entity.Name
	return
}

func FQDN(r plugin_models.GetApp_RouteSummary) string {
	return fmt.Sprintf("%v.%v", r.Host, r.Domain.Name)
}

func main() {

	log.SetFlags(0)

	p := CfPlugin{
		Deployer: &BlueGreenDeploy{
			ErrorFunc: func(message string, err error) {
				log.Fatalf("%v - %v", message, err)
			},
			Out: os.Stdout,
		},
	}

	// TODO issue #24 - (Rufus) - not sure if I'm using the plugin correctly, but if I build (go build) and run without arguments
	// I expected to see available arguments but instead the code panics.
	plugin.Start(&p)
}
<commit_msg>Print error as before<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"

	"strings"

	"code.cloudfoundry.org/cli/plugin"
	"code.cloudfoundry.org/cli/plugin/models"
	"github.com/bluemixgaragelondon/cf-blue-green-deploy/manifest"
)

var PluginVersion string

type CfPlugin struct {
	Connection plugin.CliConnection
	Deployer   BlueGreenDeployer
}

func (p *CfPlugin) Run(cliConnection plugin.CliConnection, args []string) {
	if len(args) > 0 && args[0] == "CLI-MESSAGE-UNINSTALL" {
		return
	}

	argsStruct := NewArgs(args)

	p.Connection = cliConnection

	defaultCfDomain, err := p.DefaultCfDomain()
	if err != nil {
		log.Fatalf("Failed to get default shared domain: %v", err)
	}

	p.Deployer.Setup(cliConnection)

	if argsStruct.AppName == "" {
		log.Fatal("App name was empty, must be provided.")
	}

	if !p.Deploy(defaultCfDomain, &manifest.FileManifestReader{}, argsStruct) {
		log.Fatal("Smoke tests failed")
	}
}

func (p *CfPlugin) Deploy(defaultCfDomain string, manifestReader manifest.ManifestReader, args Args) bool {
	appName := args.AppName

	p.Deployer.DeleteAllAppsExceptLiveApp(appName)
	liveAppName, liveAppRoutes := p.Deployer.LiveApp(appName)

	newAppName := appName + "-new"

	// Add route so that we can run the smoke tests
	tempRoute := plugin_models.GetApp_RouteSummary{Host: newAppName, Domain: plugin_models.GetApp_DomainFields{Name: defaultCfDomain}}
	// If deploy is unsuccessful, p.ErrorFunc will be called which exits.
	p.Deployer.PushNewApp(newAppName, tempRoute, args.ManifestPath)

	promoteNewApp := true
	smokeTestScript := args.SmokeTestPath
	if smokeTestScript != "" {
		promoteNewApp = p.Deployer.RunSmokeTests(smokeTestScript, FQDN(tempRoute))
	}

	// TODO We're overloading 'new' here for both the staging app and the 'finished' app, which is confusing
	newAppRoutes := p.GetNewAppRoutes(args.AppName, defaultCfDomain, manifestReader, liveAppRoutes)

	p.Deployer.UnmapRoutesFromApp(newAppName, tempRoute)

	if promoteNewApp {
		// If there is a live app, we want to disassociate the routes with the old version of the app
		// and instead update the routes to use the new version.
		if liveAppName != "" {
			p.Deployer.MapRoutesToApp(newAppName, newAppRoutes...)
			p.Deployer.RenameApp(liveAppName, appName+"-old")
			p.Deployer.RenameApp(newAppName, appName)
			p.Deployer.UnmapRoutesFromApp(appName+"-old", liveAppRoutes...)
		} else {
			// If there is no live app, we only need to add our new routes.
			p.Deployer.MapRoutesToApp(newAppName, newAppRoutes...)
			p.Deployer.RenameApp(newAppName, appName)
		}
		return true
	} else {
		// We don't want to promote. Instead mark it as failed.
		p.Deployer.RenameApp(newAppName, appName+"-failed")
		return false
	}
}

func (p *CfPlugin) GetNewAppRoutes(appName string, defaultCfDomain string, manifestReader manifest.ManifestReader, liveAppRoutes []plugin_models.GetApp_RouteSummary) []plugin_models.GetApp_RouteSummary {
	newAppRoutes := []plugin_models.GetApp_RouteSummary{}

	manifest, err := manifestReader.Read()
	if err != nil {
		// This error should be handled properly
		fmt.Println(err)
	}

	if manifest != nil {
		if appParams := manifest.GetAppParams(appName, defaultCfDomain); appParams != nil && appParams.Routes != nil {
			newAppRoutes = appParams.Routes
		}
	}

	uniqueRoutes := p.UnionRouteLists(newAppRoutes, liveAppRoutes)

	if len(uniqueRoutes) == 0 {
		uniqueRoutes = append(uniqueRoutes, plugin_models.GetApp_RouteSummary{Host: appName, Domain: plugin_models.GetApp_DomainFields{Name: defaultCfDomain}})
	}
	return uniqueRoutes
}

func (p *CfPlugin) UnionRouteLists(listA []plugin_models.GetApp_RouteSummary, listB []plugin_models.GetApp_RouteSummary) []plugin_models.GetApp_RouteSummary {
	duplicateList := append(listA, listB...)

	routesSet := make(map[plugin_models.GetApp_RouteSummary]struct{})

	for _, route := range duplicateList {
		routesSet[route] = struct{}{}
	}

	uniqueRoutes := []plugin_models.GetApp_RouteSummary{}
	for route := range routesSet {
		uniqueRoutes = append(uniqueRoutes, route)
	}
	return uniqueRoutes
}

func (p *CfPlugin) GetMetadata() plugin.PluginMetadata {
	var major, minor, build int
	fmt.Sscanf(PluginVersion, "%d.%d.%d", &major, &minor, &build)

	return plugin.PluginMetadata{
		Name: "blue-green-deploy",
		Version: plugin.VersionType{
			Major: major,
			Minor: minor,
			Build: build,
		},
		Commands: []plugin.Command{
			{
				Name:     "blue-green-deploy",
				Alias:    "bgd",
				HelpText: "Zero-downtime deploys with smoke tests",
				UsageDetails: plugin.Usage{
					// TODO for manifests with multiple apps, a different smoke test is needed. The approach below would not work.
					// Perhaps we could name the smoke test in the manifest?
					Usage: "blue-green-deploy APP_NAME [--smoke-test TEST_SCRIPT] [-f MANIFEST_FILE]",
					Options: map[string]string{
						"smoke-test": "The test script to run.",
						"f":          "Path to manifest",
					},
				},
			},
		},
	}
}

func (p *CfPlugin) DefaultCfDomain() (domain string, err error) {
	var res []string
	if res, err = p.Connection.CliCommandWithoutTerminalOutput("curl", "/v2/shared_domains"); err != nil {
		return
	}

	response := struct {
		Resources []struct {
			Entity struct {
				Name string
			}
		}
	}{}

	var json_string string
	json_string = strings.Join(res, "\n")

	if err = json.Unmarshal([]byte(json_string), &response); err != nil {
		return
	}

	domain = response.Resources[0].Entity.Name
	return
}

func FQDN(r plugin_models.GetApp_RouteSummary) string {
	return fmt.Sprintf("%v.%v", r.Host, r.Domain.Name)
}

func main() {

	log.SetFlags(0)

	p := CfPlugin{
		Deployer: &BlueGreenDeploy{
			ErrorFunc: func(message string, err error) {
				log.Fatalf("%v - %v", message, err)
			},
			Out: os.Stdout,
		},
	}

	// TODO issue #24 - (Rufus) - not sure if I'm using the plugin correctly, but if I build (go build) and run without arguments
	// I expected to see available arguments but instead the code panics.
	plugin.Start(&p)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"crypto/rand"
	"flag"
	"fmt"
	"log"
	"math/big"
	"os"
)

var (
	dictFile = flag.String("d", "/usr/share/dict/words",
		"File from which to read the dictionary")
	length = flag.Int("n", 4, "Desired number of words in the password")
)

func check(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	// Setup
	flag.Parse()
	file, err := os.Open(*dictFile)
	check(err)
	defer file.Close()
	dict := bufio.NewReader(file)

	// Count the number of words in the file.
	var nwords int64
	for r, _, err := dict.ReadRune(); err == nil; {
		if r == '\n' {
			nwords++
		}
		r, _, err = dict.ReadRune()
	}

	// Make some storage for our password
	result := make([]string, *length)

	for i := 0; i < *length; i++ {
		// Seek back to the beginning of the file to generate a new word.
		_, err := file.Seek(0, 0)
		check(err)

		// Make a new buffer.Reader; we can't seek on one of these as
		// best as I can tell.
		dict = bufio.NewReader(file)

		// Generate a random index into the file...
		jthword, err := rand.Int(rand.Reader, big.NewInt(nwords))
		check(err)

		// ...and go find it.
		for j := int64(0); j < jthword.Int64(); {
			r, _, err := dict.ReadRune()
			check(err)
			if r == '\n' {
				j++
			}
		}

		// Okay, we've found our word. now let's read it in and add it to
		// the password.
		result[i], err = dict.ReadString('\n')
		check(err)
	}

	// We're done! print it out.
	for _, v := range result {
		fmt.Print(v)
	}
}
<commit_msg>Removed usage of log.Fatal<commit_after>package main

import (
	"bufio"
	"crypto/rand"
	"flag"
	"fmt"
	"math/big"
	"os"
)

var (
	dictFile = flag.String("d", "/usr/share/dict/words",
		"File from which to read the dictionary")
	length = flag.Int("n", 4, "Desired number of words in the password")
)

func check(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		os.Exit(1)
	}
}

func main() {
	// Setup
	flag.Parse()
	file, err := os.Open(*dictFile)
	check(err)
	defer file.Close()
	dict := bufio.NewReader(file)

	// Count the number of words in the file.
	var nwords int64
	for r, _, err := dict.ReadRune(); err == nil; {
		if r == '\n' {
			nwords++
		}
		r, _, err = dict.ReadRune()
	}

	// Make some storage for our password
	result := make([]string, *length)

	for i := 0; i < *length; i++ {
		// Seek back to the beginning of the file to generate a new word.
		_, err := file.Seek(0, 0)
		check(err)

		// Make a new buffer.Reader; we can't seek on one of these as
		// best as I can tell.
		dict = bufio.NewReader(file)

		// Generate a random index into the file...
		jthword, err := rand.Int(rand.Reader, big.NewInt(nwords))
		check(err)

		// ...and go find it.
		for j := int64(0); j < jthword.Int64(); {
			r, _, err := dict.ReadRune()
			check(err)
			if r == '\n' {
				j++
			}
		}

		// Okay, we've found our word. now let's read it in and add it to
		// the password.
		result[i], err = dict.ReadString('\n')
		check(err)
	}

	// We're done! print it out.
	for _, v := range result {
		fmt.Print(v)
	}
}
<|endoftext|>


<commit_before>package plugin

import (
	"io/ioutil"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	"github.com/yuin/gopher-lua"
	"layeh.com/gopher-luar"
)

// L is the lua state
// This is the VM that runs the plugins
var (
	L       *lua.LState
	plugins map[string]Plugin
)

// Plugin ...
type Plugin struct {
	Path string
	Name string
}

// Call a function for the specific plugin
func (p *Plugin) Call(fn string, args ...interface{}) (lua.LValue, error) {
	return Call(p.Name+"."+fn, args...)
}

// Unload a specific plugin
func (p *Plugin) Unload() error {
	return Unload(p.Path)
}

// Set a global variable in lua VM
func Set(name string, val interface{}) {
	L.SetGlobal(name, luar.New(L, val))
}

// IsLoaded returns whether the file is loaded or not in the lua VM
func IsLoaded(path string) bool {
	filePath, err := filepath.Abs(path)
	if err != nil {
		return false
	}
	_, ok := plugins[filePath]
	return ok
}

// Unload remove plugin functions from lua VM
func Unload(path string) error {
	filePath, _ := filepath.Abs(path)
	_, fileName := filepath.Split(filePath)
	fileExt := filepath.Ext(fileName)
	pluginName := strings.TrimSuffix(fileName, fileExt)
	str := "\nlocal P = {}\n" + pluginName + " = P\nsetmetatable(" + pluginName + ", {__index = _G})\nsetfenv(1, P)\n"
	if err := L.DoString(str); err != nil {
		return errors.Wrap(err, "Unload: Failed to unload plugin")
	}
	delete(plugins, filePath)
	return nil
}

// Load plugin functions in lua VM
func Load(path string) (Plugin, error) {
	var newPlugin Plugin
	filePath, _ := filepath.Abs(path)
	_, fileName := filepath.Split(filePath)
	fileExt := filepath.Ext(fileName)
	pluginName := strings.TrimSuffix(fileName, fileExt)
	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		return newPlugin, errors.Wrap(err, "Load: Unable to read file")
	}
	pluginDef := "\nlocal P = {}\n" + pluginName + " = P\nsetmetatable(" + pluginName + ", {__index = _G})\nsetfenv(1, P)\n"
	if err := L.DoString(pluginDef + string(data)); err != nil {
		return newPlugin, errors.Wrap(err, "Load: Unable to execute lua string")
	}
	newPlugin = Plugin{filePath, pluginName}
	plugins[filePath] = newPlugin
	return newPlugin, nil
}

// Call a function in the lua VM
func Call(fn string, args ...interface{}) (lua.LValue, error) {
	var luaFunc lua.LValue
	if strings.Contains(fn, ".") {
		plugin := L.GetGlobal(strings.Split(fn, ".")[0])
		if plugin.String() == "nil" {
			return nil, errors.New("function does not exist: " + fn)
		}
		luaFunc = L.GetField(plugin, strings.Split(fn, ".")[1])
	} else {
		luaFunc = L.GetGlobal(fn)
	}
	if luaFunc.String() == "nil" {
		return nil, errors.New("function does not exist: " + fn)
	}
	var luaArgs []lua.LValue
	for _, v := range args {
		luaArgs = append(luaArgs, luar.New(L, v))
	}
	err := L.CallByParam(lua.P{
		Fn:      luaFunc,
		NRet:    1,
		Protect: true,
	}, luaArgs...)
	if err != nil {
		return nil, err
	}
	ret := L.Get(-1) // returned value
	L.Pop(1)         // remove received value
	return ret, nil
}
}

// Init the lua VM
func Init() {
	L = lua.NewState()
	plugins = make(map[string]Plugin)
}

// Close lua VM
func Close() {
	L.Close()
}
<commit_msg>CallUnmarshal idea<commit_after>package plugin

import (
	"io/ioutil"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	"github.com/yuin/gopher-lua"
	"layeh.com/gopher-luar"
)

// L is the lua state
// This is the VM that runs the plugins
var (
	L       *lua.LState
	plugins map[string]Plugin
)

// Plugin ...
type Plugin struct {
	Path string
	Name string
}

// Call a function for the specific plugin
func (p *Plugin) Call(fn string, args ...interface{}) (lua.LValue, error) {
	return Call(p.Name+"."+fn, args...)
}

// Unload a specific plugin
func (p *Plugin) Unload() error {
	return Unload(p.Path)
}

// Set a global variable in lua VM
func Set(name string, val interface{}) {
	L.SetGlobal(name, luar.New(L, val))
}

// IsLoaded returns whether the file is loaded or not in the lua VM
func IsLoaded(path string) bool {
	filePath, err := filepath.Abs(path)
	if err != nil {
		return false
	}
	_, ok := plugins[filePath]
	return ok
}

// Unload remove plugin functions from lua VM
func Unload(path string) error {
	filePath, _ := filepath.Abs(path)
	_, fileName := filepath.Split(filePath)
	fileExt := filepath.Ext(fileName)
	pluginName := strings.TrimSuffix(fileName, fileExt)
	str := "\nlocal P = {}\n" + pluginName + " = P\nsetmetatable(" + pluginName + ", {__index = _G})\nsetfenv(1, P)\n"
	if err := L.DoString(str); err != nil {
		return errors.Wrap(err, "Unload: Failed to unload plugin")
	}
	delete(plugins, filePath)
	return nil
}

// Load plugin functions in lua VM
func Load(path string) (Plugin, error) {
	var newPlugin Plugin
	filePath, _ := filepath.Abs(path)
	_, fileName := filepath.Split(filePath)
	fileExt := filepath.Ext(fileName)
	pluginName := strings.TrimSuffix(fileName, fileExt)
	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		return newPlugin, errors.Wrap(err, "Load: Unable to read file")
	}
	pluginDef := "\nlocal P = {}\n" + pluginName + " = P\nsetmetatable(" + pluginName + ", {__index = _G})\nsetfenv(1, P)\n"
	if err := L.DoString(pluginDef + string(data)); err != nil {
		return newPlugin, errors.Wrap(err, "Load: Unable to execute lua string")
	}
	newPlugin = Plugin{filePath, pluginName}
	plugins[filePath] = newPlugin
	return newPlugin, nil
}

// Call a function in the lua VM
func Call(fn string, args ...interface{}) (lua.LValue, error) {
	var luaFunc lua.LValue
	if strings.Contains(fn, ".") {
		plugin := L.GetGlobal(strings.Split(fn, ".")[0])
		if plugin.String() == "nil" {
			return nil, errors.New("function does not exist: " + fn)
		}
		luaFunc = L.GetField(plugin, strings.Split(fn, ".")[1])
	} else {
		luaFunc = L.GetGlobal(fn)
	}
	if luaFunc.String() == "nil" {
		return nil, errors.New("function does not exist: " + fn)
	}
	var luaArgs []lua.LValue
	for _, v := range args {
		luaArgs = append(luaArgs, luar.New(L, v))
	}
	err := L.CallByParam(lua.P{
		Fn:      luaFunc,
		NRet:    1,
		Protect: true,
	}, luaArgs...)
	if err != nil {
		return nil, err
	}
	ret := L.Get(-1) // returned value
	L.Pop(1)         // remove received value
	return ret, nil
}

func CallUnmarshal(fn string, v interface{}, args ...interface{}) error {
	//var v1 string
	//json.Unmarshal([]byte(""), &v1)
	//rv := reflect.ValueOf(v)
	return nil
}

// Init the lua VM
func Init() {
	L = lua.NewState()
	plugins = make(map[string]Plugin)
}

// Close lua VM
func Close() {
	L.Close()
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"github.com/gorilla/websocket"
	"github.com/op/go-logging"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	_ "net/http/pprof"
	"net/url"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

type slackEvent struct {
	Type      string `json:"type"`
	User      string `json:"user"`
	Text      string `json:"text"`
	Timestamp string `json:"ts"`
}

type restart struct {
	Reason string
}

type client struct {
	conn net.Conn
	name string
}

type connToUserMsg struct {
	UserID  string
	Message string
}

type connToAllUsersMsg struct {
	Message string
}

type connReplyMsg struct {
	Message string
}

type user struct {
	ID   string
	Recv chan slackEvent
	Send chan connReplyMsg
}

type connRegister struct {
	Name string
	Conn net.Conn
}

type connUnregister struct {
	Name string
}

type connMessage struct {
	Type   string
	Msg    string
	UserID string
}

type connQuery struct {
	Reply chan map[string]net.Conn
}

type msg struct {
	Type string `json:"type"`
	Data string `json:"data"`
	User string `json:"user,omitempty"`
}

type config struct {
	Token string   `json:"token"`
	Users []string `json:"users"`
	Port  int      `json:"port"`
	Host  string   `json:"host"`
}

type rtmResponse struct {
	URL string `json:"url"`
}

type ctx struct {
	Token       string
	Restarts    chan restart
	SlackEvents chan string
	UserEvents  chan interface{}
	Connections chan interface{}
}

var log = logging.MustGetLogger("main-logger")

func readConfig() config {
	config := config{
		Token: "",
		Users: []string{},
		Port:  9191,
		Host:  "localhost",
	}
	file, errReadFile := ioutil.ReadFile("config.json")
	if errReadFile != nil {
		log.Warningf("Config file error: %s; using default values.", errReadFile)
		return config
	}
	errUnmarshal := json.Unmarshal(file, &config)
	if errUnmarshal != nil {
		log.Warningf("Config file error: %s; using default values.", errUnmarshal)
		return config
	}
	return config
}

func startRtm(origin string) rtmResponse {
	resp, err := http.Get(origin)
	if err != nil {
		return rtmResponse{}
	}
	defer resp.Body.Close()
	var data rtmResponse
	json.NewDecoder(resp.Body).Decode(&data)
	return data
}

func sendPrvMessage(ctx ctx, userID string, msg string) error {
	token := ctx.Token
	httpclient := &http.Client{
		Timeout: time.Second * 3,
	}
	rsp, err := httpclient.PostForm("https://slack.com/api/chat.postMessage?token="+token, url.Values{"channel": {userID}, "as_user": {"true"}, "text": {msg}})
	if err != nil {
		log.Errorf("Error while sending private message: %s", err)
		return err
	}
	io.Copy(ioutil.Discard, rsp.Body)
	if rsp != nil {
		rsp.Body.Close()
	}
	return err
}

func connectWs(url string, origin string) (*websocket.Conn, error) {
	c, _, err := websocket.DefaultDialer.Dial(url, nil)
	return c, err
}

func eventsProducer(ws *websocket.Conn, ctx ctx) {
	ticker := time.NewTicker(10 * time.Second)
	stopPinger := make(chan struct{})
	ws.SetReadDeadline(time.Now().Add(15 * time.Second))
	ws.SetPongHandler(func(string) error {
		ws.SetReadDeadline(time.Now().Add(15 * time.Second))
		return nil
	})
	go func() {
		for {
			select {
			case <-ticker.C:
				if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
					log.Errorf("Error while writing ping: %v", err)
				}
			case <-stopPinger:
				ticker.Stop()
				return
			}
		}
	}()
	for {
		_, msg, err := ws.ReadMessage()
		if err != nil {
			log.Errorf("Error while reading message: %v", err)
			close(stopPinger)
			ws.Close()
			ctx.Restarts <- restart{Reason: "slack read message error"}
			go func() {
				ctx.Connections <- connMessage{Type: "restart", Msg: "slack read message error"}
			}()
			break
		}
		ctx.SlackEvents <- string(msg[:])
	}
}

func parseSlackEvent(jsonEvent string) (slackEvent, error) {
	var event slackEvent
	err := json.Unmarshal([]byte(jsonEvent), &event)
	if err != nil {
		return event, errors.New("Invalid event")
	}
	return event, nil
}

func dispatchSlackEvents(ctx ctx) {
	for {
		jsonEvent := <-ctx.SlackEvents
		event, _ := parseSlackEvent(jsonEvent)
		go func() {
			ctx.UserEvents <- event
		}()
	}
}

func userReceiveMessages(ctx ctx, u user) {
	for {
		m := <-u.Recv
		if m.Type == "message" {
			go func() {
				ctx.Connections <- connMessage{
					Type:   "msg",
					Msg:    m.Text,
					UserID: u.ID,
				}
			}()
		}
		log.Infof("Dispatching event: %#v, for user: %#v", m, u)
	}
}

func userSendMessages(ctx ctx, u user) {
	for {
		m := <-u.Send
		sendPrvMessage(ctx, u.ID, m.Message)
		log.Infof("Message for the user: %#v, for user: %s", m, u.ID)
	}
}

func usersRegistrar(ctx ctx, userIds []string) {
	users := make(map[string]user)
	for _, userID := range userIds {
		u := user{
			ID:   userID,
			Recv: make(chan slackEvent),
			Send: make(chan connReplyMsg),
		}
		users[userID] = u
		go userReceiveMessages(ctx, u)
		go userSendMessages(ctx, u)
	}
	for {
		m := <-ctx.UserEvents
		switch v := m.(type) {
		case slackEvent:
			u := users[v.User]
			if u != (user{}) {
				go func() {
					u.Recv <- v
				}()
			}
		case connToUserMsg:
			u := users[v.UserID]
			if u != (user{}) {
				go func() {
					u.Send <- connReplyMsg{Message: v.Message}
				}()
			}
		case connToAllUsersMsg:
			for _, u := range users {
				go func(u user) {
					u.Send <- connReplyMsg{Message: v.Message}
				}(u)
			}
		default:
			log.Warningf("User event unknown: %#v", v)
		}
	}
}

func slackHandler(ctx ctx, token string) {
	origin := "https://slack.com/api/rtm.start?token=" + token
	go func() {
		ctx.Restarts <- restart{Reason: "initial start"}
	}()
	for {
		msg := <-ctx.Restarts
		log.Infof("Restarting: %#v; no of goroutines: %d", msg, runtime.NumGoroutine())
		rtm := startRtm(origin)
		log.Debugf("RTM url: %s", rtm.URL)
		ws, err := connectWs(rtm.URL, origin)
		if err != nil {
			errorMsg := "Error trying to connect to WS"
			log.Errorf("%s: %v", errorMsg, err)
			go func() {
				ctx.Restarts <- restart{Reason: errorMsg}
			}()
			go func() {
				ctx.Connections <- connMessage{Type: "restart", Msg: errorMsg}
			}()
		} else {
			go eventsProducer(ws, ctx)
		}
		time.Sleep(10 * time.Second)
	}
}

func msgToJSONMsg(msg msg) string {
	json, _ := json.Marshal(msg)
	return (string(json) + "\n")
}

func jsonToMsg(jsonString string) (msg, error) {
	var msg msg
	decodeErr := json.NewDecoder(strings.NewReader(jsonString)).Decode(&msg)
	return msg, decodeErr
}

func runClient(ctx ctx, conn net.Conn) {
	defer func() {
		log.Infof("No of goroutines: %d", runtime.NumGoroutine())
		log.Info("Closing client")
	}()
	reader := bufio.NewReader(conn)
	registered := connRegister{}
	for {
		rawMsg, err := reader.ReadString('\n')
		if err != nil {
			conn.Close()
			if registered != (connRegister{}) {
				go func() {
					ctx.Connections <- connUnregister{Name: registered.Name}
				}()
			}
			return
		}
		reply := msg{Type: "ok", Data: "ok"}
		message := strings.TrimSpace(rawMsg)
		decodedMsg, decodeErr := jsonToMsg(message)
		if decodeErr != nil {
			log.Warningf("Error while decoding message: %s; invalid message format: %s", decodeErr, message)
			reply = msg{Type: "error", Data: "invalid format"}
		} else {
			switch decodedMsg.Type {
			case "register":
				registered = connRegister{
					Name: decodedMsg.Data,
					Conn: conn,
				}
				ctx.Connections <- registered
			case "msg":
				if decodedMsg.User == "" {
					ctx.UserEvents <- connToAllUsersMsg{Message: decodedMsg.Data}
				} else {
					ctx.UserEvents <- connToUserMsg{
						UserID:  decodedMsg.User,
						Message: decodedMsg.Data,
					}
				}
			default:
				reply = msg{Type: "error", Data: "message unknown"}
			}
		}
		conn.Write([]byte(msgToJSONMsg(reply)))
		log.Debugf("message %#v", decodedMsg)
	}
}

func tcpHandler(host string, port int, ctx ctx) {
	listener, err := net.Listen("tcp", host+":"+strconv.Itoa(port))
	if err != nil {
		log.Fatalf("Error starting TCP server on host %s, port %d", host, port)
	}
	log.Infof("Started TCP listener on %s:%d", host, port)
	defer listener.Close()
	for {
		conn, _ := listener.Accept()
		go runClient(ctx, conn)
	}
}

func connectionsRegistrar(ctx ctx) {
	connections := make(map[string]net.Conn)
	for {
		connCmd := <-ctx.Connections
		switch v := connCmd.(type) {
		case connRegister:
			connections[v.Name] = v.Conn
		case connUnregister:
			delete(connections, v.Name)
		case connMessage:
			for key, conn := range connections {
				log.Infof("Sent message to connection: %s", key)
				txt := msgToJSONMsg(msg{
					Type: v.Type,
					Data: v.Msg,
					User: v.UserID,
				})
				go func(conn net.Conn) {
					conn.Write([]byte(txt))
				}(conn)
			}
		case connQuery:
			go func(cmd connQuery, conns map[string]net.Conn) {
				cmd.Reply <- conns
			}(v, connections)
		default:
			log.Infof("Unknown connection message type: %v", v)
		}
	}
}

func debugConnsHandler(ctx ctx, w http.ResponseWriter, r *http.Request) {
	query := connQuery{Reply: make(chan map[string]net.Conn)}
	go func() {
		ctx.Connections <- query
	}()
	conns := <-query.Reply
	io.WriteString(w, "conns: "+strconv.Itoa(len(conns)))
}

func main() {
	config := readConfig()
	token := config.Token
	users := config.Users
	tcpport := config.Port
	tcphost := config.Host
	var wg sync.WaitGroup
	var format = logging.MustStringFormatter(
		`%{color}%{time:2006-01-02T15:04:05.000} %{shortfunc} >>> %{level} %{id:03x} %{message}%{color:reset}`,
	)
	loggingBackend := logging.NewLogBackend(os.Stderr, "", 0)
	backend2Formatter := logging.NewBackendFormatter(loggingBackend, format)
	loggingBackendLeveled := logging.AddModuleLevel(loggingBackend)
	loggingBackendLeveled.SetLevel(logging.DEBUG, "")
	logging.SetBackend(backend2Formatter)
	ctx := ctx{
		Token:       token,
		Restarts:    make(chan restart),
		SlackEvents: make(chan string),
		UserEvents:  make(chan interface{}),
		Connections: make(chan interface{}),
	}
	wg.Add(6)
	http.HandleFunc("/debug-connections", func(w http.ResponseWriter, r *http.Request) {
		debugConnsHandler(ctx, w, r)
	})
	go func() {
		defer wg.Done()
		http.ListenAndServe("0.0.0.0:6060", nil)
	}()
	go func() {
		defer wg.Done()
		usersRegistrar(ctx, users)
	}()
	go func() {
		defer wg.Done()
		slackHandler(ctx, token)
	}()
	go func() {
		defer wg.Done()
		dispatchSlackEvents(ctx)
	}()
	go func() {
		defer wg.Done()
		tcpHandler(tcphost, tcpport, ctx)
	}()
	go func() {
		defer wg.Done()
		connectionsRegistrar(ctx)
	}()
	wg.Wait()
}
<commit_msg>Fix typo<commit_after>package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"github.com/gorilla/websocket"
	"github.com/op/go-logging"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	_ "net/http/pprof"
	"net/url"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

type slackEvent struct {
	Type      string `json:"type"`
	User      string `json:"user"`
	Text      string `json:"text"`
	Timestamp string `json:"ts"`
}

type restart struct {
	Reason string
}

type client struct {
	conn net.Conn
	name string
}

type connToUserMsg struct {
	UserID  string
	Message string
}

type connToAllUsersMsg struct {
	Message string
}

type connReplyMsg struct {
	Message string
}

type user struct {
	ID   string
	Recv chan slackEvent
	Send chan connReplyMsg
}

type connRegister struct {
	Name string
	Conn net.Conn
}

type connUnregister struct {
	Name string
}

type connMessage struct {
	Type   string
	Msg    string
	UserID string
}

type connQuery struct {
	Reply chan map[string]net.Conn
}

type msg struct {
	Type string `json:"type"`
	Data string `json:"data"`
	User string `json:"user,omitempty"`
}

type config struct {
	Token string   `json:"token"`
	Users []string `json:"users"`
	Port  int      `json:"port"`
	Host  string   `json:"host"`
}

type rtmResponse struct {
	URL string `json:"url"`
}

type ctx struct {
	Token       string
	Restarts    chan restart
	SlackEvents chan string
	UserEvents  chan interface{}
	Connections chan interface{}
}

var log = logging.MustGetLogger("main-logger")

func readConfig() config {
	config := config{
		Token: "",
		Users: []string{},
		Port:  9191,
		Host:  "localhost",
	}
	file, errReadFile := ioutil.ReadFile("config.json")
	if errReadFile != nil {
		log.Warningf("Config file error: %s; using default values.", errReadFile)
		return config
	}
	errUnmarshal := json.Unmarshal(file, &config)
	if errUnmarshal != nil {
		log.Warningf("Config file error: %s; using default values.", errUnmarshal)
		return config
	}
	return config
}

func startRtm(origin string) rtmResponse {
	resp, err := http.Get(origin)
	if err != nil {
		return rtmResponse{}
	}
	defer resp.Body.Close()
	var data rtmResponse
	json.NewDecoder(resp.Body).Decode(&data)
	return data
}

func sendPrvMessage(ctx ctx, userID string, msg string) error {
	token := ctx.Token
	httpclient := &http.Client{
		Timeout: time.Second * 3,
	}
	rsp, err := httpclient.PostForm("https://slack.com/api/chat.postMessage?token="+token, url.Values{"channel": {userID}, "as_user": {"true"}, "text": {msg}})
	if err != nil {
		log.Errorf("Error while sending private message: %s", err)
		return err
	}
	io.Copy(ioutil.Discard, rsp.Body)
	if rsp != nil {
		rsp.Body.Close()
	}
	return err
}

func connectWs(url string, origin string) (*websocket.Conn, error) {
	c, _, err := websocket.DefaultDialer.Dial(url, nil)
	return c, err
}

func eventsProducer(ws *websocket.Conn, ctx ctx) {
	ticker := time.NewTicker(10 * time.Second)
	stopPinger := make(chan struct{})
	ws.SetReadDeadline(time.Now().Add(15 * time.Second))
	ws.SetPongHandler(func(string) error {
		ws.SetReadDeadline(time.Now().Add(15 * time.Second))
		return nil
	})
	go func() {
		for {
			select {
			case <-ticker.C:
				if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
					log.Errorf("Error while writing ping: %v", err)
				}
			case <-stopPinger:
				ticker.Stop()
				return
			}
		}
	}()
	for {
		_, msg, err := ws.ReadMessage()
		if err != nil {
			log.Errorf("Error while reading message: %v", err)
			close(stopPinger)
			ws.Close()
			ctx.Restarts <- restart{Reason: "slack read message error"}
			go func() {
				ctx.Connections <- connMessage{Type: "restart", Msg: "slack read message error"}
			}()
			break
		}
		ctx.SlackEvents <- string(msg[:])
	}
}

func parseSlackEvent(jsonEvent string) (slackEvent, error) {
	var event slackEvent
	err := json.Unmarshal([]byte(jsonEvent), &event)
	if err != nil {
		return event, errors.New("Invalid event")
	}
	return event, nil
}

func dispatchSlackEvents(ctx ctx) {
	for {
		jsonEvent := <-ctx.SlackEvents
		event, _ := parseSlackEvent(jsonEvent)
		go func() {
			ctx.UserEvents <- event
		}()
	}
}

func userReceiveMessages(ctx ctx, u user) {
	for {
		m := <-u.Recv
		if m.Type == "message" {
			go func() {
				ctx.Connections <- connMessage{
					Type:   "msg",
					Msg:    m.Text,
					UserID: u.ID,
				}
			}()
		}
		log.Infof("Dispatching event: %#v, for user: %#v", m, u)
	}
}

func userSendMessages(ctx ctx, u user) {
	for {
		m := <-u.Send
		sendPrvMessage(ctx, u.ID, m.Message)
		log.Infof("Message for the user: %#v, for user: %s", m, u.ID)
	}
}

func usersRegistrar(ctx ctx, userIds []string) {
	users := make(map[string]user)
	for _, userID := range userIds {
		u := user{
			ID:   userID,
			Recv: make(chan slackEvent),
			Send: make(chan connReplyMsg),
		}
		users[userID] = u
		go userReceiveMessages(ctx, u)
		go userSendMessages(ctx, u)
	}
	for {
		m := <-ctx.UserEvents
		switch v := m.(type) {
		case slackEvent:
			u := users[v.User]
			if u != (user{}) {
				go func() {
					u.Recv <- v
				}()
			}
		case connToUserMsg:
			u := users[v.UserID]
			if u != (user{}) {
				go func() {
					u.Send <- connReplyMsg{Message: v.Message}
				}()
			}
		case connToAllUsersMsg:
			for _, u := range users {
				go func(u user) {
					u.Send <- connReplyMsg{Message: v.Message}
				}(u)
			}
		default:
			log.Warningf("User event unknown: %#v", v)
		}
	}
}

func slackHandler(ctx ctx, token string) {
	origin := "https://slack.com/api/rtm.start?token=" + token
	go func() {
		ctx.Restarts <- restart{Reason: "initial start"}
	}()
	for {
		msg := <-ctx.Restarts
		log.Infof("Restarting: %#v; no of goroutines: %d", msg, runtime.NumGoroutine())
		rtm := startRtm(origin)
		log.Debugf("RTM url: %s", rtm.URL)
		ws, err := connectWs(rtm.URL, origin)
		if err != nil {
			errorMsg := "Error trying to connect to WS"
			log.Errorf("%s: %v", errorMsg, err)
			go func() {
				ctx.Restarts <- restart{Reason: errorMsg}
			}()
			go func() {
				ctx.Connections <- connMessage{Type: "restart", Msg: errorMsg}
			}()
		} else {
			go eventsProducer(ws, ctx)
		}
		time.Sleep(10 * time.Second)
	}
}

func msgToJSONMsg(msg msg) string {
	json, _ := json.Marshal(msg)
	return (string(json) + "\n")
}

func jsonToMsg(jsonString string) (msg, error) {
	var msg msg
	decodeErr := json.NewDecoder(strings.NewReader(jsonString)).Decode(&msg)
	return msg, decodeErr
}

func runClient(ctx ctx, conn net.Conn) {
	defer func() {
		log.Infof("No of goroutines: %d", runtime.NumGoroutine())
		log.Info("Closing client")
	}()
	reader := bufio.NewReader(conn)
	registered := connRegister{}
	for {
		rawMsg, err := reader.ReadString('\n')
		if err != nil {
			conn.Close()
			if registered != (connRegister{}) {
				go func() {
					ctx.Connections <- connUnregister{Name: registered.Name}
				}()
			}
			return
		}
		reply := msg{Type: "ok", Data: "ok"}
		message := strings.TrimSpace(rawMsg)
		decodedMsg, decodeErr := jsonToMsg(message)
		if decodeErr != nil {
			log.Warningf("Error while decoding message: %s; invalid message format: %s", decodeErr, message)
			reply = msg{Type: "error", Data: "invalid format"}
		} else {
			switch decodedMsg.Type {
			case "register":
				registered = connRegister{
					Name: decodedMsg.Data,
					Conn: conn,
				}
				ctx.Connections <- registered
			case "msg":
				if decodedMsg.User == "" {
					ctx.UserEvents <- connToAllUsersMsg{Message: decodedMsg.Data}
				} else {
					ctx.UserEvents <- connToUserMsg{
						UserID:  decodedMsg.User,
						Message: decodedMsg.Data,
					}
				}
			default:
				reply = msg{Type: "error", Data: "message unknown"}
			}
		}
		conn.Write([]byte(msgToJSONMsg(reply)))
		log.Debugf("message %#v", decodedMsg)
	}
}

func tcpHandler(host string, port int, ctx ctx) {
	listener, err := net.Listen("tcp", host+":"+strconv.Itoa(port))
	if err != nil {
		log.Fatalf("Error starting TCP server on host %s, port %d", host, port)
	}
	log.Infof("Started TCP listener on %s:%d", host, port)
	defer listener.Close()
	for {
		conn, _ := listener.Accept()
		go runClient(ctx, conn)
	}
}

func connectionsRegistrar(ctx ctx) {
	connections := make(map[string]net.Conn)
	for {
		connCmd := <-ctx.Connections
		switch v := connCmd.(type) {
		case connRegister:
			connections[v.Name] = v.Conn
		case connUnregister:
			delete(connections, v.Name)
		case connMessage:
			for key, conn := range connections {
				log.Infof("Sent message to connection: %s", key)
				txt := msgToJSONMsg(msg{
					Type: v.Type,
					Data: v.Msg,
					User: v.UserID,
				})
				go func(conn net.Conn) {
					conn.Write([]byte(txt))
				}(conn)
			}
		case connQuery:
			go func(cmd connQuery, conns map[string]net.Conn) {
				cmd.Reply <- conns
			}(v, connections)
		default:
			log.Infof("Unknown connection message type: %v", v)
		}
	}
}

func debugConnsHandler(ctx ctx, w http.ResponseWriter, r *http.Request) {
	query := connQuery{Reply: make(chan map[string]net.Conn)}
	go func() {
		ctx.Connections <- query
	}()
	conns := <-query.Reply
	io.WriteString(w, "conns: "+strconv.Itoa(len(conns)))
}

func main() {
	config := readConfig()
	token := config.Token
	users := config.Users
	tcpport := config.Port
	tcphost := config.Host
	var wg sync.WaitGroup
	var format = logging.MustStringFormatter(
		`%{color}%{time:2006-01-02T15:04:05.000} %{shortfunc} >>> %{level} %{id:03x} %{message}%{color:reset}`,
	)
	loggingBackend := logging.NewLogBackend(os.Stderr, "", 0)
	backend2Formatter := logging.NewBackendFormatter(loggingBackend, format)
	loggingBackendLeveled := logging.AddModuleLevel(loggingBackend)
	loggingBackendLeveled.SetLevel(logging.DEBUG, "")
	logging.SetBackend(backend2Formatter)
	ctx := ctx{
		Token:       token,
		Restarts:    make(chan restart),
		SlackEvents: make(chan string),
		UserEvents:  make(chan interface{}),
		Connections: make(chan interface{}),
	}
	wg.Add(6)
	http.HandleFunc("/debug/connections", func(w http.ResponseWriter, r *http.Request) {
		debugConnsHandler(ctx, w, r)
	})
	go func() {
		defer wg.Done()
		http.ListenAndServe("0.0.0.0:6060", nil)
	}()
	go func() {
		defer wg.Done()
		usersRegistrar(ctx, users)
	}()
	go func() {
		defer wg.Done()
		slackHandler(ctx, token)
	}()
	go func() {
		defer wg.Done()
		dispatchSlackEvents(ctx)
	}()
	go func() {
		defer wg.Done()
		tcpHandler(tcphost, tcpport, ctx)
	}()
	go func() {
		defer wg.Done()
		connectionsRegistrar(ctx)
	}()
	wg.Wait()
}
<|endoftext|>


<commit_before>/*
Quick and simple monitoring system

Usage:
 jsonmon path.to/config.yml
 jsonmon -v # Prints version to stdout and exits

Environment:
 HOST
  - defaults to localhost
  - the JSON API network interface
 PORT
  - defaults to 3000
  - the JSON API port
 GOMAXPROCS
  - defaults to CPU number + 1
  - number of threads to start
*/
package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"gopkg.in/yaml.v2"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path"
	"regexp"
	"runtime"
	"strconv"
	"sync"
	"time"
)

// Application version
const Version = "0.9.9"

// Check details
type Check struct {
	Name   string      `json:"name,omitempty" yaml:"name"`
	Web    string      `json:"web,omitempty" yaml:"web"`
	Shell  string      `json:"shell,omitempty" yaml:"shell"`
	Match  string      `json:"-" yaml:"match"`
	Return int         `json:"-" yaml:"return"`
	Notify interface{} `json:"-" yaml:"notify"`
	Tries  int         `json:"-" yaml:"tries"`
	Repeat int         `json:"-" yaml:"repeat"`
	Failed bool        `json:"failed" yaml:"-"`
	Since  string      `json:"since,omitempty" yaml:"-"`
}

// Global checks list. Need to share it with workers and Web UI.
var checks []Check

// The main loop.
func main() {
	// Parse CLI args.
	version := flag.Bool("v", false, "print version to stdout and exit")
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage:", path.Base(os.Args[0]), "path.to/config.yml")
		fmt.Fprintln(os.Stderr, "Docs:  https://github.com/chillum/jsonmon/wiki")
	}
	flag.Parse()
	// -v for version.
	if *version {
		fmt.Println("jsonmon", Version)
		fmt.Println(runtime.Version(), runtime.GOOS, runtime.GOARCH)
		os.Exit(0)
	}
	// Should supply a config file.
	args := flag.Args()
	if len(args) < 1 {
		flag.Usage()
	}
	// Tune concurrency.
	if os.Getenv("GOMAXPROCS") == "" {
		runtime.GOMAXPROCS(runtime.NumCPU() + 1)
	}
	// Read config file or exit with error.
	config, err := ioutil.ReadFile(args[0])
	if err != nil {
		fmt.Fprintln(os.Stderr, "ERROR:", err)
		os.Exit(3)
	}
	err = yaml.Unmarshal(config, &checks)
	if err != nil {
		fmt.Fprintln(os.Stderr, "ERROR:", config[0], err)
		os.Exit(3)
	}
	// Run checks.
	var wg sync.WaitGroup
	wg.Add(1)
	for i := range checks {
		go worker(&checks[i])
	}
	// Launch the JSON API.
	host := os.Getenv("HOST")
	port := os.Getenv("PORT")
	if host == "" {
		host = "localhost"
	}
	if port == "" {
		port = "3000"
	}
	http.HandleFunc("/", display)
	err = http.ListenAndServe(host+":"+port, nil)
	if err != nil {
		fmt.Fprintln(os.Stderr, "ERROR:", err)
	}
	// Wait forever.
	wg.Wait()
}

// Background worker.
func worker(check *Check) {
	for {
		if check.Repeat == 0 { // Set default timeout.
			check.Repeat = 60
		}
		if check.Tries == 0 { // Default to 1 attempt.
			check.Tries = 1
		}
		if check.Web != "" {
			web(check)
		}
		if check.Shell != "" {
			shell(check)
		}
		time.Sleep(time.Second * time.Duration(check.Repeat))
	}
}

// Shell worker.
func shell(check *Check) {
	// Set check's display name.
	var name string
	if check.Name != "" {
		name = check.Name
	} else {
		name = check.Shell
	}
	// Execute with shell in N attemps.
	var out []byte
	var err error
	for i := 0; i < check.Tries; i++ {
		out, err = exec.Command("/bin/sh", "-c", check.Shell).CombinedOutput()
		if err == nil {
			if check.Match != "" { // Match regexp.
				var regex *regexp.Regexp
				regex, err = regexp.Compile(check.Match)
				if err == nil && !regex.Match(out) {
					err = errors.New("ERROR: output did not match " + check.Match)
				}
			}
			break
		}
	}
	// Process results.
	if err == nil {
		if check.Failed {
			check.Failed = false
			check.Since = time.Now().Format(time.RFC3339)
			alert(check.Notify, "Fixed: "+name, "")
		}
	} else {
		if !check.Failed {
			check.Failed = true
			check.Since = time.Now().Format(time.RFC3339)
			alert(check.Notify, "Failed: "+name, string(out)+err.Error())
		}
	}
}

// Web worker.
func web(check *Check) {
	// Set check's display name.
	var name string
	if check.Name != "" {
		name = check.Name
	} else {
		name = check.Web
	}
	if check.Return == 0 { // Successful HTTP return code is 200.
		check.Return = 200
	}
	// Get the URL in N attempts.
	var err error
	for i := 0; i < check.Tries; i++ {
		err = fetch(check.Web, check.Match, check.Return)
		if err == nil {
			break
		}
	}
	// Process results.
	if err == nil {
		if check.Failed {
			check.Failed = false
			check.Since = time.Now().Format(time.RFC3339)
			alert(check.Notify, "FIXED: "+name, "")
		}
	} else {
		if !check.Failed {
			check.Failed = true
			check.Since = time.Now().Format(time.RFC3339)
			alert(check.Notify, "FAILED: "+name, err.Error())
		}
	}
}

// The actual HTTP GET.
func fetch(url string, match string, code int) error {
	var err error
	var resp *http.Response
	resp, err = http.Get(url)
	if err == nil && resp.StatusCode != code {
		err = errors.New(url + " returned " + strconv.Itoa(resp.StatusCode))
	} else { // Match regexp.
		if resp != nil && match != "" {
			var regex *regexp.Regexp
			regex, err = regexp.Compile(match)
			if err == nil {
				var body []byte
				body, _ = ioutil.ReadAll(resp.Body)
				if !regex.Match(body) {
					err = errors.New(url + " output did not match " + match)
				}
			}
		}
	}
	if resp != nil {
		resp.Body.Close()
	}
	return err
}

// Logs and mail alerting.
func alert(mail interface{}, subject string, message string) {
	fmt.Println(subject)
	// Log the alerts.
	if message != "" {
		fmt.Println(message)
	}
	// Mail the alerts.
	if mail != nil {
		// Make the message.
		var rcpt string
		var ok bool
		if rcpt, ok = mail.(string); !ok {
			for i, v := range mail.([]interface{}) {
				if i != 0 {
					rcpt += ", "
				}
				rcpt += v.(string)
			}
		}
		msg := "To: " + rcpt + "\nSubject: " + subject + "\n\n"
		if message != "" {
			msg += message
		}
		msg += "\n.\n"
		// And send it.
		sendmail := exec.Command("/usr/sbin/sendmail", "-t")
		stdin, err := sendmail.StdinPipe()
		err = sendmail.Start()
		if err != nil {
			fmt.Fprintln(os.Stderr, "ERROR:", err)
		}
		io.WriteString(stdin, msg)
		sendmail.Wait()
	}
}

// Format JSON for output.
func display(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" { // Serve for root page only, 404 otherwise.
		http.NotFound(w, r)
		return
	}
	json, _ := json.MarshalIndent(&checks, "", "  ")
	w.Header().Set("Content-Type", "application/json")
	w.Write(json)
	fmt.Fprintln(w, "") // Trailing newline.
}
<commit_msg>fixed possible race condition in alert<commit_after>/*
Quick and simple monitoring system

Usage:
 jsonmon path.to/config.yml
 jsonmon -v # Prints version to stdout and exits

Environment:
 HOST
  - defaults to localhost
  - the JSON API network interface
 PORT
  - defaults to 3000
  - the JSON API port
 GOMAXPROCS
  - defaults to CPU number + 1
  - number of threads to start
*/
package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"gopkg.in/yaml.v2"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path"
	"regexp"
	"runtime"
	"strconv"
	"sync"
	"time"
)

// Application version
const Version = "0.9.9"

// Check details
type Check struct {
	Name   string      `json:"name,omitempty" yaml:"name"`
	Web    string      `json:"web,omitempty" yaml:"web"`
	Shell  string      `json:"shell,omitempty" yaml:"shell"`
	Match  string      `json:"-" yaml:"match"`
	Return int         `json:"-" yaml:"return"`
	Notify interface{} `json:"-" yaml:"notify"`
	Tries  int         `json:"-" yaml:"tries"`
	Repeat int         `json:"-" yaml:"repeat"`
	Failed bool        `json:"failed" yaml:"-"`
	Since  string      `json:"since,omitempty" yaml:"-"`
}

// Global checks list. Need to share it with workers and Web UI.
var checks []Check

// The main loop.
func main() {
	// Parse CLI args.
	version := flag.Bool("v", false, "print version to stdout and exit")
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage:", path.Base(os.Args[0]), "path.to/config.yml")
		fmt.Fprintln(os.Stderr, "Docs:  https://github.com/chillum/jsonmon/wiki")
	}
	flag.Parse()
	// -v for version.
	if *version {
		fmt.Println("jsonmon", Version)
		fmt.Println(runtime.Version(), runtime.GOOS, runtime.GOARCH)
		os.Exit(0)
	}
	// Should supply a config file.
	args := flag.Args()
	if len(args) < 1 {
		flag.Usage()
	}
	// Tune concurrency.
	if os.Getenv("GOMAXPROCS") == "" {
		runtime.GOMAXPROCS(runtime.NumCPU() + 1)
	}
	// Read config file or exit with error.
	config, err := ioutil.ReadFile(args[0])
	if err != nil {
		fmt.Fprintln(os.Stderr, "ERROR:", err)
		os.Exit(3)
	}
	err = yaml.Unmarshal(config, &checks)
	if err != nil {
		fmt.Fprintln(os.Stderr, "ERROR:", config[0], err)
		os.Exit(3)
	}
	// Run checks.
	var wg sync.WaitGroup
	wg.Add(1)
	for i := range checks {
		go worker(&checks[i])
	}
	// Launch the JSON API.
	host := os.Getenv("HOST")
	port := os.Getenv("PORT")
	if host == "" {
		host = "localhost"
	}
	if port == "" {
		port = "3000"
	}
	http.HandleFunc("/", display)
	err = http.ListenAndServe(host+":"+port, nil)
	if err != nil {
		fmt.Fprintln(os.Stderr, "ERROR:", err)
	}
	// Wait forever.
	wg.Wait()
}

// Background worker.
func worker(check *Check) {
	for {
		if check.Repeat == 0 { // Set default timeout.
			check.Repeat = 60
		}
		if check.Tries == 0 { // Default to 1 attempt.
			check.Tries = 1
		}
		if check.Web != "" {
			web(check)
		}
		if check.Shell != "" {
			shell(check)
		}
		time.Sleep(time.Second * time.Duration(check.Repeat))
	}
}

// Shell worker.
func shell(check *Check) {
	// Set check's display name.
	var name string
	if check.Name != "" {
		name = check.Name
	} else {
		name = check.Shell
	}
	// Execute with shell in N attemps.
	var out []byte
	var err error
	for i := 0; i < check.Tries; i++ {
		out, err = exec.Command("/bin/sh", "-c", check.Shell).CombinedOutput()
		if err == nil {
			if check.Match != "" { // Match regexp.
				var regex *regexp.Regexp
				regex, err = regexp.Compile(check.Match)
				if err == nil && !regex.Match(out) {
					err = errors.New("ERROR: output did not match " + check.Match)
				}
			}
			break
		}
	}
	// Process results.
	if err == nil {
		if check.Failed {
			check.Failed = false
			check.Since = time.Now().Format(time.RFC3339)
			alert(check.Notify, "Fixed: "+name, "")
		}
	} else {
		if !check.Failed {
			check.Failed = true
			check.Since = time.Now().Format(time.RFC3339)
			alert(check.Notify, "Failed: "+name, string(out)+err.Error())
		}
	}
}

// Web worker.
func web(check *Check) {
	// Set check's display name.
	var name string
	if check.Name != "" {
		name = check.Name
	} else {
		name = check.Web
	}
	if check.Return == 0 { // Successful HTTP return code is 200.
		check.Return = 200
	}
	// Get the URL in N attempts.
	var err error
	for i := 0; i < check.Tries; i++ {
		err = fetch(check.Web, check.Match, check.Return)
		if err == nil {
			break
		}
	}
	// Process results.
	if err == nil {
		if check.Failed {
			check.Failed = false
			check.Since = time.Now().Format(time.RFC3339)
			alert(check.Notify, "FIXED: "+name, "")
		}
	} else {
		if !check.Failed {
			check.Failed = true
			check.Since = time.Now().Format(time.RFC3339)
			alert(check.Notify, "FAILED: "+name, err.Error())
		}
	}
}

// The actual HTTP GET.
func fetch(url string, match string, code int) error {
	var err error
	var resp *http.Response
	resp, err = http.Get(url)
	if err == nil && resp.StatusCode != code {
		err = errors.New(url + " returned " + strconv.Itoa(resp.StatusCode))
	} else { // Match regexp.
		if resp != nil && match != "" {
			var regex *regexp.Regexp
			regex, err = regexp.Compile(match)
			if err == nil {
				var body []byte
				body, _ = ioutil.ReadAll(resp.Body)
				if !regex.Match(body) {
					err = errors.New(url + " output did not match " + match)
				}
			}
		}
	}
	if resp != nil {
		resp.Body.Close()
	}
	return err
}

// Logs and mail alerting.
func alert(mail interface{}, subject string, message string) {
	// Log the alerts.
	if message == "" {
		fmt.Println(subject)
	} else {
		fmt.Println(subject + "\n" + message)
	}
	// Mail the alerts.
	if mail != nil {
		// Make the message.
		var rcpt string
		var ok bool
		if rcpt, ok = mail.(string); !ok {
			for i, v := range mail.([]interface{}) {
				if i != 0 {
					rcpt += ", "
				}
				rcpt += v.(string)
			}
		}
		msg := "To: " + rcpt + "\nSubject: " + subject + "\n\n"
		if message != "" {
			msg += message
		}
		msg += "\n.\n"
		// And send it.
		sendmail := exec.Command("/usr/sbin/sendmail", "-t")
		stdin, err := sendmail.StdinPipe()
		err = sendmail.Start()
		if err != nil {
			fmt.Fprintln(os.Stderr, "ERROR:", err)
		}
		io.WriteString(stdin, msg)
		sendmail.Wait()
	}
}

// Format JSON for output.
func display(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" { // Serve for root page only, 404 otherwise.
		http.NotFound(w, r)
		return
	}
	json, _ := json.MarshalIndent(&checks, "", "  ")
	w.Header().Set("Content-Type", "application/json")
	w.Write(json)
	fmt.Fprintln(w, "") // Trailing newline.
}
<|endoftext|>


<commit_before>// Copyright 2015 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 mathstats

import (
	"math"
	"sort"
)

// Sample is a collection of possibly weighted data points.
type Sample struct {
	// Xs is the slice of sample values.
	Xs []float64

	// Weights[i] is the weight of sample Xs[i].  If Weights is
	// nil, all Xs have weight 1.  Weights must have the same
	// length of Xs and all values must be non-negative.
	Weights []float64

	// Sorted indicates that Xs is sorted in ascending order.
	Sorted bool
}

// Bounds returns the minimum and maximum values of xs.
func Bounds(xs []float64) (min float64, max float64) {
	if len(xs) == 0 {
		return math.NaN(), math.NaN()
	}
	min, max = xs[0], xs[0]
	for _, x := range xs {
		if x < min {
			min = x
		}
		if x > max {
			max = x
		}
	}
	return
}

// Bounds returns the minimum and maximum values of the Sample.
//
// If the Sample is weighted, this ignores samples with zero weight.
//
// This is constant time if s.Sorted and there are no zero-weighted
// values.
func (s Sample) Bounds() (min float64, max float64) {
	if len(s.Xs) == 0 || (!s.Sorted && s.Weights == nil) {
		return Bounds(s.Xs)
	}

	if s.Sorted {
		if s.Weights == nil {
			return s.Xs[0], s.Xs[len(s.Xs)-1]
		}
		min, max = math.NaN(), math.NaN()
		for i, w := range s.Weights {
			if w != 0 {
				min = s.Xs[i]
				break
			}
		}
		if math.IsNaN(min) {
			return
		}
		for i := range s.Weights {
			if s.Weights[len(s.Weights)-i-1] != 0 {
				max = s.Xs[len(s.Weights)-i-1]
				break
			}
		}
	} else {
		min, max = math.Inf(1), math.Inf(-1)
		for i, x := range s.Xs {
			w := s.Weights[i]
			if x < min && w != 0 {
				min = x
			}
			if x > max && w != 0 {
				max = x
			}
		}
		if math.IsInf(min, 0) {
			min, max = math.NaN(), math.NaN()
		}
	}
	return
}

// vecSum returns the sum of xs.
func vecSum(xs []float64) float64 {
	sum := 0.0
	for _, x := range xs {
		sum += x
	}
	return sum
}

// Sum returns the (possibly weighted) sum of the Sample.
func (s Sample) Sum() float64 {
	if s.Weights == nil {
		return vecSum(s.Xs)
	}
	sum := 0.0
	for i, x := range s.Xs {
		sum += x * s.Weights[i]
	}
	return sum
}

// Weight returns the total weight of the Sasmple.
func (s Sample) Weight() float64 {
	if s.Weights == nil {
		return float64(len(s.Xs))
	}
	return vecSum(s.Weights)
}

// Mean returns the arithmetic mean of xs.
func Mean(xs []float64) float64 {
	if len(xs) == 0 {
		return math.NaN()
	}
	m := 0.0
	for i, x := range xs {
		m += (x - m) / float64(i+1)
	}
	return m
}

// Mean returns the arithmetic mean of the Sample.
func (s Sample) Mean() float64 {
	if len(s.Xs) == 0 || s.Weights == nil {
		return Mean(s.Xs)
	}

	m, wsum := 0.0, 0.0
	for i, x := range s.Xs {
		// Use weighted incremental mean:
		//   m_i = (1 - w_i/wsum_i) * m_(i-1) + (w_i/wsum_i) * x_i
		//       = m_(i-1) + (x_i - m_(i-1)) * (w_i/wsum_i)
		w := s.Weights[i]
		wsum += w
		m += (x - m) * w / wsum
	}
	return m
}

// GeoMean returns the geometric mean of xs. xs must be positive.
func GeoMean(xs []float64) float64 {
	if len(xs) == 0 {
		return math.NaN()
	}
	m := 0.0
	for i, x := range xs {
		if x <= 0 {
			return math.NaN()
		}
		lx := math.Log(x)
		m += (lx - m) / float64(i+1)
	}
	return math.Exp(m)
}

// GeoMean returns the geometric mean of the Sample. All samples
// values must be positive.
func (s Sample) GeoMean() float64 {
	if len(s.Xs) == 0 || s.Weights == nil {
		return GeoMean(s.Xs)
	}

	m, wsum := 0.0, 0.0
	for i, x := range s.Xs {
		w := s.Weights[i]
		wsum += w
		lx := math.Log(x)
		m += (lx - m) * w / wsum
	}
	return math.Exp(m)
}

// Variance returns the sample variance of xs.
func Variance(xs []float64) float64 {
	if len(xs) == 0 {
		return math.NaN()
	} else if len(xs) <= 1 {
		return 0
	}

	// Based on Wikipedia's presentation of Welford 1962
	// (http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm).
	// This is more numerically stable than the standard two-pass
	// formula and not prone to massive cancellation.
	mean, M2 := 0.0, 0.0
	for n, x := range xs {
		delta := x - mean
		mean += delta / float64(n+1)
		M2 += delta * (x - mean)
	}
	return M2 / float64(len(xs)-1)
}

func (s Sample) Variance() float64 {
	if len(s.Xs) == 0 || s.Weights == nil {
		return Variance(s.Xs)
	}
	// TODO(austin)
	panic("Weighted Variance not implemented")
}

// StdDev returns the sample standard deviation of xs.
func StdDev(xs []float64) float64 {
	return math.Sqrt(Variance(xs))
}

// StdDev returns the sample standard deviation of the Sample.
func (s Sample) StdDev() float64 {
	if len(s.Xs) == 0 || s.Weights == nil {
		return StdDev(s.Xs)
	}
	// TODO(austin)
	panic("Weighted StdDev not implemented")
}

// Percentile returns the pctileth value from the Sample. This uses
// interpolation method R8 from Hyndman and Fan (1996).
//
// pctile will be capped to the range [0, 1]. If len(xs) == 0 or all
// weights are 0, returns NaN.
//
// Percentile(0.5) is the median. Percentile(0.25) and
// Percentile(0.75) are the first and third quartiles, respectively.
//
// This is constant time if s.Sorted and s.Weights == nil.
func (s *Sample) Percentile(pctile float64) float64 {
	if len(s.Xs) == 0 {
		return math.NaN()
	} else if pctile <= 0 {
		min, _ := s.Bounds()
		return min
	} else if pctile >= 1 {
		_, max := s.Bounds()
		return max
	}

	if !s.Sorted {
		s.Sort()
	}

	if s.Weights == nil {
		N := float64(len(s.Xs))
		//n := pctile * (N + 1) // R6
		n := 1/3.0 + pctile*(N+1/3.0) // R8
		kf, frac := math.Modf(n)
		k := int(kf)
		if k <= 0 {
			return s.Xs[0]
		} else if k >= len(s.Xs) {
			return s.Xs[len(s.Xs)-1]
		}
		return s.Xs[k-1] + frac*(s.Xs[k]-s.Xs[k-1])
	} else {
		// TODO(austin): Implement interpolation

		target := s.Weight() * pctile

		// TODO(austin) If we had cumulative weights, we could
		// do this in log time.
		for i, weight := range s.Weights {
			target -= weight
			if target < 0 {
				return s.Xs[i]
			}
		}
		return s.Xs[len(s.Xs)-1]
	}
}

// IQR returns the interquartile range of the Sample.
//
// This is constant time if s.Sorted and s.Weights == nil.
func (s Sample) IQR() float64 {
	if !s.Sorted {
		s = *s.Copy().Sort()
	}
	return s.Percentile(0.75) - s.Percentile(0.25)
}

type sampleSorter struct {
	xs      []float64
	weights []float64
}

func (p *sampleSorter) Len() int {
	return len(p.xs)
}

func (p *sampleSorter) Less(i, j int) bool {
	return p.xs[i] < p.xs[j]
}

func (p *sampleSorter) Swap(i, j int) {
	p.xs[i], p.xs[j] = p.xs[j], p.xs[i]
	p.weights[i], p.weights[j] = p.weights[j], p.weights[i]
}

// Sort sorts the samples in place in s and returns s.
//
// A sorted sample improves the performance of some algorithms.
func (s *Sample) Sort() *Sample {
	if s.Sorted || sort.Float64sAreSorted(s.Xs) {
		// All set
	} else if s.Weights == nil {
		sort.Float64s(s.Xs)
	} else {
		sort.Sort(&sampleSorter{s.Xs, s.Weights})
	}
	s.Sorted = true
	return s
}

// Copy returns a copy of the Sample.
//
// The returned Sample shares no data with the original, so they can
// be modified (for example, sorted) independently.
func (s Sample) Copy() *Sample {
	xs := make([]float64, len(s.Xs))
	copy(xs, s.Xs)

	weights := []float64(nil)
	if s.Weights != nil {
		weights = make([]float64, len(s.Weights))
		copy(weights, s.Weights)
	}

	return &Sample{xs, weights, s.Sorted}
}

func (s *Sample) FilterOutliers() {
	rvalues := make([]float64, 0, len(s.Xs))
	// Discard outliers.
	q1, q3 := s.Percentile(0.25), s.Percentile(0.75)
	lo, hi := q1-1.5*(q3-q1), q3+1.5*(q3-q1)
	for _, value := range s.Xs {
		if lo <= value && value <= hi {
			rvalues = append(rvalues, value)
		}
	}
	s.Xs = rvalues
}
<commit_msg>mathstats: remove weight from Sample<commit_after>// Copyright 2015 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 mathstats

import (
	"math"
	"sort"
)

// Sample is a collection of possibly weighted data points.
type Sample struct {
	// Xs is the slice of sample values.
	Xs []float64

	// Sorted indicates that Xs is sorted in ascending order.
	Sorted bool
}

// Bounds returns the minimum and maximum values of xs.
func Bounds(xs []float64) (min float64, max float64) {
	if len(xs) == 0 {
		return math.NaN(), math.NaN()
	}
	min, max = xs[0], xs[0]
	for _, x := range xs {
		if x < min {
			min = x
		}
		if x > max {
			max = x
		}
	}
	return
}

// Bounds returns the minimum and maximum values of the Sample.
//
// If the Sample is weighted, this ignores samples with zero weight.
//
// This is constant time if s.Sorted and there are no zero-weighted
// values.
func (s Sample) Bounds() (min float64, max float64) {
	if len(s.Xs) == 0 || !s.Sorted {
		return Bounds(s.Xs)
	}
	return s.Xs[0], s.Xs[len(s.Xs)-1]
}

// vecSum returns the sum of xs.
func vecSum(xs []float64) float64 {
	sum := 0.0
	for _, x := range xs {
		sum += x
	}
	return sum
}

// Sum returns the (possibly weighted) sum of the Sample.
func (s Sample) Sum() float64 {
	return vecSum(s.Xs)
}

// Weight returns the total weight of the Sasmple.
func (s Sample) Weight() float64 {
	return float64(len(s.Xs))
}

// Mean returns the arithmetic mean of xs.
func Mean(xs []float64) float64 {
	if len(xs) == 0 {
		return math.NaN()
	}
	m := 0.0
	for i, x := range xs {
		m += (x - m) / float64(i+1)
	}
	return m
}

// Mean returns the arithmetic mean of the Sample.
func (s Sample) Mean() float64 {
	return Mean(s.Xs)
}

// GeoMean returns the geometric mean of xs. xs must be positive.
func GeoMean(xs []float64) float64 {
	if len(xs) == 0 {
		return math.NaN()
	}
	m := 0.0
	for i, x := range xs {
		if x <= 0 {
			return math.NaN()
		}
		lx := math.Log(x)
		m += (lx - m) / float64(i+1)
	}
	return math.Exp(m)
}

// GeoMean returns the geometric mean of the Sample. All samples
// values must be positive.
func (s Sample) GeoMean() float64 {
	return GeoMean(s.Xs)
}

// Variance returns the sample variance of xs.
func Variance(xs []float64) float64 {
	if len(xs) == 0 {
		return math.NaN()
	} else if len(xs) <= 1 {
		return 0
	}

	// Based on Wikipedia's presentation of Welford 1962
	// (http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm).
	// This is more numerically stable than the standard two-pass
	// formula and not prone to massive cancellation.
	mean, M2 := 0.0, 0.0
	for n, x := range xs {
		delta := x - mean
		mean += delta / float64(n+1)
		M2 += delta * (x - mean)
	}
	return M2 / float64(len(xs)-1)
}

// Variance returns the variance of xs
func (s Sample) Variance() float64 {
	return Variance(s.Xs)
}

// StdDev returns the sample standard deviation of xs.
func StdDev(xs []float64) float64 {
	return math.Sqrt(Variance(xs))
}

// StdDev returns the sample standard deviation of the Sample.
func (s Sample) StdDev() float64 {
	return StdDev(s.Xs)
}

// Percentile returns the pctileth value from the Sample. This uses
// interpolation method R8 from Hyndman and Fan (1996).
//
// pctile will be capped to the range [0, 1]. If len(xs) == 0 or all
// weights are 0, returns NaN.
//
// Percentile(0.5) is the median. Percentile(0.25) and
// Percentile(0.75) are the first and third quartiles, respectively.
//
// This is constant time if s.Sorted and s.Weights == nil.
func (s *Sample) Percentile(pctile float64) float64 {
	if len(s.Xs) == 0 {
		return math.NaN()
	} else if pctile <= 0 {
		min, _ := s.Bounds()
		return min
	} else if pctile >= 1 {
		_, max := s.Bounds()
		return max
	}

	if !s.Sorted {
		s.Sort()
	}

	N := float64(len(s.Xs))
	//n := pctile * (N + 1) // R6
	n := 1/3.0 + pctile*(N+1/3.0) // R8
	kf, frac := math.Modf(n)
	k := int(kf)
	if k <= 0 {
		return s.Xs[0]
	} else if k >= len(s.Xs) {
		return s.Xs[len(s.Xs)-1]
	}
	return s.Xs[k-1] + frac*(s.Xs[k]-s.Xs[k-1])
}

// IQR returns the interquartile range of the Sample.
//
// This is constant time if s.Sorted and s.Weights == nil.
func (s Sample) IQR() float64 {
	if !s.Sorted {
		s = *s.Copy().Sort()
	}
	return s.Percentile(0.75) - s.Percentile(0.25)
}

// Sort sorts the samples in place in s and returns s.
//
// A sorted sample improves the performance of some algorithms.
func (s *Sample) Sort() *Sample {
	if s.Sorted || sort.Float64sAreSorted(s.Xs) {
		// All set
	} else {
		sort.Float64s(s.Xs)
	}
	s.Sorted = true
	return s
}

// Copy returns a copy of the Sample.
//
// The returned Sample shares no data with the original, so they can
// be modified (for example, sorted) independently.
func (s Sample) Copy() *Sample {
	xs := make([]float64, len(s.Xs))
	copy(xs, s.Xs)
	return &Sample{xs, s.Sorted}
}

// FilterOutliers updates this sample in-place by removing all the values that are outliers
func (s *Sample) FilterOutliers() {
	// Discard outliers.
	q1, q3 := s.Percentile(0.25), s.Percentile(0.75)
	lo, hi := q1-1.5*(q3-q1), q3+1.5*(q3-q1)
	nn := 0
	for _, value := range s.Xs {
		if lo <= value && value <= hi {
			s.Xs[nn] = value
			nn++
		}
	}
	s.Xs = s.Xs[:nn]
}

// Clear resets this sample so it contains 0 values
func (s *Sample) Clear() {
	s.Xs = s.Xs[:0]
	s.Sorted = false
}
<|endoftext|>


<commit_before>package main

import(
	"io/ioutil"
	"encoding/json"
	"net/http"
	"os"
	"io"
	"text/template"
	"fmt"
	"strconv"
	"flag"
)

// Account Model
type AccountEntry struct {
	Username string
	Password string
	SuccessRate string `json:"success_rate"`
	Upvotes string
	Posted string
}

// Account Collection
type AccountBook struct {
	Accounts []*AccountEntry
}

func main(){

	flag.Usage = usage
	flag.Parse()

	args:=flag.Args()

	if len(args) != 1 {
		usage()
	}

	query := args[0]

	accountBook,_ := bmn(query)
	if len(accountBook.Accounts) > 0 {
		printMessage(os.Stdout,strconv.Itoa(len(accountBook.Accounts))+" accounts for "+query)
	} else {
		printMessage(os.Stdout,"No accounts for this domain.")
	}

	for _,account := range accountBook.Accounts {
		printAccount(os.Stdout,account)
	}
}

// Usage

const usageTmpl = `BMN is a command-line utility to find logins/passwords for websites that force you to register.
Usage:
  bmn [website]
`

func usage() {
	printUsage(os.Stderr)
	os.Exit(2)
}

func printUsage(w io.Writer) {
	tmpl(w, usageTmpl, nil)
}

// Result

const accountTmpl = `
	Username: {{.Username}}
	Password: {{.Password}}
	SuccessRate: {{.SuccessRate}}
	Upvotes: {{.Upvotes}}
	Posted: {{.Posted}}

`

func printAccount(w io.Writer,a *AccountEntry){
	tmpl(w,accountTmpl,a)
}

// Helpers

// Messages

const messageTmpl = `
	{{.Message}}
`
// printing message on command line
func printMessage(w io.Writer,msg string) {
	tmpl(w,messageTmpl,struct {Message string}{msg})
}

func printErr(err error) {
	fmt.Fprintln(os.Stderr, err.Error())
	os.Exit(2)
}

const BASE_URL = "http://bugmenotapi.herokuapp.com/"

// Fetching data from bmn webservice
func bmn(website string)(*AccountBook,error) {

	res, err := http.Get(BASE_URL+website)
	if err != nil {
		return nil,err
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		return nil,err
	}
	var accountBook AccountBook
	json.Unmarshal(body,&accountBook)

	return &accountBook,nil
}

// Output template
func tmpl(w io.Writer, text string, data interface{}) {
	t := template.New("top")
	template.Must(t.Parse(text))
	if err := t.Execute(w, data); err != nil {
		panic(err)
	}
}
<commit_msg>updated comments<commit_after>package main

import(
	"io/ioutil"
	"encoding/json"
	"net/http"
	"os"
	"io"
	"text/template"
	"fmt"
	"strconv"
	"flag"
)

// Account Model
type AccountEntry struct {
	Username string
	Password string
	SuccessRate string `json:"success_rate"`
	Upvotes string
	Posted string
}

// Account Collection
type AccountBook struct {
	Accounts []*AccountEntry
}

func main(){

	flag.Usage = usage
	flag.Parse()

	args:=flag.Args()

	if len(args) != 1 {
		usage()
	}

	query := args[0]

	accountBook,_ := bmn(query)
	if len(accountBook.Accounts) > 0 {
		printMessage(os.Stdout,strconv.Itoa(len(accountBook.Accounts))+" accounts for "+query)
	} else {
		printMessage(os.Stdout,"No accounts for this domain.")
	}

	for _,account := range accountBook.Accounts {
		printAccount(os.Stdout,account)
	}
}

// Usage

const usageTmpl = `BMN is a command-line utility to find logins/passwords for websites that force you to register.
Usage:
  bmn [website]
`

func usage() {
	printUsage(os.Stderr)
	os.Exit(2)
}

func printUsage(w io.Writer) {
	tmpl(w, usageTmpl, nil)
}

// Result

const accountTmpl = `
	Username: {{.Username}}
	Password: {{.Password}}
	SuccessRate: {{.SuccessRate}}
	Upvotes: {{.Upvotes}}
	Posted: {{.Posted}}

`

func printAccount(w io.Writer,a *AccountEntry){
	tmpl(w,accountTmpl,a)
}

// Helper Functions

// Messages

const messageTmpl = `
	{{.Message}}
`
// printing message on command line
func printMessage(w io.Writer,msg string) {
	tmpl(w,messageTmpl,struct {Message string}{msg})
}

func printErr(err error) {
	fmt.Fprintln(os.Stderr, err.Error())
	os.Exit(2)
}

const BASE_URL = "http://bugmenotapi.herokuapp.com/"

// Fetching data from bmn webservice
func bmn(website string)(*AccountBook,error) {

	res, err := http.Get(BASE_URL+website)
	if err != nil {
		return nil,err
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		return nil,err
	}
	var accountBook AccountBook
	json.Unmarshal(body,&accountBook)

	return &accountBook,nil
}

// Output template
func tmpl(w io.Writer, text string, data interface{}) {
	t := template.New("top")
	template.Must(t.Parse(text))
	if err := t.Execute(w, data); err != nil {
		panic(err)
	}
}
<|endoftext|>