├── .gitignore ├── config.yaml ├── test ├── tests.bash ├── dns-request.sh └── http │ └── index.html ├── cmd ├── dogstatsd_test.go ├── dogstatsd.go ├── match.go ├── dnsmasq_full_logs.go ├── goshe_test.go ├── root.go ├── dnsmasq.go ├── ping.go ├── util.go ├── goshe.go ├── apache_test.go ├── tail.go ├── apache.go └── dnsmasq_signal_stats.go ├── glide.yaml ├── wercker.yml ├── Makefile ├── main.go ├── Vagrantfile ├── glide.lock ├── README.md ├── CHANGELOG.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .envrc 3 | .vagrant 4 | vendor/ 5 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # dogtstatsd_address: 127.0.0.1:8125 3 | apache_status: http://127.0.0.1:8181/server-status 4 | -------------------------------------------------------------------------------- /test/tests.bash: -------------------------------------------------------------------------------- 1 | 2 | trap "make test_cleanup" INT TERM EXIT 3 | 4 | export GOSHE_DEBUG=1 5 | 6 | T_06runbinary() { 7 | result="$(bin/goshe)" 8 | } 9 | -------------------------------------------------------------------------------- /cmd/dogstatsd_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDogStatsdSetup(t *testing.T) { 8 | connection := DogStatsdSetup() 9 | if connection == nil { 10 | t.Error("Did not setup DogStatsd connection.") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/dns-request.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while [ 1 ]; 4 | do 5 | dig kafka.service.consul +short 6 | dig cassandra.service.consul +short; 7 | dig goshe.service.consul +short; 8 | dig vagrant.service.consul +short; 9 | dig datadog.service.consul +short; 10 | done 11 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/darron/goshe 2 | import: 3 | - package: github.com/DataDog/datadog-go 4 | subpackages: 5 | - statsd 6 | - package: github.com/cloudfoundry/gosigar 7 | - package: github.com/davecgh/go-spew 8 | subpackages: 9 | - spew 10 | - package: github.com/hpcloud/tail 11 | - package: github.com/spf13/cobra 12 | - package: github.com/spf13/viper 13 | - package: github.com/tatsushid/go-fastping 14 | - package: github.com/yhat/scrape 15 | - package: golang.org/x/net 16 | subpackages: 17 | - html 18 | - html/atom 19 | -------------------------------------------------------------------------------- /cmd/dogstatsd.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/DataDog/datadog-go/statsd" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | const ( 12 | // DogStatsdAddr is the default address for Dogstatsd. 13 | DogStatsdAddr = "127.0.0.1:8125" 14 | ) 15 | 16 | // DogConnect sets up a connection and sets standard tags. 17 | func DogConnect() *statsd.Client { 18 | connection := DogStatsdSetup() 19 | return connection 20 | } 21 | 22 | // DogStatsdSetup sets up a connection to DogStatsd. 23 | func DogStatsdSetup() *statsd.Client { 24 | dogstatsd := "" 25 | if dogstatsd = viper.GetString("dogtstatsd_address"); dogstatsd == "" { 26 | dogstatsd = DogStatsdAddr 27 | } 28 | c, err := statsd.New(dogstatsd) 29 | if err != nil { 30 | Log(fmt.Sprintf("DogStatsdSetup Error: %#v", err), "info") 31 | } 32 | c.Namespace = fmt.Sprintf("%s.", MetricPrefix) 33 | return c 34 | } 35 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: darron/go-wercker 2 | build: 3 | steps: 4 | # Build the project 5 | - script: 6 | name: go build 7 | code: | 8 | make deps 9 | make build 10 | make test 11 | after-steps: 12 | - wantedly/pretty-slack-notify: 13 | webhook_url: $SLACK_WEBHOOK_URL 14 | 15 | deploy: 16 | steps: 17 | - script: 18 | name: package build 19 | code: | 20 | cd build 21 | sudo fpm-cook install-deps 22 | sudo fpm-cook 23 | PACKAGECLOUD=$(eval echo "\$PACKAGECLOUD_TOKEN") && echo -e "$PACKAGECLOUD" > ~/.packagecloud && chmod 600 ~/.packagecloud 24 | DEB_FILE=$(find . -name '*.deb') && package_cloud push darron/goshe/ubuntu/trusty $DEB_FILE 25 | DEB_FILE=$(find . -name '*.deb') && package_cloud push darron/goshe/ubuntu/precise $DEB_FILE 26 | after-steps: 27 | - wantedly/pretty-slack-notify: 28 | webhook_url: $SLACK_WEBHOOK_URL 29 | -------------------------------------------------------------------------------- /cmd/match.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | "os" 9 | "time" 10 | ) 11 | 12 | var matchCmd = &cobra.Command{ 13 | Use: "match", 14 | Short: "Grab memory stats from matching processes and sends to Datadog.", 15 | Long: `Grab memory stats from matching processes and sends to Datadog.`, 16 | PreRun: func(cmd *cobra.Command, args []string) { 17 | checkMatchFlags() 18 | }, 19 | Run: startMatch, 20 | } 21 | 22 | func startMatch(cmd *cobra.Command, args []string) { 23 | for { 24 | matches := GetMatches(ProcessName, true) 25 | if matches != nil { 26 | fmt.Printf("Found %d matches.\n", len(matches)-1) 27 | SendMetrics(matches) 28 | } else { 29 | fmt.Printf("Did not find any matches.\n") 30 | } 31 | time.Sleep(time.Duration(Interval) * time.Second) 32 | } 33 | } 34 | 35 | func checkMatchFlags() { 36 | if ProcessName == "" { 37 | fmt.Println("Need to specify the process to search for: -p") 38 | os.Exit(1) 39 | } 40 | fmt.Println("Press CTRL-C to shutdown.") 41 | } 42 | 43 | func init() { 44 | RootCmd.AddCommand(matchCmd) 45 | } 46 | -------------------------------------------------------------------------------- /cmd/dnsmasq_full_logs.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/DataDog/datadog-go/statsd" 8 | "github.com/hpcloud/tail" 9 | "strings" 10 | ) 11 | 12 | func dnsmasqFullLogsStats(t *tail.Tail, dog *statsd.Client) { 13 | for line := range t.Lines { 14 | content := strings.Split(line.Text, "]: ")[1] 15 | if strings.HasPrefix(content, "/") { 16 | SendLineStats(dog, content, "hosts") 17 | continue 18 | } 19 | if strings.HasPrefix(content, "query") { 20 | SendLineStats(dog, content, "query") 21 | continue 22 | } 23 | if strings.HasPrefix(content, "cached") { 24 | SendLineStats(dog, content, "cached") 25 | continue 26 | } 27 | if strings.HasPrefix(content, "forwarded") { 28 | SendLineStats(dog, content, "forwarded") 29 | continue 30 | } 31 | if strings.HasPrefix(content, "reply") { 32 | SendLineStats(dog, content, "reply") 33 | continue 34 | } 35 | } 36 | } 37 | 38 | // SendLineStats sends the stats to Datadog. 39 | func SendLineStats(dog *statsd.Client, line string, metric string) { 40 | Log(fmt.Sprintf("%s: %s", metric, line), "debug") 41 | oldTags := dog.Tags 42 | dog.Tags = append(dog.Tags, fmt.Sprintf("record:%s", metric)) 43 | dog.Count("dnsmasq.event", 1, dog.Tags, 1) 44 | dog.Tags = oldTags 45 | } 46 | -------------------------------------------------------------------------------- /cmd/goshe_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetPIDs(t *testing.T) { 8 | pids := GetPIDs() 9 | if pids == nil { 10 | t.Error("Got a nil *sigar.ProcList.") 11 | } 12 | } 13 | 14 | func TestConvertProcessList(t *testing.T) { 15 | pids := GetPIDs() 16 | processes := ConvertProcessList(pids) 17 | if processes == nil { 18 | t.Error("Got a nil *[]ProcessList.") 19 | } 20 | } 21 | 22 | func TestMatchProcessList(t *testing.T) { 23 | match := "go" 24 | pids := GetPIDs() 25 | processes := ConvertProcessList(pids) 26 | matches := MatchProcessList(*processes, match, true) 27 | if matches == nil { 28 | t.Error("Got nill matches") 29 | } 30 | } 31 | 32 | func TestGetProcessList(t *testing.T) { 33 | processes := GetProcessList() 34 | if processes == nil { 35 | t.Error("Didn't get any processes.") 36 | } 37 | } 38 | 39 | func TestGetMatches(t *testing.T) { 40 | match := "go" 41 | matches := GetMatches(match, true) 42 | if matches == nil { 43 | t.Error("Got no matches.") 44 | } 45 | } 46 | 47 | func TestSendMetrics(t *testing.T) { 48 | matches := GetMatches("go", true) 49 | if matches != nil { 50 | result := SendMetrics(matches) 51 | if !result { 52 | t.Error("Did not send Go RSS Metrics.") 53 | } 54 | } else { 55 | t.Error("Didn't find any matches.") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // RootCmd is the default Cobra struct that starts it all off. 11 | // https://github.com/spf13/cobra 12 | var RootCmd = &cobra.Command{ 13 | Use: "goshe", 14 | Short: "Additional stats to datadog.", 15 | Long: `Additional stats to datadog.`, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | fmt.Println("`goshe -h` for help information.") 18 | fmt.Println("`goshe -v` for version information.") 19 | }, 20 | } 21 | 22 | var ( 23 | // Direction adds information about which command is running to the logs. 24 | Direction string 25 | 26 | // Verbose logs all output to stdout. 27 | Verbose bool 28 | 29 | // ProcessName is the process to match. 30 | ProcessName string 31 | 32 | // MetricPrefix prefixes all metrics emitted. 33 | MetricPrefix string 34 | 35 | // Interval is the amount of seconds to loop. 36 | Interval int 37 | ) 38 | 39 | func init() { 40 | Direction = SetDirection() 41 | LoadConfig() 42 | RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "", false, "log output to stdout") 43 | RootCmd.PersistentFlags().StringVarP(&ProcessName, "process", "p", "", "Process name to match.") 44 | RootCmd.PersistentFlags().StringVarP(&MetricPrefix, "prefix", "", "goshe", "Metric prefix.") 45 | RootCmd.PersistentFlags().IntVarP(&Interval, "interval", "i", 5, "Interval when running in a loop.") 46 | } 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOSHE_VERSION="0.5-dev" 2 | GIT_COMMIT=$(shell git rev-parse HEAD) 3 | COMPILE_DATE=$(shell date -u +%Y%m%d.%H%M%S) 4 | BUILD_FLAGS=-X main.CompileDate=$(COMPILE_DATE) -X main.GitCommit=$(GIT_COMMIT) -X main.Version=$(GOSHE_VERSION) 5 | UNAME=$(shell uname -s | tr '[:upper:]' '[:lower:]') 6 | ARCH=$(shell arch) 7 | 8 | all: build ## Build goshe. 9 | 10 | deps: ## Install goshe dependencies. 11 | go get -u github.com/progrium/basht 12 | go get -u github.com/davecgh/go-spew/spew 13 | go get -u github.com/hpcloud/tail/... 14 | go get -u github.com/darron/goshe 15 | 16 | format: ## Format the code with gofmt. 17 | gofmt -w . 18 | 19 | clean: ## Remove the compiled goshe binary. 20 | rm -f bin/goshe || true 21 | 22 | build: clean ## Remove the compiled binary and build. 23 | go build -ldflags "$(BUILD_FLAGS)" -o bin/goshe main.go 24 | 25 | gzip: ## Gzip and rename the goshe binary according to the version, platform and architecture. 26 | gzip bin/goshe 27 | mv bin/goshe.gz bin/goshe-$(GOSHE_VERSION)-$(UNAME)-$(ARCH).gz 28 | 29 | release: clean build gzip ## Make a complete release: clean, build and gzip. 30 | 31 | unit: ## Run the unit tests. 32 | cd cmd && go test -v -cover 33 | 34 | test: unit wercker ## Run all tests, unit and integration. 35 | 36 | test_cleanup: ## Cleanup after the test run. 37 | echo "All cleaned up!" 38 | 39 | wercker: ## Run the integration tests. 40 | basht test/tests.bash 41 | 42 | .PHONY: help 43 | 44 | help: 45 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 46 | -------------------------------------------------------------------------------- /cmd/dnsmasq.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | const ( 11 | dnsmasqLog = "/var/log/dnsmasq/dnsmasq" 12 | signalInterval = 20 13 | yearSetInterval = 1 14 | ) 15 | 16 | var dnsmasqCmd = &cobra.Command{ 17 | Use: "dnsmasq", 18 | Short: "Grab stats from dnsmasq logs and send to Datadog.", 19 | Long: `Grab stats from dnsmasq logs and send to Datadog.`, 20 | PreRun: func(cmd *cobra.Command, args []string) { 21 | checkDnsmasqFlags() 22 | }, 23 | Run: startDnsmasq, 24 | } 25 | 26 | func startDnsmasq(cmd *cobra.Command, args []string) { 27 | dog := DogConnect() 28 | t := OpenLogfile(DnsmasqLog) 29 | if FullLogs { 30 | dnsmasqFullLogsStats(t, dog) 31 | } else { 32 | dnsmasqSignalStats(t) 33 | } 34 | } 35 | 36 | func checkDnsmasqFlags() { 37 | fmt.Println("Press CTRL-C to shutdown.") 38 | } 39 | 40 | var ( 41 | // DnsmasqLog is the logfile that dnsmasq logs to. 42 | DnsmasqLog string 43 | 44 | // FullLogs determines whether we're looking at '--log-queries' 45 | // levels of logs for dnsmasq. 46 | // It's disabled by default as it's pretty inefficient. 47 | FullLogs bool 48 | 49 | // CurrentTimestamp is the current timestamp from the dnsmasq logs. 50 | CurrentTimestamp int64 51 | 52 | // StatsCurrent is the current timestamp's stats. 53 | StatsCurrent *DNSStats 54 | 55 | // StatsPrevious is the last timestamp's stats. 56 | StatsPrevious *DNSStats 57 | 58 | // MemoryFlag sends dnsmasq memory stats to Datadog if true. 59 | MemoryFlag bool 60 | ) 61 | 62 | func init() { 63 | dnsmasqCmd.Flags().StringVarP(&DnsmasqLog, "log", "", dnsmasqLog, "dnsmasq log file.") 64 | dnsmasqCmd.Flags().BoolVarP(&FullLogs, "full", "", false, "Use full --log-queries logs.") 65 | dnsmasqCmd.Flags().BoolVarP(&MemoryFlag, "mem", "", false, "Send dnsmasq memory stats.") 66 | RootCmd.AddCommand(dnsmasqCmd) 67 | } 68 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package main 4 | 5 | import ( 6 | _ "expvar" 7 | "fmt" 8 | "github.com/darron/goshe/cmd" 9 | "log" 10 | "log/syslog" 11 | "net/http" 12 | "os" 13 | "os/signal" 14 | "runtime" 15 | "syscall" 16 | ) 17 | 18 | // CompileDate tracks when the binary was compiled. It's inserted during a build 19 | // with build flags. Take a look at the Makefile for information. 20 | var CompileDate = "No date provided." 21 | 22 | // GitCommit tracks the SHA of the built binary. It's inserted during a build 23 | // with build flags. Take a look at the Makefile for information. 24 | var GitCommit = "No revision provided." 25 | 26 | // Version is the version of the built binary. It's inserted during a build 27 | // with build flags. Take a look at the Makefile for information. 28 | var Version = "No version provided." 29 | 30 | // GoVersion details the version of Go this was compiled with. 31 | var GoVersion = runtime.Version() 32 | 33 | // The name of the program. 34 | var programName = "goshe" 35 | 36 | func main() { 37 | logwriter, e := syslog.New(syslog.LOG_NOTICE, programName) 38 | if e == nil { 39 | log.SetOutput(logwriter) 40 | } 41 | cmd.Log(fmt.Sprintf("%s version: %s", programName, Version), "info") 42 | 43 | args := os.Args[1:] 44 | for _, arg := range args { 45 | if arg == "-v" || arg == "--version" { 46 | fmt.Printf("Version : %s\nRevision : %s\nDate : %s\nGo : %s\n", Version, GitCommit, CompileDate, GoVersion) 47 | os.Exit(0) 48 | } 49 | } 50 | // Setup nice shutdown with CTRL-C. 51 | c := make(chan os.Signal) 52 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 53 | go handleCtrlC(c) 54 | 55 | // Listen for expvar if we have GOSHE_DEBUG set. 56 | if os.Getenv("GOSHE_DEBUG") != "" { 57 | go setupExpvarHTTP() 58 | } 59 | 60 | cmd.RootCmd.Execute() 61 | } 62 | 63 | func setupExpvarHTTP() { 64 | // Listen for expvar 65 | http.ListenAndServe(":1313", nil) 66 | } 67 | 68 | // Any cleanup tasks on shutdown could happen here. 69 | func handleCtrlC(c chan os.Signal) { 70 | sig := <-c 71 | message := fmt.Sprintf("Received '%s' - shutting down.", sig) 72 | cmd.Log(message, "info") 73 | fmt.Printf("%s\n", message) 74 | os.Exit(0) 75 | } 76 | -------------------------------------------------------------------------------- /cmd/ping.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/DataDog/datadog-go/statsd" 8 | "github.com/spf13/cobra" 9 | "github.com/tatsushid/go-fastping" 10 | "net" 11 | "os" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | var pingCmd = &cobra.Command{ 17 | Use: "ping", 18 | Short: "Ping an address and send stats to Datadog.", 19 | Long: `Ping an address and send stats to Datadog. Need to be root to use.`, 20 | PreRun: func(cmd *cobra.Command, args []string) { 21 | checkPingFlags() 22 | }, 23 | Run: startPing, 24 | } 25 | 26 | func startPing(cmd *cobra.Command, args []string) { 27 | dog := DogConnect() 28 | for { 29 | go ping(Endpoint, dog) 30 | time.Sleep(time.Duration(Interval) * time.Second) 31 | } 32 | } 33 | 34 | func checkPingFlags() { 35 | if Endpoint == "" { 36 | fmt.Println("Please enter an address or domain name to ping: -e") 37 | os.Exit(1) 38 | } 39 | if !checkRootUser() { 40 | fmt.Println("You need to be root to run this.") 41 | os.Exit(1) 42 | } 43 | fmt.Println("Press CTRL-C to shutdown.") 44 | } 45 | 46 | var ( 47 | // Endpoint holds the address we're going to ping. 48 | Endpoint string 49 | ) 50 | 51 | func init() { 52 | pingCmd.Flags().StringVarP(&Endpoint, "endpoint", "e", "www.google.com", "Endpoint to ping.") 53 | RootCmd.AddCommand(pingCmd) 54 | } 55 | 56 | func checkRootUser() bool { 57 | user := GetCurrentUsername() 58 | if user != "root" { 59 | return false 60 | } 61 | return true 62 | } 63 | 64 | func ping(address string, dog *statsd.Client) { 65 | p := fastping.NewPinger() 66 | ra, err := net.ResolveIPAddr("ip4:icmp", address) 67 | if err != nil { 68 | fmt.Println(err) 69 | } 70 | p.AddIPAddr(ra) 71 | p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) { 72 | fmt.Printf("IP Addr: %s receive, RTT: %v\n", addr.String(), rtt) 73 | go sendPingStats(dog, rtt) 74 | } 75 | err = p.Run() 76 | if err != nil { 77 | fmt.Println(err) 78 | } 79 | } 80 | 81 | func sendPingStats(dog *statsd.Client, rtt time.Duration) { 82 | var err error 83 | seconds := (float64(rtt) / 1000000000) 84 | address := strings.ToLower(strings.Replace(Endpoint, ".", "_", -1)) 85 | metricName := fmt.Sprintf("ping.%s", address) 86 | err = dog.Histogram(metricName, seconds, dog.Tags, 1) 87 | if err != nil { 88 | Log(fmt.Sprintf("Error sending ping stats for '%s'", Endpoint), "info") 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /cmd/util.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/hpcloud/tail" 8 | "github.com/spf13/viper" 9 | "log" 10 | "os" 11 | "os/user" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | // ReturnCurrentUTC returns the current UTC time in RFC3339 format. 17 | func ReturnCurrentUTC() string { 18 | t := time.Now().UTC() 19 | dateUpdated := (t.Format(time.RFC3339)) 20 | return dateUpdated 21 | } 22 | 23 | // SetDirection returns the direction. 24 | func SetDirection() string { 25 | args := fmt.Sprintf("%x", os.Args) 26 | direction := "main" 27 | if strings.ContainsAny(args, " ") { 28 | if strings.HasPrefix(os.Args[1], "-") { 29 | direction = "main" 30 | } else { 31 | direction = os.Args[1] 32 | } 33 | } 34 | return direction 35 | } 36 | 37 | // Log adds the global Direction to a message and sends to syslog. 38 | // Syslog is setup in main.go 39 | func Log(message, priority string) { 40 | message = fmt.Sprintf("%s: %s", Direction, message) 41 | if Verbose { 42 | time := ReturnCurrentUTC() 43 | fmt.Printf("%s: %s\n", time, message) 44 | } 45 | switch { 46 | case priority == "debug": 47 | if os.Getenv("GOSHE_DEBUG") != "" { 48 | log.Print(message) 49 | } 50 | default: 51 | log.Print(message) 52 | } 53 | } 54 | 55 | // GetHostname returns the hostname. 56 | func GetHostname() string { 57 | hostname, _ := os.Hostname() 58 | return hostname 59 | } 60 | 61 | // GetCurrentUsername grabs the current user running the binary. 62 | func GetCurrentUsername() string { 63 | usr, _ := user.Current() 64 | username := usr.Username 65 | Log(fmt.Sprintf("username='%s'", username), "debug") 66 | return username 67 | } 68 | 69 | // LoadConfig loads the configuration from a config file. 70 | func LoadConfig() { 71 | Log("Loading viper config.", "info") 72 | viper.SetConfigName("config") 73 | viper.AddConfigPath("/etc/goshe/") 74 | viper.AddConfigPath(".") 75 | viper.SetConfigType("yaml") 76 | err := viper.ReadInConfig() 77 | if err != nil { 78 | Log(fmt.Sprintf("No config file found: %s \n", err), "info") 79 | } 80 | viper.SetEnvPrefix("GOSHE") 81 | } 82 | 83 | // OpenLogfile opens a logfile and passes back a *tail.Tail pointer. 84 | func OpenLogfile(logfile string) *tail.Tail { 85 | t, err := tail.TailFile(logfile, tail.Config{ 86 | Location: &tail.SeekInfo{Whence: os.SEEK_END}, 87 | ReOpen: true, 88 | Follow: true}) 89 | if err != nil { 90 | Log("There was an error opening the file.", "info") 91 | } 92 | return t 93 | } 94 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure(2) do |config| 5 | config.vm.box = "ubuntu/trusty64" 6 | 7 | config.vm.provision "shell", inline: <<-SHELL 8 | curl -sf -o /tmp/go1.6.1.linux-amd64.tar.gz -L https://storage.googleapis.com/golang/go1.6.1.linux-amd64.tar.gz 9 | sudo mkdir -p /opt && cd /opt && sudo tar xfz /tmp/go1.6.1.linux-amd64.tar.gz && rm -f /tmp/go1.6.1.linux-amd64.tar.gz 10 | curl -s https://packagecloud.io/install/repositories/darron/consul/script.deb.sh | sudo bash 11 | sudo apt-get install -y consul git make graphviz dnsmasq 12 | sudo mkdir -p /var/log/dnsmasq /etc/consul.d /var/lib/consul /var/log/consul 13 | sudo ln -s /lib/init/upstart-job /etc/init.d/consul 14 | curl -s https://raw.githubusercontent.com/DataDog/kvexpress-cookbook/master/files/default/consul.conf > /tmp/consul.conf && chmod 644 /tmp/consul.conf && sudo chown root.root /tmp/consul.conf && sudo mv -f /tmp/consul.conf /etc/init/consul.conf 15 | sudo cat > /etc/consul.d/default.json << EOF 16 | { 17 | "client_addr": "127.0.0.1", 18 | "data_dir": "/var/lib/consul", 19 | "server": true, 20 | "bootstrap": true, 21 | "recursor": "8.8.8.8", 22 | "bind_addr": "0.0.0.0", 23 | "log_level": "debug", 24 | "node_name": "goshe-consul" 25 | } 26 | EOF 27 | sudo cat > /etc/consul.d/kafka.json << EOF 28 | { 29 | "service": { 30 | "name": "kafka", 31 | "check": { 32 | "interval": "60s", 33 | "script": "/bin/true" 34 | } 35 | } 36 | } 37 | EOF 38 | sudo cat > /etc/consul.d/casandra.json << EOF 39 | { 40 | "service": { 41 | "name": "cassandra", 42 | "check": { 43 | "interval": "60s", 44 | "script": "/bin/true" 45 | } 46 | } 47 | } 48 | EOF 49 | sudo cat > /etc/hosts.consul << EOF 50 | 127.0.0.1 goshe.service.consul 51 | 127.0.0.1 vagrant.service.consul 52 | 127.0.0.1 datadog.service.consul 53 | EOF 54 | sudo cat > /etc/dnsmasq.d/10-consul << EOF 55 | server=/consul/127.0.0.1#8600 56 | EOF 57 | sudo cat > /etc/default/dnsmasq << EOF 58 | DNSMASQ_OPTS="--addn-hosts=/etc/hosts.consul --log-facility=/var/log/dnsmasq/dnsmasq --local-ttl=10" 59 | ENABLED=1 60 | CONFIG_DIR=/etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new 61 | EOF 62 | sudo service dnsmasq restart 63 | sudo service consul start 64 | sudo cat > /etc/profile.d/go.sh << EOF 65 | export GOROOT="/opt/go" 66 | export GOPATH="/home/vagrant/gocode" 67 | export PATH="/opt/go/bin://home/vagrant/gocode/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 68 | export GOSHE_DEBUG=1 69 | EOF 70 | cd /vagrant && source /etc/profile.d/go.sh 71 | SHELL 72 | end 73 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 9dc37f84ef48252c5bfd1b05f7bbefc1bfcb11e849e9ae35ad7e6a5cc5c60261 2 | updated: 2016-02-26T11:20:56.794666594-07:00 3 | imports: 4 | - name: github.com/BurntSushi/toml 5 | version: a4eecd407cf4129fc902ece859a0114e4cf1a7f4 6 | - name: github.com/cloudfoundry/gosigar 7 | version: 3ed7c74352dae6dc00bdc8c74045375352e3ec05 8 | - name: github.com/DataDog/datadog-go 9 | version: 8b6f59aa4f252b3b547523c21381330138bfe3ac 10 | subpackages: 11 | - statsd 12 | - name: github.com/davecgh/go-spew 13 | version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d 14 | subpackages: 15 | - spew 16 | - name: github.com/hashicorp/hcl 17 | version: 1c284ec98f4b398443cbabb0d9197f7f4cc0077c 18 | subpackages: 19 | - hcl/ast 20 | - hcl/parser 21 | - hcl/token 22 | - json/parser 23 | - hcl/scanner 24 | - hcl/strconv 25 | - json/scanner 26 | - json/token 27 | - name: github.com/hpcloud/tail 28 | version: 1a0242e795eeefe54261ff308dc685f7d29cc58c 29 | subpackages: 30 | - ratelimiter 31 | - util 32 | - watch 33 | - winfile 34 | - name: github.com/inconshreveable/mousetrap 35 | version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 36 | - name: github.com/kr/pretty 37 | version: e6ac2fc51e89a3249e82157fa0bb7a18ef9dd5bb 38 | - name: github.com/kr/text 39 | version: bb797dc4fb8320488f47bf11de07a733d7233e1f 40 | - name: github.com/magiconair/properties 41 | version: c81f9d71af8f8cba1466501d30326b99a4e56c19 42 | - name: github.com/mitchellh/mapstructure 43 | version: d2dd0262208475919e1a362f675cfc0e7c10e905 44 | - name: github.com/spf13/cast 45 | version: ee7b3e0353166ab1f3a605294ac8cd2b77953778 46 | - name: github.com/spf13/cobra 47 | version: 65a708cee0a4424f4e353d031ce440643e312f92 48 | - name: github.com/spf13/jwalterweatherman 49 | version: d00654080cddbd2b082acaa74007cb94a2b40866 50 | - name: github.com/spf13/pflag 51 | version: 7f60f83a2c81bc3c3c0d5297f61ddfa68da9d3b7 52 | - name: github.com/spf13/viper 53 | version: c975dc1b4eacf4ec7fdbf0873638de5d090ba323 54 | - name: github.com/tatsushid/go-fastping 55 | version: d7bb493dee3e090e2ffb6914adddf17c1e7c026c 56 | - name: github.com/yhat/scrape 57 | version: 47d070048ae58bf228dfdfcfaf1d1c6abc00620e 58 | - name: golang.org/x/net 59 | version: 6acef71eb69611914f7a30939ea9f6e194c78172 60 | subpackages: 61 | - html 62 | - html/atom 63 | - icmp 64 | - ipv4 65 | - ipv6 66 | - internal/iana 67 | - name: gopkg.in/fsnotify.v1 68 | version: 8611c35ab31c1c28aa903d33cf8b6e44a399b09e 69 | - name: gopkg.in/tomb.v1 70 | version: dd632973f1e7218eb1089048e0798ec9ae7dceb8 71 | - name: gopkg.in/yaml.v2 72 | version: f7716cbe52baa25d2e9b0d0da546fcf909fc16b4 73 | devImports: [] 74 | -------------------------------------------------------------------------------- /cmd/goshe.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/cloudfoundry/gosigar" 8 | _ "github.com/davecgh/go-spew/spew" // I want to use this sometimes. 9 | "strings" 10 | "syscall" 11 | ) 12 | 13 | // ProcessList is a simplified list of processes on a system. 14 | type ProcessList struct { 15 | Pname string 16 | Pid int 17 | Pmem uint64 // in bytes 18 | } 19 | 20 | // USR1 sends a USR1 signal to the process. 21 | func (p *ProcessList) USR1() bool { 22 | Log(fmt.Sprintf("Sent USR1 to pid: %d", p.Pid), "debug") 23 | syscall.Kill(p.Pid, syscall.SIGUSR1) 24 | return true 25 | } 26 | 27 | // GetMatches returns only the matches we want from running processes. 28 | func GetMatches(match string, wantGoshe bool) []ProcessList { 29 | var Matches []ProcessList 30 | processes := GetProcessList() 31 | // spew.Dump(processes) 32 | Matches = MatchProcessList(*processes, match, wantGoshe) 33 | return Matches 34 | } 35 | 36 | // GetProcessList returns all the processes. 37 | func GetProcessList() *[]ProcessList { 38 | pids := GetPIDs() 39 | processes := ConvertProcessList(pids) 40 | return processes 41 | } 42 | 43 | // GetPIDs returns a pointer to all pids on machine. 44 | func GetPIDs() *sigar.ProcList { 45 | pids := sigar.ProcList{} 46 | pids.Get() 47 | return &pids 48 | } 49 | 50 | // ConvertProcessList converts the *sigar.ProcList into our []ProcessList struct. 51 | func ConvertProcessList(p *sigar.ProcList) *[]ProcessList { 52 | var List []ProcessList 53 | var proc ProcessList 54 | for _, pid := range p.List { 55 | var memory uint64 56 | state := sigar.ProcState{} 57 | mem := sigar.ProcMem{} 58 | time := sigar.ProcTime{} 59 | if err := state.Get(pid); err != nil { 60 | continue 61 | } 62 | if err := mem.Get(pid); err != nil { 63 | continue 64 | } 65 | if err := time.Get(pid); err != nil { 66 | continue 67 | } 68 | memory = mem.Resident 69 | proc = ProcessList{Pname: state.Name, Pid: pid, Pmem: memory} 70 | List = append(List, proc) 71 | } 72 | return &List 73 | } 74 | 75 | // MatchProcessList looks through the struct processes that match. 76 | func MatchProcessList(procs []ProcessList, match string, goshe bool) []ProcessList { 77 | var Matches []ProcessList 78 | for _, proc := range procs { 79 | if proc.Pname == match || (proc.Pname == "goshe" && goshe == true) { 80 | Matches = append(Matches, proc) 81 | } 82 | } 83 | return Matches 84 | } 85 | 86 | // SendMetrics sends memory metrics to Dogstatsd. 87 | func SendMetrics(p []ProcessList) bool { 88 | var err error 89 | dog := DogConnect() 90 | for _, proc := range p { 91 | processName := strings.ToLower(strings.Replace(proc.Pname, " ", "_", -1)) 92 | metricName := fmt.Sprintf("%s.rss_memory", processName) 93 | Log(fmt.Sprintf("SendMetrics process='%#v' processName='%s' metricName='%s' memory='%b'", proc, processName, metricName, float64(proc.Pmem)), "debug") 94 | err = dog.Histogram(metricName, float64(proc.Pmem), dog.Tags, 1) 95 | if err != nil { 96 | Log(fmt.Sprintf("Error sending rss_memory stats for '%s'", processName), "info") 97 | return false 98 | } 99 | } 100 | return true 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | goshe 2 | =========== 3 | 4 | [](https://app.wercker.com/project/bykey/f25e70250066e5f1e03744ef4d5be79e) 5 | 6 | Replacement for some old Ruby scripts that send stats to Datadog. Works with Apache, dnsmasq, ping (afternoon hack) and general memory stats. 7 | 8 | ## apache 9 | 10 | ``` 11 | darron@: bin/goshe apache -h 12 | Grab stats from Apache2 processes - and mod_status - and sends to Datadog. 13 | 14 | Usage: 15 | goshe apache [flags] 16 | 17 | Flags: 18 | -m, --memory uint Smallest Apache memory size to log. (default 10485760) 19 | 20 | Global Flags: 21 | -i, --interval int Interval when running in a loop. (default 5) 22 | --prefix string Metric prefix. (default "goshe") 23 | -p, --process string Process name to match. 24 | --verbose log output to stdout 25 | ``` 26 | 27 | ## dnsmasq 28 | 29 | ``` 30 | darron@: bin/goshe dnsmasq -h 31 | Grab stats from dnsmasq logs and send to Datadog. 32 | 33 | Usage: 34 | goshe dnsmasq [flags] 35 | 36 | Flags: 37 | --full Use full --log-queries logs. 38 | --log string dnsmasq log file. (default "/var/log/dnsmasq/dnsmasq") 39 | 40 | Global Flags: 41 | --prefix string Metric prefix. (default "goshe") 42 | --verbose log output to stdout 43 | ``` 44 | 45 | ## ping 46 | 47 | ``` 48 | darron@: bin/goshe ping -h 49 | Ping an address and send stats to Datadog. Need to be root to use. 50 | 51 | Usage: 52 | goshe ping [flags] 53 | 54 | Flags: 55 | -e, --endpoint string Endpoint to ping. (default "www.google.com") 56 | 57 | Global Flags: 58 | -i, --interval int Interval when running in a loop. (default 5) 59 | --prefix string Metric prefix. (default "goshe") 60 | --verbose log output to stdout 61 | ``` 62 | 63 | ## match 64 | 65 | ``` 66 | darron@: bin/goshe match -h 67 | Grab memory stats from matching processes and sends to Datadog. 68 | 69 | Usage: 70 | goshe match [flags] 71 | 72 | Global Flags: 73 | -i, --interval int Interval when running in a loop. (default 5) 74 | --prefix string Metric prefix. (default "goshe") 75 | -p, --process string Process name to match. 76 | --verbose log output to stdout 77 | ``` 78 | 79 | ## tail 80 | 81 | ``` 82 | darron@: bin/goshe tail -h 83 | Tail logs, match lines and send metrics to Datadog. 84 | 85 | Usage: 86 | goshe tail [flags] 87 | 88 | Flags: 89 | --log string File to tail. 90 | --match string Match this regex. 91 | --metric string Send this metric name. 92 | --tag string Add this tag to the metric. 93 | 94 | Global Flags: 95 | --prefix string Metric prefix. (default "goshe") 96 | --verbose log output to stdout 97 | ``` 98 | 99 | These are much faster and use significantly less memory than the old Ruby versions. 100 | 101 | Plus - they run from a single binary that doesn't require a Ruby runtime. 102 | 103 | [](https://app.wercker.com/project/bykey/f25e70250066e5f1e03744ef4d5be79e) 104 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v0.4](https://github.com/darron/goshe/tree/v0.4) (2016-03-09) 4 | [Full Changelog](https://github.com/darron/goshe/compare/v0.3...v0.4) 5 | 6 | **Implemented enhancements:** 7 | 8 | - tail - Add a tag for an invocation of `goshe tail` [\#31](https://github.com/darron/goshe/issues/31) 9 | - tail to the end of the file when --skip is true. [\#30](https://github.com/darron/goshe/issues/30) 10 | - Let's add some timing stats around the dnsmasq stats generation. [\#22](https://github.com/darron/goshe/issues/22) 11 | 12 | **Fixed bugs:** 13 | 14 | - Removed architecture from the Makefile during build. [\#29](https://github.com/darron/goshe/issues/29) 15 | 16 | **Closed issues:** 17 | 18 | - We're automatically skipping to the end of the file. [\#32](https://github.com/darron/goshe/issues/32) 19 | - Tail is supposed to follow logrotation. [\#26](https://github.com/darron/goshe/issues/26) 20 | - Do some profiling around the tail command. [\#19](https://github.com/darron/goshe/issues/19) 21 | 22 | ## [v0.3](https://github.com/darron/goshe/tree/v0.3) (2016-02-25) 23 | [Full Changelog](https://github.com/darron/goshe/compare/v0.2...v0.3) 24 | 25 | **Implemented enhancements:** 26 | 27 | - Build with Go 1.6 [\#25](https://github.com/darron/goshe/issues/25) 28 | - What happens when dnsmasq is restarted? [\#24](https://github.com/darron/goshe/issues/24) 29 | - Let's try a lower interval for dnsmasq stats. [\#21](https://github.com/darron/goshe/issues/21) 30 | - Send memory and CPU stats whenever this is running. [\#18](https://github.com/darron/goshe/issues/18) 31 | 32 | **Fixed bugs:** 33 | 34 | - There's a significant bug in dnsmasq stats post 0.2. [\#27](https://github.com/darron/goshe/issues/27) 35 | - Can't cross compile - because of `user.Current\(\)`. [\#20](https://github.com/darron/goshe/issues/20) 36 | 37 | ## [v0.2](https://github.com/darron/goshe/tree/v0.2) (2016-02-17) 38 | [Full Changelog](https://github.com/darron/goshe/compare/v0.1...v0.2) 39 | 40 | ## [v0.1](https://github.com/darron/goshe/tree/v0.1) (2016-01-13) 41 | **Implemented enhancements:** 42 | 43 | - Grab stats from Apache's mod\_status for the `apache` command. [\#15](https://github.com/darron/goshe/issues/15) 44 | - Refactor the current Metrics sending function. [\#14](https://github.com/darron/goshe/issues/14) 45 | - Make goshe listen for CTRL-C to stop properly. [\#7](https://github.com/darron/goshe/issues/7) 46 | - Make metric\(s\) monitored configurable with a flag. [\#6](https://github.com/darron/goshe/issues/6) 47 | - Expose memory metrics about goshe during the run. [\#5](https://github.com/darron/goshe/issues/5) 48 | - Add metric name that's settable with a flag. [\#4](https://github.com/darron/goshe/issues/4) 49 | - Add metrics prefix so that we can easily change metric names. [\#3](https://github.com/darron/goshe/issues/3) 50 | - Add go-metrics so we can submit metrics in a more cross platform way. [\#2](https://github.com/darron/goshe/issues/2) 51 | - Add interval so that things run on a loop. [\#1](https://github.com/darron/goshe/issues/1) 52 | 53 | **Fixed bugs:** 54 | 55 | - I don't think we need to add the hostname tag. [\#8](https://github.com/darron/goshe/issues/8) 56 | 57 | **Closed issues:** 58 | 59 | - Need to add some more logs. [\#16](https://github.com/darron/goshe/issues/16) 60 | - Handles 0 matches and doesn't crash. [\#12](https://github.com/darron/goshe/issues/12) 61 | - Add more general `goshe match` [\#11](https://github.com/darron/goshe/issues/11) 62 | 63 | 64 | 65 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /cmd/apache_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestGetServerStatus(t *testing.T) { 11 | ts := httptest.NewServer(http.FileServer(http.Dir("../test/http"))) 12 | url := fmt.Sprintf("%s/index.html", ts.URL) 13 | serverStatus := getServerStatus(url) 14 | if serverStatus == nil { 15 | t.Error("That's not good! Not getting html.Nodes back.") 16 | } 17 | } 18 | 19 | func TestParseServerStatus(t *testing.T) { 20 | ts := httptest.NewServer(http.FileServer(http.Dir("../test/http"))) 21 | url := fmt.Sprintf("%s/index.html", ts.URL) 22 | serverStatus := getServerStatus(url) 23 | stringResults := parseServerStatus(serverStatus) 24 | if len(stringResults) != 64 { 25 | t.Error("We're not getting the right number of strings back. Should be 64.") 26 | } 27 | } 28 | 29 | func TestParseServerStats(t *testing.T) { 30 | ts := httptest.NewServer(http.FileServer(http.Dir("../test/http"))) 31 | url := fmt.Sprintf("%s/index.html", ts.URL) 32 | serverStatus := getServerStatus(url) 33 | stringResults := parseServerStatus(serverStatus) 34 | ApacheProcesses := parseProcessStats(stringResults) 35 | if len(ApacheProcesses) != 9 { 36 | t.Error("That's bad - we should see 9 Apache structs.") 37 | } 38 | } 39 | 40 | func createTestProcessList() []ProcessList { 41 | var procs []ProcessList 42 | var proc ProcessList 43 | proc = ProcessList{Pname: "apache2", Pid: 10434, Pmem: 10520} 44 | procs = append(procs, proc) 45 | proc = ProcessList{Pname: "apache2", Pid: 10360, Pmem: 20520} 46 | procs = append(procs, proc) 47 | proc = ProcessList{Pname: "apache2", Pid: 10282, Pmem: 30520} 48 | procs = append(procs, proc) 49 | proc = ProcessList{Pname: "apache2", Pid: 10345, Pmem: 15520} 50 | procs = append(procs, proc) 51 | proc = ProcessList{Pname: "apache2", Pid: 10475, Pmem: 25520} 52 | procs = append(procs, proc) 53 | return procs 54 | } 55 | 56 | func TestCreateProcessMemMap(t *testing.T) { 57 | procs := createTestProcessList() 58 | processMap := createProcessMemMap(procs) 59 | if processMap[10434] != uint64(10520) { 60 | t.Error("That's incorrect - it should be uint64(10520).") 61 | } 62 | } 63 | 64 | func createTestApacheList() []ApacheProcess { 65 | var stats []ApacheProcess 66 | var apache ApacheProcess 67 | apache = ApacheProcess{Pid: 10434, Vhost: "andy.bam.nonwebdev.com"} 68 | stats = append(stats, apache) 69 | apache = ApacheProcess{Pid: 10360, Vhost: "jon.bam.nonwebdev.com"} 70 | stats = append(stats, apache) 71 | apache = ApacheProcess{Pid: 10282, Vhost: "darron.bam.nonwebdev.com"} 72 | stats = append(stats, apache) 73 | apache = ApacheProcess{Pid: 10345, Vhost: "darron.bam.nonwebdev.com"} 74 | stats = append(stats, apache) 75 | apache = ApacheProcess{Pid: 10475, Vhost: "robb.bam.nonwebdev.com"} 76 | stats = append(stats, apache) 77 | return stats 78 | } 79 | 80 | // Testing to see if the stats get sent. 81 | func TestSendApacheServerStats(t *testing.T) { 82 | procs := createTestProcessList() 83 | if len(procs) != 5 { 84 | t.Error("That's a problem - there should be 5 processes.") 85 | } 86 | apaches := createTestApacheList() 87 | if len(apaches) != 5 { 88 | t.Error("That's a problem - there should be 5 Apaches.") 89 | } 90 | procMap := createProcessMemMap(procs) 91 | SendApacheServerStats(apaches, procMap) 92 | } 93 | 94 | // Adding an extra Apache process without corresponding memory info. 95 | func TestSendApacheServerStatsWithExtraApache(t *testing.T) { 96 | procs := createTestProcessList() 97 | apaches := createTestApacheList() 98 | apache := ApacheProcess{Pid: 10800, Vhost: "wildcard.bam.nonwebdev.com"} 99 | apaches = append(apaches, apache) 100 | procMap := createProcessMemMap(procs) 101 | SendApacheServerStats(apaches, procMap) 102 | } 103 | 104 | // Adding an extra Process without a matching Apache process. 105 | func TestSendApacheServerStatsWithExtraProcess(t *testing.T) { 106 | procs := createTestProcessList() 107 | proc := ProcessList{Pname: "apache2", Pid: 16475, Pmem: 255520} 108 | procs = append(procs, proc) 109 | apaches := createTestApacheList() 110 | procMap := createProcessMemMap(procs) 111 | SendApacheServerStats(apaches, procMap) 112 | } 113 | -------------------------------------------------------------------------------- /cmd/tail.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "bufio" 7 | "fmt" 8 | "github.com/DataDog/datadog-go/statsd" 9 | "github.com/hpcloud/tail" 10 | "github.com/spf13/cobra" 11 | "os" 12 | "os/exec" 13 | "regexp" 14 | "strings" 15 | ) 16 | 17 | var tailCmd = &cobra.Command{ 18 | Use: "tail", 19 | Short: "Tail logs or stdout, match lines and send metrics to Datadog.", 20 | Long: `Tail logs or stdout, match lines and send metrics to Datadog.`, 21 | PreRun: func(cmd *cobra.Command, args []string) { 22 | checkTailFlags() 23 | }, 24 | Run: startTail, 25 | } 26 | 27 | func startTail(cmd *cobra.Command, args []string) { 28 | // Try to compile the regex - throw an error if it doesn't work. 29 | regex, err := regexp.Compile(Match) 30 | if err != nil { 31 | fmt.Println("There's something wrong with your regex. Try again.") 32 | fmt.Printf("Error: %s\n", err) 33 | os.Exit(1) 34 | } 35 | dog := DogConnect() 36 | // For the Logfile option. 37 | if LogFile != "" { 38 | t := OpenLogfile(LogFile) 39 | TailLog(t, dog, regex) 40 | } 41 | // If you're capturing stdout of a program. 42 | if ProgramStdout != "" { 43 | TailOutput(dog, regex) 44 | } 45 | } 46 | 47 | func checkTailFlags() { 48 | if LogFile == "" && ProgramStdout == "" { 49 | fmt.Println("Please enter a filename to tail '--log' OR a program to run '--program'") 50 | os.Exit(1) 51 | } 52 | if LogFile != "" && ProgramStdout != "" { 53 | fmt.Println("Please choose '--log' OR '--program' - you cannot have both.") 54 | os.Exit(1) 55 | } 56 | if Match == "" { 57 | fmt.Println("Please enter a regex to match '--match'") 58 | os.Exit(1) 59 | } 60 | if MetricName == "" { 61 | fmt.Println("Please enter a metric name to send '--metric'") 62 | os.Exit(1) 63 | } 64 | // If you're sending a MetricTag - it needs to have a ':' 65 | if MetricTag != "" && !strings.Contains(MetricTag, ":") { 66 | fmt.Println("Tags need to contain a ':'") 67 | os.Exit(1) 68 | } 69 | fmt.Println("Press CTRL-C to shutdown.") 70 | } 71 | 72 | var ( 73 | // LogFile is the file to tail. 74 | LogFile string 75 | 76 | // ProgramStdout is a program to run to capture stdout. 77 | ProgramStdout string 78 | 79 | // Match is the regex to match in the file. 80 | Match string 81 | 82 | // MetricName is the name of the metric to send to Datadog. 83 | MetricName string 84 | 85 | // MetricTag is the name of the tag to add to the metric we're sending to Datadog. 86 | MetricTag string 87 | ) 88 | 89 | func init() { 90 | tailCmd.Flags().StringVarP(&LogFile, "log", "", "", "File to tail.") 91 | tailCmd.Flags().StringVarP(&ProgramStdout, "program", "", "", "Program to run for stdout.") 92 | tailCmd.Flags().StringVarP(&Match, "match", "", "", "Match this regex.") 93 | tailCmd.Flags().StringVarP(&MetricName, "metric", "", "", "Send this metric name.") 94 | tailCmd.Flags().StringVarP(&MetricTag, "tag", "", "", "Add this tag to the metric.") 95 | RootCmd.AddCommand(tailCmd) 96 | } 97 | 98 | // TailLog tails a file and sends stats to Datadog. 99 | func TailLog(t *tail.Tail, dog *statsd.Client, r *regexp.Regexp) { 100 | for line := range t.Lines { 101 | // Blank lines really mess this up - this protects against it. 102 | if line.Text == "" { 103 | continue 104 | } 105 | match := r.FindAllStringSubmatch(line.Text, -1) 106 | if match != nil { 107 | Log(fmt.Sprintf("Match: %s", match), "debug") 108 | Log(fmt.Sprintf("Sending Stat: %s", MetricName), "debug") 109 | tags := dog.Tags 110 | if MetricTag != "" { 111 | tags = append(tags, MetricTag) 112 | } 113 | dog.Count(MetricName, 1, tags, 1) 114 | } 115 | } 116 | } 117 | 118 | // TailOutput watches the output of ProgramStdout and matches on those lines. 119 | func TailOutput(dog *statsd.Client, r *regexp.Regexp) { 120 | cli, args := processCommand(ProgramStdout) 121 | runCommand(cli, args, r, dog) 122 | } 123 | 124 | func processCommand(command string) (string, []string) { 125 | var cli string 126 | var args []string 127 | 128 | parts := strings.Fields(command) 129 | cli = parts[0] 130 | args = parts[1:] 131 | Log(fmt.Sprintf("Cli: %s Args: %s", cli, args), "debug") 132 | 133 | return cli, args 134 | } 135 | 136 | func runCommand(cli string, args []string, r *regexp.Regexp, dog *statsd.Client) { 137 | cmd := exec.Command(cli, args...) 138 | cmdReader, err := cmd.StdoutPipe() 139 | if err != nil { 140 | Log(fmt.Sprintf("There was an error running '%s': %s", ProgramStdout, err), "info") 141 | os.Exit(1) 142 | } 143 | scanner := bufio.NewScanner(cmdReader) 144 | go func() { 145 | for scanner.Scan() { 146 | line := scanner.Text() 147 | Log(fmt.Sprintf("Line: %s", line), "debug") 148 | // Blank lines are bad for the matching software - it freaks out. 149 | if line == "" { 150 | continue 151 | } 152 | match := r.FindAllStringSubmatch(line, -1) 153 | if match != nil { 154 | Log(fmt.Sprintf("Match: %s", match), "debug") 155 | Log(fmt.Sprintf("Sending Stat: %s", MetricName), "debug") 156 | tags := dog.Tags 157 | if MetricTag != "" { 158 | tags = append(tags, MetricTag) 159 | } 160 | dog.Count(MetricName, 1, tags, 1) 161 | } 162 | } 163 | }() 164 | 165 | err = cmd.Start() 166 | if err != nil { 167 | Log("There was and error starting the command.", "info") 168 | } 169 | 170 | err = cmd.Wait() 171 | if err != nil { 172 | Log("There was and error waiting for the command.", "info") 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /cmd/apache.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "github.com/yhat/scrape" 10 | "golang.org/x/net/html" 11 | "golang.org/x/net/html/atom" 12 | "net/http" 13 | "regexp" 14 | "strconv" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | const ( 20 | modStatus = "http://127.0.0.1/server-status/" 21 | minMemory = 10 * 1024 * 1024 // Skip logging Apache processes with less than this memory used: 10MB by default. 22 | ) 23 | 24 | // ApacheProcess holds the interesting pieces of Apache stats. 25 | type ApacheProcess struct { 26 | Pid int64 27 | Vhost string 28 | } 29 | 30 | var apacheCmd = &cobra.Command{ 31 | Use: "apache", 32 | Short: "Grab stats from Apache2 processes - and mod_status - and sends to Datadog.", 33 | Long: `Grab stats from Apache2 processes - and mod_status - and sends to Datadog.`, 34 | PreRun: func(cmd *cobra.Command, args []string) { 35 | checkApacheFlags() 36 | }, 37 | Run: startApache, 38 | } 39 | 40 | func startApache(cmd *cobra.Command, args []string) { 41 | for { 42 | matches := GetMatches(ProcessName, true) 43 | if matches != nil { 44 | fmt.Printf("Found %d matches.\n", len(matches)-1) 45 | SendMetrics(matches) 46 | processMap := createProcessMemMap(matches) 47 | // Let's get the Apache details and then submit those. 48 | apaches := GetApacheServerStats() 49 | SendApacheServerStats(apaches, processMap) 50 | } else { 51 | fmt.Printf("Did not find any matches.\n") 52 | } 53 | time.Sleep(time.Duration(Interval) * time.Second) 54 | } 55 | } 56 | 57 | func checkApacheFlags() { 58 | if ProcessName == "" { 59 | ProcessName = "apache2" 60 | } 61 | fmt.Println("Press CTRL-C to shutdown.") 62 | } 63 | 64 | var ( 65 | // MinimumMemory is the minimum size of an Apache process that we log stats for. 66 | MinimumMemory uint64 67 | ) 68 | 69 | func init() { 70 | apacheCmd.Flags().Uint64VarP(&MinimumMemory, "memory", "m", minMemory, "Smallest Apache memory size to log.") 71 | RootCmd.AddCommand(apacheCmd) 72 | } 73 | 74 | // SendApacheServerStats sends tagged Apache stats to Datadog 75 | func SendApacheServerStats(apache []ApacheProcess, procs map[int]uint64) { 76 | var err error 77 | dog := DogConnect() 78 | for _, server := range apache { 79 | Log(fmt.Sprintf("SendApacheServerStats server='%#v'", server), "debug") 80 | pid := int(server.Pid) 81 | memory := procs[pid] 82 | if memory > MinimumMemory { 83 | Log(fmt.Sprintf("sending memory='%f' vhost='%s'", float64(memory), server.Vhost), "debug") 84 | dog.Tags = append(dog.Tags, fmt.Sprintf("site:%s", server.Vhost)) 85 | err = dog.Histogram("apache2.rss_memory_tagged", float64(memory), dog.Tags, 1) 86 | if err != nil { 87 | Log("Error sending tagged rss_memory stats for Apache", "info") 88 | } 89 | } 90 | } 91 | } 92 | 93 | // GetApacheServerStats grabs info from mod_status and parses it. 94 | func GetApacheServerStats() []ApacheProcess { 95 | htmlContent := getServerStatus("") 96 | stringResults := parseServerStatus(htmlContent) 97 | apaches := parseProcessStats(stringResults) 98 | return apaches 99 | } 100 | 101 | // getServerStatus returns the HTML nodes from the Apache serverStatus page. 102 | func getServerStatus(server string) *html.Node { 103 | serverStatus := "" 104 | if serverStatus = viper.GetString("apache_status"); serverStatus == "" { 105 | serverStatus = modStatus 106 | } 107 | if server != "" { 108 | serverStatus = server 109 | } 110 | request, err := http.NewRequest("GET", serverStatus, nil) 111 | if err != nil { 112 | Log("Error connecting to the Apache server.", "info") 113 | } 114 | response, err := http.DefaultClient.Do(request) 115 | if err != nil { 116 | Log("Error reading the response.", "info") 117 | } 118 | root, err := html.Parse(response.Body) 119 | if err != nil { 120 | Log("Something went wrong with the html parse.", "info") 121 | } 122 | defer response.Body.Close() 123 | return root 124 | } 125 | 126 | // parseServerStatus returns a slice of strings containing only server stats. 127 | func parseServerStatus(root *html.Node) []string { 128 | var apacheStats []string 129 | // Lines with stats start with a number. 130 | var validStats = regexp.MustCompile(`^[0-9]`) 131 | // Grab all the table rows. 132 | rows := scrape.FindAll(root, scrape.ByTag(atom.Tr)) 133 | // If each row matches - add it to the stats lines. 134 | for _, row := range rows { 135 | content := scrape.Text(row) 136 | if validStats.MatchString(content) { 137 | apacheStats = append(apacheStats, content) 138 | } 139 | } 140 | Log(fmt.Sprintf("parseServerStatus apacheStats='%d'", len(apacheStats)), "debug") 141 | return apacheStats 142 | } 143 | 144 | // parseProcessStats takes the slice of strings and returns a slice of Apache processes. 145 | func parseProcessStats(processes []string) []ApacheProcess { 146 | var stats []ApacheProcess 147 | var apache ApacheProcess 148 | for _, process := range processes { 149 | fields := strings.SplitAfterN(process, " ", -1) 150 | pid := strings.TrimSpace(fields[1]) 151 | vhost := strings.TrimSpace(fields[11]) 152 | if pid != "-" && !strings.HasPrefix(vhost, "*") && vhost != "?" { 153 | pidInt, _ := strconv.ParseInt(pid, 10, 64) 154 | apache = ApacheProcess{Pid: pidInt, Vhost: vhost} 155 | stats = append(stats, apache) 156 | } 157 | } 158 | Log(fmt.Sprintf("parseProcessStats stats='%d'", len(stats)), "debug") 159 | return stats 160 | } 161 | 162 | // Take a slice of ProcessList items and create a map. 163 | func createProcessMemMap(p []ProcessList) map[int]uint64 { 164 | m := make(map[int]uint64) 165 | for _, proc := range p { 166 | pid := proc.Pid 167 | mem := proc.Pmem 168 | Log(fmt.Sprintf("pid='%d' mem='%d'", pid, mem), "debug") 169 | m[pid] = mem 170 | } 171 | return m 172 | } 173 | -------------------------------------------------------------------------------- /cmd/dnsmasq_signal_stats.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/DataDog/datadog-go/statsd" 8 | "github.com/hpcloud/tail" 9 | "os" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | // DNSServer is data gathered from a dnsmasq server log line. 17 | type DNSServer struct { 18 | timestamp int64 19 | address string 20 | queriesSent int64 21 | queriesFailed int64 22 | } 23 | 24 | // DNSStats is data gathered from dnsmasq time, queries and server lines. 25 | type DNSStats struct { 26 | timestamp int64 27 | queriesForwarded int64 28 | queriesLocal int64 29 | authoritativeZones int64 30 | servers []DNSServer 31 | } 32 | 33 | // dnsmasqSignalStats processes the logs that are output by dnsmasq 34 | // when the USR1 signal is sent to it. 35 | func dnsmasqSignalStats(t *tail.Tail) { 36 | // Set the current time from timestamp. Helps us to skip any items that are old. 37 | CurrentTimestamp = time.Now().Unix() 38 | StatsCurrent = new(DNSStats) 39 | StatsPrevious = new(DNSStats) 40 | 41 | go dnsmasqSignals() 42 | if MemoryFlag { 43 | go sendDnsmasqMemStats() 44 | } 45 | for line := range t.Lines { 46 | // Blank lines really mess this up - this protects against it. 47 | if line.Text == "" { 48 | continue 49 | } 50 | // Let's process the lines. 51 | content := strings.Split(line.Text, "]: ")[1] 52 | if strings.HasPrefix(content, "time") { 53 | Log(fmt.Sprintf("line: %s", content), "debug") 54 | grabTimestamp(content) 55 | } 56 | if strings.HasPrefix(content, "queries") { 57 | Log(fmt.Sprintf("line: %s", content), "debug") 58 | queriesForwarded(content) 59 | queriesLocal(content) 60 | queriesAuthoritativeZones(content) 61 | } 62 | if strings.HasPrefix(content, "server") { 63 | Log(fmt.Sprintf("line: %s", content), "debug") 64 | serverStats(content) 65 | } 66 | if strings.HasPrefix(content, "read") { 67 | Log(fmt.Sprintf("line: %s", content), "debug") 68 | readStats(content) 69 | } 70 | } 71 | } 72 | 73 | // grabTimestamp pulls the timestamp out of the logs and checks 74 | // to see if we can send stats via checkStats()/ 75 | func grabTimestamp(content string) { 76 | // Check to see if we can send stats. 77 | // A new timestamp means we're getting new stats. 78 | checkStats() 79 | // Grab the timestamp from the log line. 80 | r := regexp.MustCompile(`\d+`) 81 | timestamp := r.FindString(content) 82 | unixTimestamp, _ := strconv.ParseInt(timestamp, 10, 64) 83 | CurrentTimestamp = unixTimestamp 84 | Log(fmt.Sprintf("StatsCurrent: %#v", StatsCurrent), "debug") 85 | StatsCurrent.timestamp = unixTimestamp 86 | Log(fmt.Sprintf("Timestamp: %d", unixTimestamp), "debug") 87 | } 88 | 89 | // checkStats looks to see if we have current and previous stats and 90 | // then does what's appropriate. 91 | func checkStats() { 92 | // If we have actual stats in both Current and Previous. 93 | if (StatsCurrent.timestamp > 0) && (StatsPrevious.timestamp > 0) { 94 | // Let's send the stats to Datadog. 95 | SendSignalStats(*StatsCurrent, *StatsPrevious) 96 | Log(fmt.Sprintf("Current : %#v", StatsCurrent), "debug") 97 | Log(fmt.Sprintf("Previous: %#v", StatsPrevious), "debug") 98 | // Copy Current to Previous and zero out current. 99 | StatsPrevious = StatsCurrent 100 | StatsCurrent = new(DNSStats) 101 | } else if (StatsCurrent.timestamp > 0) && (StatsPrevious.timestamp == 0) { 102 | // We don't have enough stats to send. 103 | // Copy Current to Previous and zero out current. 104 | Log("Not enough stats to send.", "info") 105 | StatsPrevious = StatsCurrent 106 | StatsCurrent = new(DNSStats) 107 | } else if (StatsCurrent.timestamp == 0) && (StatsPrevious.timestamp == 0) { 108 | Log("Just starting up - nothing to do.", "info") 109 | } 110 | } 111 | 112 | // SendSignalStats sends stats to Datadog using copies of the current data. 113 | // TODO: Right now we're ignoring all sorts of stats - will see if we need them. 114 | func SendSignalStats(current DNSStats, previous DNSStats) { 115 | Log("Sending stats now.", "debug") 116 | Log(fmt.Sprintf("Current Copy : %#v", current), "debug") 117 | Log(fmt.Sprintf("Previous Copy: %#v", previous), "debug") 118 | forwards := current.queriesForwarded - previous.queriesForwarded 119 | locallyAnswered := current.queriesLocal - previous.queriesLocal 120 | dog := DogConnect() 121 | // Make sure the stats are positive - if they're negative dnsmasq must have been 122 | // restarted and those numbers will not be accurate. 123 | if forwards >= 0 { 124 | sendQueriesStats("dnsmasq.queries", forwards, "query:forward", dog) 125 | Log(fmt.Sprintf("Forwards: %d", forwards), "debug") 126 | } else { 127 | Log("Negative forwarded queries detected - dnsmasq must have been restarted.", "info") 128 | sendQueriesStats("dnsmasq.queries", current.queriesForwarded, "query:forward", dog) 129 | Log(fmt.Sprintf("Forwards: %d", current.queriesForwarded), "debug") 130 | } 131 | if locallyAnswered >= 0 { 132 | sendQueriesStats("dnsmasq.queries", locallyAnswered, "query:local", dog) 133 | Log(fmt.Sprintf("Locally Answered: %d", locallyAnswered), "debug") 134 | } else { 135 | Log("Negative locally answered queries detected - dnsmasq must have been restarted.", "info") 136 | sendQueriesStats("dnsmasq.queries", current.queriesLocal, "query:local", dog) 137 | Log(fmt.Sprintf("Locally Answered: %d", current.queriesLocal), "debug") 138 | } 139 | } 140 | 141 | // sendQueriesStats actually sends the stats to Dogstatsd. 142 | func sendQueriesStats(metric string, value int64, additionalTag string, dog *statsd.Client) { 143 | tags := dog.Tags 144 | dog.Tags = append(dog.Tags, additionalTag) 145 | if os.Getenv("GOSHE_ADDITIONAL_TAGS") != "" { 146 | dog.Tags = append(dog.Tags, os.Getenv("GOSHE_ADDITIONAL_TAGS")) 147 | } 148 | dog.Count(metric, value, tags, signalInterval) 149 | dog.Tags = tags 150 | } 151 | 152 | // serverStats gets the stats for a DNSServer struct. 153 | func serverStats(content string) { 154 | r := regexp.MustCompile(`server (\d+\.\d+\.\d+\.\d+#\d+): queries sent (\d+), retried or failed (\d+)`) 155 | server := r.FindAllStringSubmatch(content, -1) 156 | if server != nil { 157 | srvr := server[0] 158 | serverAddress := srvr[1] 159 | serverAddressSent, _ := strconv.ParseInt(srvr[2], 10, 64) 160 | serverAddressRetryFailures, _ := strconv.ParseInt(srvr[3], 10, 64) 161 | serverStruct := DNSServer{timestamp: CurrentTimestamp, address: serverAddress, queriesSent: serverAddressSent, queriesFailed: serverAddressRetryFailures} 162 | StatsCurrent.servers = append(StatsCurrent.servers, serverStruct) 163 | Log(fmt.Sprintf("Time: %d Server: %s Queries: %d Retries/Failures: %d\n", CurrentTimestamp, serverAddress, serverAddressSent, serverAddressRetryFailures), "debug") 164 | } 165 | } 166 | 167 | // queriesForwarded gets how many queries are forwarded to a DNSServer 168 | func queriesForwarded(content string) { 169 | r := regexp.MustCompile(`forwarded (\d+),`) 170 | forwarded := r.FindAllStringSubmatch(content, -1) 171 | if forwarded != nil { 172 | fwd := forwarded[0] 173 | queriesForwarded, _ := strconv.ParseInt(fwd[1], 10, 64) 174 | StatsCurrent.queriesForwarded = queriesForwarded 175 | Log(fmt.Sprintf("Forwarded Queries: %d", queriesForwarded), "debug") 176 | } 177 | } 178 | 179 | // queriesLocal gets how many queries are answered locally. Hosts files 180 | // are included. 181 | func queriesLocal(content string) { 182 | r := regexp.MustCompile(`queries answered locally (\d+)`) 183 | local := r.FindAllStringSubmatch(content, -1) 184 | if local != nil { 185 | lcl := local[0] 186 | localResponses, _ := strconv.ParseInt(lcl[1], 10, 64) 187 | StatsCurrent.queriesLocal = localResponses 188 | Log(fmt.Sprintf("Responded Locally: %d", localResponses), "debug") 189 | } 190 | } 191 | 192 | // queriesAuthoritativeZones gets how many authoritative zones are present. 193 | func queriesAuthoritativeZones(content string) { 194 | r := regexp.MustCompile(`for authoritative zones (\d+)`) 195 | zones := r.FindAllStringSubmatch(content, -1) 196 | if zones != nil { 197 | zone := zones[0] 198 | authoritativeZones, _ := strconv.ParseInt(zone[1], 10, 64) 199 | StatsCurrent.authoritativeZones = authoritativeZones 200 | Log(fmt.Sprintf("Authoritative Zones: %d", authoritativeZones), "debug") 201 | } 202 | } 203 | 204 | func sendHistogramStats(metric string, value float64, additionalTag string, dog *statsd.Client) { 205 | tags := dog.Tags 206 | dog.Tags = append(dog.Tags, additionalTag) 207 | if os.Getenv("GOSHE_ADDITIONAL_TAGS") != "" { 208 | dog.Tags = append(dog.Tags, os.Getenv("GOSHE_ADDITIONAL_TAGS")) 209 | } 210 | dog.Histogram(metric, value, tags, 1) 211 | dog.Tags = tags 212 | } 213 | 214 | func readStats(content string) { 215 | filename, addresses := LoadFilesStats(content) 216 | if filename != "" && addresses > 0 { 217 | Log("Sending the loaded file stats", "debug") 218 | dog := DogConnect() 219 | fileTag := fmt.Sprintf("filename:%s", filename) 220 | sendHistogramStats("dnsmasq.hosts_file_stats", addresses, fileTag, dog) 221 | } 222 | } 223 | 224 | // LoadFilesStats - a testable function to get the loaded file stats. 225 | func LoadFilesStats(content string) (string, float64) { 226 | r := regexp.MustCompile(`read (.*) - (\d+) addresses`) 227 | domainsLoaded := r.FindAllStringSubmatch(content, -1) 228 | if domainsLoaded != nil { 229 | pieces := domainsLoaded[0] 230 | filename := pieces[1] 231 | addresses, _ := strconv.ParseFloat(pieces[2], 64) 232 | Log(fmt.Sprintf("Filename: %s, Addresses: %f", filename, addresses), "debug") 233 | return filename, addresses 234 | } 235 | return "", 0 236 | } 237 | 238 | // dnsmasqSignals loops and send USR1 to each dnsmasq process 239 | // after each signalInterval - USR1 outputs logs with statistics. 240 | func dnsmasqSignals() { 241 | for { 242 | procs := GetMatches("dnsmasq", false) 243 | // If we've defined this ENV VAR - then we do NOT want to send 244 | // signals. It's a way to run multiple versions at the same time. 245 | if os.Getenv("GOSHE_DISABLE_DNSMASQ_SIGNALS") == "" { 246 | sendUSR1(procs) 247 | } 248 | time.Sleep(time.Duration(signalInterval) * time.Second) 249 | } 250 | } 251 | 252 | // sendUSR1 actually sends the signal. 253 | func sendUSR1(procs []ProcessList) { 254 | if len(procs) > 0 { 255 | for _, proc := range procs { 256 | proc.USR1() 257 | } 258 | } 259 | } 260 | 261 | // sendDnsmasqMemStats sends memory stats for dnsmasq if MemoryFlag 262 | // is true. 263 | func sendDnsmasqMemStats() { 264 | for { 265 | matches := GetMatches("dnsmasq", false) 266 | if matches != nil { 267 | fmt.Printf("Found %d matches.\n", len(matches)) 268 | SendMetrics(matches) 269 | } else { 270 | fmt.Printf("Did not find any matches.\n") 271 | } 272 | time.Sleep(time.Duration(Interval) * time.Second) 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/http/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |WWC.KK__WK_W_.CK.C..W........................................... 19 |20 |
Scoreboard Key:
21 | "_" Waiting for Connection,
22 | "S" Starting up,
23 | "R" Reading Request,
24 | "W" Sending Reply,
25 | "K" Keepalive (read),
26 | "D" DNS Lookup,
27 | "C" Closing connection,
28 | "L" Logging,
29 | "G" Gracefully finishing,
30 | "I" Idle cleanup of worker,
31 | "." Open slot with no current process
| Srv | PID | Acc | M | CPU 36 | | SS | Req | Conn | Child | Slot | Client | VHost | Request |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0-0 | 10434 | 2/6/29078 | W 39 | | 0.01 | 12 | 0 | 45.3 | 0.05 | 1030.61 40 | | 70.75.244.156 | crave.bam.nonwebdev.com | GET /upload/body_image/193/73/alohabanner-01.jpg HTTP/1.1 |
| 1-0 | 10360 | 3/22/28103 | W 43 | | 0.34 | 12 | 0 | 4.3 | 0.07 | 903.65 44 | | 70.75.244.156 | crave.bam.nonwebdev.com | GET /upload/body_image/193/73/alohabanner-01.jpg HTTP/1.1 |
| 2-0 | 10473 | 2/4/27166 | C 47 | | 0.00 | 1 | 0 | 18.1 | 0.02 | 822.82 48 | | 192.199.63.173 | *.bam.nonwebdev.com | NULL |
| 3-0 | - | 0/0/27561 | . 51 | | 0.00 | 1 | 0 | 0.0 | 0.00 | 930.32 52 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 4-0 | 10282 | 1/53/26375 | K 55 | | 4.91 | 0 | 0 | 0.6 | 6.57 | 978.42 56 | | 70.75.244.156 | crave.bam.nonwebdev.com | GET /stylesheets/tabs.css HTTP/1.1 |
| 5-0 | 10345 | 1/39/26313 | K 59 | | 0.20 | 0 | 0 | 3.5 | 0.37 | 802.91 60 | | 70.75.244.156 | crave.bam.nonwebdev.com | GET /stylesheets/default.css HTTP/1.1 |
| 6-0 | 10435 | 0/22/25136 | _ 63 | | 0.47 | 3 | 1 | 0.0 | 0.01 | 890.16 64 | | 162.158.56.101 | *.bam.nonwebdev.com | NULL |
| 7-0 | 10475 | 0/3/24629 | _ 67 | | 0.05 | 1 | 448 | 0.0 | 0.00 | 800.37 68 | | 69.191.211.207 | harvest.bam.nonwebdev.com | GET /investor-relations/investor-information/ HTTP/1.1 |
| 8-0 | 10453 | 0/11/23076 | W 71 | | 0.08 | 0 | 0 | 0.0 | 0.06 | 835.69 72 | | 127.0.0.1 | *.bam.nonwebdev.com | GET /server-status HTTP/1.1 |
| 9-0 | 10285 | 3/46/22038 | K 75 | | 1.11 | 0 | 0 | 4.3 | 0.58 | 810.47 76 | | 70.75.244.156 | crave.bam.nonwebdev.com | GET /stylesheets/print.css HTTP/1.1 |
| 10-0 | 10476 | 0/2/20044 | _ 79 | | 0.00 | 3 | 1 | 0.0 | 0.00 | 650.15 80 | | 162.158.56.101 | *.bam.nonwebdev.com | NULL |
| 11-0 | 10454 | 2/14/19480 | W 83 | | 0.07 | 0 | 0 | 45.3 | 0.06 | 728.11 84 | | 70.75.244.156 | crave.bam.nonwebdev.com | GET /upload/body_image/193/73/alohabanner-01.jpg HTTP/1.1 |
| 12-0 | 10477 | 0/2/18416 | _ 87 | | 0.00 | 1 | 1 | 0.0 | 0.00 | 587.17 88 | | localhost | *.bam.nonwebdev.com | NULL |
| 13-0 | - | 0/0/17551 | . 91 | | 2.49 | 0 | 0 | 0.0 | 0.00 | 527.85 92 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 14-0 | 10478 | 3/3/15643 | C 95 | | 0.08 | 1 | 0 | 47.5 | 0.05 | 589.58 96 | | 192.199.63.173 | *.bam.nonwebdev.com | NULL |
| 15-0 | 10382 | 4/30/14352 | K 99 | | 0.14 | 0 | 0 | 17.7 | 0.52 | 513.64 100 | | 70.75.244.156 | crave.bam.nonwebdev.com | GET /javascripts/tabs.js HTTP/1.1 |
| 16-0 | - | 0/0/13051 | . 103 | | 0.00 | 2 | 0 | 0.0 | 0.00 | 411.40 104 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 17-0 | 10352 | 2/31/11319 | C 107 | | 0.50 | 0 | 172 | 0.0 | 0.24 | 383.20 108 | | 216.197.194.116 | *.bam.nonwebdev.com | NULL |
| 18-0 | - | 0/0/10137 | . 111 | | 0.00 | 84 | 0 | 0.0 | 0.00 | 412.55 112 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 19-0 | - | 0/0/8950 | . 115 | | 0.00 | 98 | 0 | 0.0 | 0.00 | 248.03 116 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 20-0 | 10355 | 1/42/8592 | W 119 | | 1.82 | 0 | 0 | 2.2 | 0.46 | 328.14 120 | | 70.75.244.156 | crave.bam.nonwebdev.com | GET /upload/body_image/237/06/midlewoodland-01.png HTTP/1.1 |
| 21-0 | - | 0/0/7288 | . 123 | | 0.00 | 83 | 0 | 0.0 | 0.00 | 195.64 124 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 22-0 | - | 0/0/6970 | . 127 | | 0.21 | 61 | 0 | 0.0 | 0.00 | 214.68 128 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 23-0 | - | 0/0/6049 | . 131 | | 1.31 | 96 | 0 | 0.0 | 0.00 | 183.95 132 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 24-0 | - | 0/0/5187 | . 135 | | 0.04 | 235 | 0 | 0.0 | 0.00 | 121.41 136 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 25-0 | - | 0/0/4182 | . 139 | | 0.21 | 237 | 0 | 0.0 | 0.00 | 124.07 140 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 26-0 | - | 0/0/3733 | . 143 | | 0.54 | 256 | 0 | 0.0 | 0.00 | 144.16 144 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 27-0 | - | 0/0/2969 | . 147 | | 0.00 | 250 | 0 | 0.0 | 0.00 | 80.92 148 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 28-0 | - | 0/0/2029 | . 151 | | 0.00 | 249 | 0 | 0.0 | 0.00 | 69.14 152 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 29-0 | - | 0/0/2007 | . 155 | | 0.04 | 222 | 0 | 0.0 | 0.00 | 55.19 156 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 30-0 | - | 0/0/1471 | . 159 | | 0.50 | 118 | 0 | 0.0 | 0.00 | 46.43 160 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 31-0 | - | 0/0/1465 | . 163 | | 0.00 | 248 | 0 | 0.0 | 0.00 | 39.66 164 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 32-0 | - | 0/0/1192 | . 167 | | 0.06 | 234 | 0 | 0.0 | 0.00 | 53.33 168 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 33-0 | - | 0/0/1083 | . 171 | | 0.00 | 260 | 0 | 0.0 | 0.00 | 34.44 172 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 34-0 | - | 0/0/1050 | . 175 | | 0.00 | 259 | 0 | 0.0 | 0.00 | 40.94 176 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 35-0 | - | 0/0/1143 | . 179 | | 0.87 | 169 | 0 | 0.0 | 0.00 | 32.02 180 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 36-0 | - | 0/0/990 | . 183 | | 0.06 | 240 | 0 | 0.0 | 0.00 | 29.76 184 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 37-0 | - | 0/0/1092 | . 187 | | 0.26 | 207 | 0 | 0.0 | 0.00 | 41.30 188 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 38-0 | - | 0/0/1001 | . 191 | | 0.00 | 258 | 0 | 0.0 | 0.00 | 17.76 192 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 39-0 | - | 0/0/987 | . 195 | | 0.59 | 189 | 0 | 0.0 | 0.00 | 41.35 196 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 40-0 | - | 0/0/786 | . 199 | | 0.00 | 257 | 0 | 0.0 | 0.00 | 18.00 200 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 41-0 | - | 0/0/528 | . 203 | | 0.00 | 1325 | 0 | 0.0 | 0.00 | 19.97 204 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 42-0 | - | 0/0/440 | . 207 | | 0.22 | 1335 | 0 | 0.0 | 0.00 | 14.81 208 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 43-0 | - | 0/0/263 | . 211 | | 0.00 | 1355 | 0 | 0.0 | 0.00 | 4.09 212 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 44-0 | - | 0/0/169 | . 215 | | 0.01 | 1333 | 0 | 0.0 | 0.00 | 3.81 216 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 45-0 | - | 0/0/161 | . 219 | | 0.00 | 1354 | 0 | 0.0 | 0.00 | 9.98 220 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 46-0 | - | 0/0/136 | . 223 | | 1.39 | 1164 | 0 | 0.0 | 0.00 | 3.31 224 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 47-0 | - | 0/0/180 | . 227 | | 1.22 | 1276 | 0 | 0.0 | 0.00 | 4.96 228 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 48-0 | - | 0/0/95 | . 231 | | 0.04 | 1316 | 0 | 0.0 | 0.00 | 0.47 232 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 49-0 | - | 0/0/80 | . 235 | | 0.00 | 1353 | 0 | 0.0 | 0.00 | 2.90 236 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 50-0 | - | 0/0/60 | . 239 | | 0.00 | 1352 | 0 | 0.0 | 0.00 | 2.36 240 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 51-0 | - | 0/0/116 | . 243 | | 0.70 | 1300 | 0 | 0.0 | 0.00 | 3.88 244 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 52-0 | - | 0/0/91 | . 247 | | 0.07 | 13196 | 491 | 0.0 | 0.00 | 3.28 248 | | 69.191.211.213 | *.bam.nonwebdev.com | NULL |
| 53-0 | - | 0/0/52 | . 251 | | 0.00 | 13205 | 0 | 0.0 | 0.00 | 3.32 252 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 54-0 | - | 0/0/36 | . 255 | | 1.54 | 13170 | 0 | 0.0 | 0.00 | 4.99 256 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 55-0 | - | 0/0/28 | . 259 | | 0.05 | 13192 | 0 | 0.0 | 0.00 | 0.62 260 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 56-0 | - | 0/0/27 | . 263 | | 0.07 | 13185 | 0 | 0.0 | 0.00 | 0.52 264 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 57-0 | - | 0/0/49 | . 267 | | 0.00 | 13218 | 0 | 0.0 | 0.00 | 0.58 268 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 58-0 | - | 0/0/99 | . 271 | | 0.07 | 13173 | 0 | 0.0 | 0.00 | 1.65 272 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 59-0 | - | 0/0/37 | . 275 | | 0.01 | 13171 | 0 | 0.0 | 0.00 | 1.80 276 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 60-0 | - | 0/0/28 | . 279 | | 0.00 | 13204 | 0 | 0.0 | 0.00 | 0.11 280 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 61-0 | - | 0/0/29 | . 283 | | 0.00 | 13180 | 0 | 0.0 | 0.00 | 0.06 284 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 62-0 | - | 0/0/106 | . 287 | | 0.31 | 13152 | 0 | 0.0 | 0.00 | 1.62 288 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| 63-0 | - | 0/0/59 | . 291 | | 0.00 | 13203 | 0 | 0.0 | 0.00 | 1.95 292 | | 127.0.0.1 | *.bam.nonwebdev.com | OPTIONS * HTTP/1.0 |
| Srv | Child Server number - generation |
|---|---|
| PID | OS process ID |
| Acc | Number of accesses this connection / this child / this slot |
| M | Mode of operation |
| CPU | CPU usage, number of seconds |
| SS | Seconds since beginning of most recent request |
| Req | Milliseconds required to process most recent request |
| Conn | Kilobytes transferred this connection |
| Child | Megabytes transferred this child |
| Slot | Total megabytes transferred this slot |