github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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  }