├── .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 | [![wercker status](https://app.wercker.com/status/f25e70250066e5f1e03744ef4d5be79e/m "wercker status")](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 | [![wercker status](https://app.wercker.com/status/f25e70250066e5f1e03744ef4d5be79e/m "wercker status")](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 | Apache Status 4 | 5 |

Apache Server Status for localhost

6 | 7 |
Server Version: Apache/2.2.14 (Ubuntu) PHP/5.3.2-1ubuntu4.30 with Suhosin-Patch
8 |
Server Built: Mar 5 2015 18:10:23 9 |

10 |
Current Time: Monday, 11-Jan-2016 03:58:54 UTC
11 |
Restart Time: Sunday, 10-Jan-2016 01:38:15 UTC
12 |
Parent Server Generation: 0
13 |
Server uptime: 1 day 2 hours 20 minutes 39 seconds
14 |
Total accesses: 472553 - Total Traffic: 15.5 GB
15 |
CPU Usage: u17.7 s4.96 cu0 cs0 - .0239% CPU load
16 |
4.98 requests/sec - 171.2 kB/second - 34.4 kB/request
17 |
12 requests currently being processed, 4 idle workers
18 |
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

32 |

33 | 34 | 35 | 37 | 38 | 41 | 42 | 45 | 46 | 49 | 50 | 53 | 54 | 57 | 58 | 61 | 62 | 65 | 66 | 69 | 70 | 73 | 74 | 77 | 78 | 81 | 82 | 85 | 86 | 89 | 90 | 93 | 94 | 97 | 98 | 101 | 102 | 105 | 106 | 109 | 110 | 113 | 114 | 117 | 118 | 121 | 122 | 125 | 126 | 129 | 130 | 133 | 134 | 137 | 138 | 141 | 142 | 145 | 146 | 149 | 150 | 153 | 154 | 157 | 158 | 161 | 162 | 165 | 166 | 169 | 170 | 173 | 174 | 177 | 178 | 181 | 182 | 185 | 186 | 189 | 190 | 193 | 194 | 197 | 198 | 201 | 202 | 205 | 206 | 209 | 210 | 213 | 214 | 217 | 218 | 221 | 222 | 225 | 226 | 229 | 230 | 233 | 234 | 237 | 238 | 241 | 242 | 245 | 246 | 249 | 250 | 253 | 254 | 257 | 258 | 261 | 262 | 265 | 266 | 269 | 270 | 273 | 274 | 277 | 278 | 281 | 282 | 285 | 286 | 289 | 290 | 293 | 294 |
SrvPIDAccMCPU 36 | SSReqConnChildSlotClientVHostRequest
0-0104342/6/29078W 39 | 0.0112045.30.051030.61 40 | 70.75.244.156crave.bam.nonwebdev.comGET /upload/body_image/193/73/alohabanner-01.jpg HTTP/1.1
1-0103603/22/28103W 43 | 0.341204.30.07903.65 44 | 70.75.244.156crave.bam.nonwebdev.comGET /upload/body_image/193/73/alohabanner-01.jpg HTTP/1.1
2-0104732/4/27166C 47 | 0.001018.10.02822.82 48 | 192.199.63.173*.bam.nonwebdev.comNULL
3-0-0/0/27561. 51 | 0.00100.00.00930.32 52 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
4-0102821/53/26375K 55 | 4.91000.66.57978.42 56 | 70.75.244.156crave.bam.nonwebdev.comGET /stylesheets/tabs.css HTTP/1.1
5-0103451/39/26313K 59 | 0.20003.50.37802.91 60 | 70.75.244.156crave.bam.nonwebdev.comGET /stylesheets/default.css HTTP/1.1
6-0104350/22/25136_ 63 | 0.47310.00.01890.16 64 | 162.158.56.101*.bam.nonwebdev.comNULL
7-0104750/3/24629_ 67 | 0.0514480.00.00800.37 68 | 69.191.211.207harvest.bam.nonwebdev.comGET /investor-relations/investor-information/ HTTP/1.1
8-0104530/11/23076W 71 | 0.08000.00.06835.69 72 | 127.0.0.1*.bam.nonwebdev.comGET /server-status HTTP/1.1
9-0102853/46/22038K 75 | 1.11004.30.58810.47 76 | 70.75.244.156crave.bam.nonwebdev.comGET /stylesheets/print.css HTTP/1.1
10-0104760/2/20044_ 79 | 0.00310.00.00650.15 80 | 162.158.56.101*.bam.nonwebdev.comNULL
11-0104542/14/19480W 83 | 0.070045.30.06728.11 84 | 70.75.244.156crave.bam.nonwebdev.comGET /upload/body_image/193/73/alohabanner-01.jpg HTTP/1.1
12-0104770/2/18416_ 87 | 0.00110.00.00587.17 88 | localhost*.bam.nonwebdev.comNULL
13-0-0/0/17551. 91 | 2.49000.00.00527.85 92 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
14-0104783/3/15643C 95 | 0.081047.50.05589.58 96 | 192.199.63.173*.bam.nonwebdev.comNULL
15-0103824/30/14352K 99 | 0.140017.70.52513.64 100 | 70.75.244.156crave.bam.nonwebdev.comGET /javascripts/tabs.js HTTP/1.1
16-0-0/0/13051. 103 | 0.00200.00.00411.40 104 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
17-0103522/31/11319C 107 | 0.5001720.00.24383.20 108 | 216.197.194.116*.bam.nonwebdev.comNULL
18-0-0/0/10137. 111 | 0.008400.00.00412.55 112 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
19-0-0/0/8950. 115 | 0.009800.00.00248.03 116 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
20-0103551/42/8592W 119 | 1.82002.20.46328.14 120 | 70.75.244.156crave.bam.nonwebdev.comGET /upload/body_image/237/06/midlewoodland-01.png HTTP/1.1
21-0-0/0/7288. 123 | 0.008300.00.00195.64 124 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
22-0-0/0/6970. 127 | 0.216100.00.00214.68 128 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
23-0-0/0/6049. 131 | 1.319600.00.00183.95 132 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
24-0-0/0/5187. 135 | 0.0423500.00.00121.41 136 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
25-0-0/0/4182. 139 | 0.2123700.00.00124.07 140 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
26-0-0/0/3733. 143 | 0.5425600.00.00144.16 144 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
27-0-0/0/2969. 147 | 0.0025000.00.0080.92 148 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
28-0-0/0/2029. 151 | 0.0024900.00.0069.14 152 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
29-0-0/0/2007. 155 | 0.0422200.00.0055.19 156 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
30-0-0/0/1471. 159 | 0.5011800.00.0046.43 160 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
31-0-0/0/1465. 163 | 0.0024800.00.0039.66 164 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
32-0-0/0/1192. 167 | 0.0623400.00.0053.33 168 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
33-0-0/0/1083. 171 | 0.0026000.00.0034.44 172 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
34-0-0/0/1050. 175 | 0.0025900.00.0040.94 176 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
35-0-0/0/1143. 179 | 0.8716900.00.0032.02 180 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
36-0-0/0/990. 183 | 0.0624000.00.0029.76 184 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
37-0-0/0/1092. 187 | 0.2620700.00.0041.30 188 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
38-0-0/0/1001. 191 | 0.0025800.00.0017.76 192 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
39-0-0/0/987. 195 | 0.5918900.00.0041.35 196 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
40-0-0/0/786. 199 | 0.0025700.00.0018.00 200 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
41-0-0/0/528. 203 | 0.00132500.00.0019.97 204 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
42-0-0/0/440. 207 | 0.22133500.00.0014.81 208 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
43-0-0/0/263. 211 | 0.00135500.00.004.09 212 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
44-0-0/0/169. 215 | 0.01133300.00.003.81 216 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
45-0-0/0/161. 219 | 0.00135400.00.009.98 220 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
46-0-0/0/136. 223 | 1.39116400.00.003.31 224 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
47-0-0/0/180. 227 | 1.22127600.00.004.96 228 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
48-0-0/0/95. 231 | 0.04131600.00.000.47 232 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
49-0-0/0/80. 235 | 0.00135300.00.002.90 236 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
50-0-0/0/60. 239 | 0.00135200.00.002.36 240 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
51-0-0/0/116. 243 | 0.70130000.00.003.88 244 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
52-0-0/0/91. 247 | 0.07131964910.00.003.28 248 | 69.191.211.213*.bam.nonwebdev.comNULL
53-0-0/0/52. 251 | 0.001320500.00.003.32 252 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
54-0-0/0/36. 255 | 1.541317000.00.004.99 256 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
55-0-0/0/28. 259 | 0.051319200.00.000.62 260 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
56-0-0/0/27. 263 | 0.071318500.00.000.52 264 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
57-0-0/0/49. 267 | 0.001321800.00.000.58 268 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
58-0-0/0/99. 271 | 0.071317300.00.001.65 272 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
59-0-0/0/37. 275 | 0.011317100.00.001.80 276 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
60-0-0/0/28. 279 | 0.001320400.00.000.11 280 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
61-0-0/0/29. 283 | 0.001318000.00.000.06 284 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
62-0-0/0/106. 287 | 0.311315200.00.001.62 288 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
63-0-0/0/59. 291 | 0.001320300.00.001.95 292 | 127.0.0.1*.bam.nonwebdev.comOPTIONS * HTTP/1.0
295 |


296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 |
SrvChild Server number - generation
PIDOS process ID
AccNumber of accesses this connection / this child / this slot
MMode of operation
CPUCPU usage, number of seconds
SSSeconds since beginning of most recent request
ReqMilliseconds required to process most recent request
ConnKilobytes transferred this connection
ChildMegabytes transferred this child
SlotTotal megabytes transferred this slot
307 |
308 |
Apache Server at localhost Port 8181
309 | 310 | --------------------------------------------------------------------------------