github.com/m3db/m3@v1.5.0/src/x/debug/debug.go (about) 1 // Copyright (c) 2019 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package debug 22 23 import ( 24 "archive/zip" 25 "bytes" 26 "fmt" 27 "io" 28 "net/http" 29 "time" 30 31 xerrors "github.com/m3db/m3/src/x/errors" 32 "github.com/m3db/m3/src/x/instrument" 33 xhttp "github.com/m3db/m3/src/x/net/http" 34 35 "go.uber.org/zap" 36 ) 37 38 const ( 39 // DebugURL is the url for the debug dump endpoint. 40 DebugURL = "/debug/dump" 41 // DebugMethod is the HTTP method. 42 DebugMethod = http.MethodGet 43 ) 44 45 // Source is the interface that must be implemented to provide a new debug 46 // source. Each debug source's Write method will be called to write out a debug 47 // file for that source into the overall debug zip file. 48 type Source interface { 49 // Write writes it's debug information into the provided writer. 50 Write(w io.Writer, r *http.Request) error 51 } 52 53 // ZipWriter aggregates sources and writes them in a zip file. 54 type ZipWriter interface { 55 // WriteZip writes a ZIP file in the provided writer. 56 // The archive contains the dumps of all sources in separate files. 57 WriteZip(io.Writer, *http.Request) error 58 // RegisterSource adds a new source to the produced archive. 59 RegisterSource(fileName string, source Source) error 60 // HTTPHandler sends out the ZIP file as raw bytes. 61 HTTPHandler() http.Handler 62 // RegisterHandler wires the HTTPHandlerFunc with the given router. 63 RegisterHandler(handlerPath string, router *http.ServeMux) error 64 } 65 66 type zipWriter struct { 67 sources map[string]Source 68 logger *zap.Logger 69 } 70 71 // NewZipWriter returns an instance of an ZipWriter. The passed prefix 72 // indicates the folder where to save the zip files. 73 func NewZipWriter(iopts instrument.Options) ZipWriter { 74 return &zipWriter{ 75 sources: make(map[string]Source), 76 logger: iopts.Logger(), 77 } 78 } 79 80 // NewZipWriterWithDefaultSources returns a zipWriter with the following 81 // debug sources already registered: CPU, heap, host, goroutines. 82 func NewZipWriterWithDefaultSources( 83 cpuProfileDuration time.Duration, 84 iopts instrument.Options, 85 ) (ZipWriter, error) { 86 zw := NewZipWriter(iopts) 87 88 err := zw.RegisterSource("cpu.prof", NewCPUProfileSource(cpuProfileDuration)) 89 if err != nil { 90 return nil, fmt.Errorf("unable to register CPUProfileSource: %s", err) 91 } 92 93 err = zw.RegisterSource("heap.prof", NewHeapDumpSource()) 94 if err != nil { 95 return nil, fmt.Errorf("unable to register HeapDumpSource: %s", err) 96 } 97 98 err = zw.RegisterSource("host.json", NewHostInfoSource()) 99 if err != nil { 100 return nil, fmt.Errorf("unable to register HostInfoSource: %s", err) 101 } 102 103 gp, err := NewProfileSource("goroutine", 2) 104 if err != nil { 105 return nil, fmt.Errorf("unable to create goroutineProfileSource: %s", err) 106 } 107 108 err = zw.RegisterSource("goroutine.prof", gp) 109 return zw, nil 110 } 111 112 // RegisterSource adds a new source in the ZipWriter instance. 113 // It will return an error if a source with the same filename exists. 114 func (i *zipWriter) RegisterSource(dumpFileName string, p Source) error { 115 if _, ok := i.sources[dumpFileName]; ok { 116 return fmt.Errorf("dumpfile already registered %s", dumpFileName) 117 } 118 i.sources[dumpFileName] = p 119 return nil 120 } 121 122 // WriteZip writes a ZIP file with the data from all sources in the given writer. 123 // It will return an error if any of the sources fail to write their data. 124 func (i *zipWriter) WriteZip(w io.Writer, r *http.Request) error { 125 zw := zip.NewWriter(w) 126 defer zw.Close() 127 128 for filename, p := range i.sources { 129 fw, err := zw.Create(filename) 130 if err != nil { 131 return err 132 } 133 err = p.Write(fw, r) 134 if err != nil { 135 return xerrors.Wrapf(err, "error writing '%v'", filename) 136 } 137 } 138 return nil 139 } 140 141 func (i *zipWriter) HTTPHandler() http.Handler { 142 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 143 buf := bytes.NewBuffer([]byte{}) 144 if err := i.WriteZip(buf, r); err != nil { 145 xhttp.WriteError(w, fmt.Errorf("unable to write ZIP file: %s", err)) 146 return 147 } 148 if _, err := io.Copy(w, buf); err != nil { 149 i.logger.Error("unable to write ZIP response", zap.Error(err)) 150 } 151 }) 152 } 153 154 func (i *zipWriter) RegisterHandler(path string, r *http.ServeMux) error { 155 r.Handle(path, i.HTTPHandler()) 156 return nil 157 }