diff --git a/fgtrace.go b/fgtrace.go index e209fb5..77bd2d2 100644 --- a/fgtrace.go +++ b/fgtrace.go @@ -3,9 +3,11 @@ package fgtrace import ( "bytes" "errors" + "fmt" "io" "net/http" "runtime" + "strconv" "strings" "time" @@ -96,7 +98,53 @@ func (c Config) Trace() *Trace { func (c Config) ServeHTTP(w http.ResponseWriter, r *http.Request) { c = c.WithDefaults() c.Dst = Writer(w) + + params := []struct { + Name string + Fn func(val string) error + }{ + { + Name: "seconds", + Fn: func(val string) error { + seconds, err := strconv.ParseFloat(val, 64) + if err != nil { + return err + } else if seconds <= 0 { + return errors.New("invalid value") + } + c.HTTPDuration = time.Duration(float64(time.Second) * seconds) + return nil + }, + }, + { + Name: "hz", + Fn: func(val string) error { + hz, err := strconv.Atoi(val) + if err != nil { + return err + } else if hz <= 0 { + return errors.New("invalid value") + } + c.Hz = hz + return nil + }, + }, + } + + for _, p := range params { + val := r.URL.Query().Get(p.Name) + if val == "" { + continue + } else if err := p.Fn(val); err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "bad %s: %q: %s\n", p.Name, val, err) + return + } + } + defer c.Trace().Stop() + time.Sleep(c.HTTPDuration) + } // File is a helper for Config.Dst that returns an io.WriteCloser that creates diff --git a/fgtrace_test.go b/fgtrace_test.go index f19d5f1..dcca9a7 100644 --- a/fgtrace_test.go +++ b/fgtrace_test.go @@ -7,6 +7,7 @@ import ( "fmt" "math" "net/http" + "net/http/httptest" "os" "strings" "testing" @@ -182,6 +183,34 @@ func TestConfig(t *testing.T) { }) }) }) + + t.Run("ServeHTTP", func(t *testing.T) { + t.Run("seconds", func(t *testing.T) { + for _, duration := range []time.Duration{ + 100 * time.Millisecond, + 200 * time.Millisecond, + 300 * time.Millisecond, + } { + rr := httptest.NewRecorder() + r := httptest.NewRequest("GET", fmt.Sprintf("/?seconds=%f", duration.Seconds()), nil) + start := time.Now() + Config{}.ServeHTTP(rr, r) + dt := time.Since(start) + require.InDelta(t, duration, dt, float64(25*time.Millisecond)) + } + }) + + t.Run("hz", func(t *testing.T) { + hz := 123 + rr := httptest.NewRecorder() + r := httptest.NewRequest("GET", fmt.Sprintf("/?hz=%d&seconds=0.1", hz), nil) + Config{}.ServeHTTP(rr, r) + data, err := internal.Unmarshal(rr.Body.Bytes()) + require.NoError(t, err) + require.Equal(t, hz, data.MetaHz()) + }) + + }) } // workloadSimulator calls workloadA followed by workloadB in a loop. Each call diff --git a/handler.go b/handler.go deleted file mode 100644 index 55304ad..0000000 --- a/handler.go +++ /dev/null @@ -1,74 +0,0 @@ -package fgtrace - -// type Handler struct { -// // Duration is the default duration for traces served via http. -// // WithDefaults() sets it to 30s if it is 0. -// Duration time.Duration -// // Hz determines how often the stack traces of all goroutines are captured -// // per second. WithDefaults() sets it to 99 Hz if it is 0. -// Hz int -// } - -// func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - -// } - -// type HttpConfig struct { -// // Duration is the default duration for traces served via http. -// // WithDefaults() sets it to 30s if it is 0. -// Duration time.Duration -// // Hz determines how often the stack traces of all goroutines are captured -// // per second. WithDefaults() sets it to 99 Hz if it is 0. -// Hz int -// } - -// // WithDefaults returns a copy of c with default values applied as described -// // in the type documentation. -// func (c HttpConfig) WithDefaults() HttpConfig { -// if c.Duration == 0 { -// c.Duration = 30 * time.Second -// } -// if c.Hz == 0 { -// c.Hz = defaultHz -// } -// return c -// } - -// DefaultDuration is the default duration for goroutine profile tracing used -// by the Handler() function. -// const DefaultDuration = 30 * time.Second - -// Handler returns an http handler that captures a goroutine profile trace for -// DefaultDuration at DefaultHz and sends it as a reply. The defaults can be -// overwritten using the "seconds" and "hz" query parameters. -// func HandlerFunc() http.Handler { -// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// // var ( -// // seconds float64 -// // hz int -// // err error -// // ) - -// // // parse seconds param -// // if v := r.URL.Query().Get("seconds"); v == "" { -// // seconds = DefaultDuration.Seconds() -// // } else if seconds, err = strconv.ParseFloat(v, 64); err != nil || seconds <= 0 { -// // w.WriteHeader(http.StatusBadRequest) -// // fmt.Fprintf(w, "bad seconds: %q: %s\n", v, err) -// // return -// // } - -// // // parse hz param -// // if v := r.URL.Query().Get("hz"); v == "" { -// // hz = defaultHz -// // } else if hz, err = strconv.Atoi(v); err != nil || hz <= 0 { -// // w.WriteHeader(http.StatusBadRequest) -// // fmt.Fprintf(w, "bad hz: %q: %s\n", v, err) -// // return -// // } - -// // t := Trace(w, WithHz(hz)) -// // defer t.Stop() -// // time.Sleep(time.Duration(seconds * float64(time.Second))) -// }) -// } diff --git a/handler_test.go b/handler_test.go deleted file mode 100644 index d2b0637..0000000 --- a/handler_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package fgtrace - -import ( - "testing" -) - -func TestHandler(t *testing.T) { - // defer goleak.VerifyNone(t) - - // t.Run("duration", func(t *testing.T) { - // for _, duration := range []time.Duration{ - // 100 * time.Millisecond, - // 200 * time.Millisecond, - // } { - // rr := httptest.NewRecorder() - // r := httptest.NewRequest("GET", fmt.Sprintf("/?seconds=%f", duration.Seconds()), nil) - // start := time.Now() - // Handler().ServeHTTP(rr, r) - // dt := time.Since(start) - // require.InDelta(t, duration, dt, float64(25*time.Millisecond)) - // } - // }) - - // t.Run("hz", func(t *testing.T) { - // hz := 123 - // rr := httptest.NewRecorder() - // r := httptest.NewRequest("GET", fmt.Sprintf("/?hz=%d", hz), nil) - // Handler().ServeHTTP(rr, r) - // tr, err := parseTrace(rr.Body) - // require.NoError(t, err) - // require.Equal(t, hz, tr.MetaHz()) - // }) -}