github.com/minio/console@v1.4.1/api/admin_trace.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"net/http"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/minio/madmin-go/v3"
    27  	"github.com/minio/websocket"
    28  )
    29  
    30  // shortTraceMsg Short trace record
    31  type shortTraceMsg struct {
    32  	Host       string    `json:"host"`
    33  	Time       string    `json:"time"`
    34  	Client     string    `json:"client"`
    35  	CallStats  callStats `json:"callStats"`
    36  	FuncName   string    `json:"api"`
    37  	Path       string    `json:"path"`
    38  	Query      string    `json:"query"`
    39  	StatusCode int       `json:"statusCode"`
    40  	StatusMsg  string    `json:"statusMsg"`
    41  }
    42  
    43  type callStats struct {
    44  	Rx       int    `json:"rx"`
    45  	Tx       int    `json:"tx"`
    46  	Duration string `json:"duration"`
    47  	Ttfb     string `json:"timeToFirstByte"`
    48  }
    49  
    50  // trace filters
    51  func matchTrace(opts TraceRequest, traceInfo madmin.ServiceTraceInfo) bool {
    52  	statusCode := int(opts.statusCode)
    53  	method := opts.method
    54  	funcName := opts.funcName
    55  	apiPath := opts.path
    56  
    57  	if statusCode == 0 && method == "" && funcName == "" && apiPath == "" {
    58  		// no specific filtering found trace all the requests
    59  		return true
    60  	}
    61  
    62  	// Filter request path if passed by the user
    63  	if apiPath != "" {
    64  		pathToLookup := strings.ToLower(apiPath)
    65  		pathFromTrace := strings.ToLower(traceInfo.Trace.Path)
    66  
    67  		return strings.Contains(pathFromTrace, pathToLookup)
    68  	}
    69  
    70  	// Filter response status codes if passed by the user
    71  	if statusCode > 0 && traceInfo.Trace.HTTP != nil {
    72  		statusCodeFromTrace := traceInfo.Trace.HTTP.RespInfo.StatusCode
    73  
    74  		return statusCodeFromTrace == statusCode
    75  	}
    76  
    77  	// Filter request method if passed by the user
    78  	if method != "" && traceInfo.Trace.HTTP != nil {
    79  		methodFromTrace := traceInfo.Trace.HTTP.ReqInfo.Method
    80  
    81  		return methodFromTrace == method
    82  	}
    83  
    84  	if funcName != "" {
    85  		funcToLookup := strings.ToLower(funcName)
    86  		funcFromTrace := strings.ToLower(traceInfo.Trace.FuncName)
    87  
    88  		return strings.Contains(funcFromTrace, funcToLookup)
    89  	}
    90  
    91  	return true
    92  }
    93  
    94  // startTraceInfo starts trace of the servers
    95  func startTraceInfo(ctx context.Context, conn WSConn, client MinioAdmin, opts TraceRequest) error {
    96  	// Start listening on all trace activity.
    97  	traceCh := client.serviceTrace(ctx, opts.threshold, opts.s3, opts.internal, opts.storage, opts.os, opts.onlyErrors)
    98  	for {
    99  		select {
   100  		case <-ctx.Done():
   101  			return nil
   102  		case traceInfo, ok := <-traceCh:
   103  			// zero value returned because the channel is closed and empty
   104  			if !ok {
   105  				return nil
   106  			}
   107  			if traceInfo.Err != nil {
   108  				LogError("error on serviceTrace: %v", traceInfo.Err)
   109  				return traceInfo.Err
   110  			}
   111  			if matchTrace(opts, traceInfo) {
   112  				// Serialize message to be sent
   113  				traceInfoBytes, err := json.Marshal(shortTrace(&traceInfo))
   114  				if err != nil {
   115  					LogError("error on json.Marshal: %v", err)
   116  					return err
   117  				}
   118  				// Send Message through websocket connection
   119  				err = conn.writeMessage(websocket.TextMessage, traceInfoBytes)
   120  				if err != nil {
   121  					LogError("error writeMessage: %v", err)
   122  					return err
   123  				}
   124  			}
   125  		}
   126  	}
   127  }
   128  
   129  // shortTrace creates a shorter Trace Info message.
   130  // Same implementation as github/minio/mc/cmd/admin-trace.go
   131  func shortTrace(info *madmin.ServiceTraceInfo) shortTraceMsg {
   132  	t := info.Trace
   133  	s := shortTraceMsg{}
   134  
   135  	s.Time = t.Time.Format(time.RFC3339)
   136  	s.Path = t.Path
   137  	s.FuncName = t.FuncName
   138  	s.CallStats.Duration = t.Duration.String()
   139  	if info.Trace.HTTP != nil {
   140  		s.Query = t.HTTP.ReqInfo.RawQuery
   141  		s.StatusCode = t.HTTP.RespInfo.StatusCode
   142  		s.StatusMsg = http.StatusText(t.HTTP.RespInfo.StatusCode)
   143  		s.CallStats.Rx = t.HTTP.CallStats.InputBytes
   144  		s.CallStats.Tx = t.HTTP.CallStats.OutputBytes
   145  		s.CallStats.Ttfb = t.HTTP.CallStats.TimeToFirstByte.String()
   146  		if host, ok := t.HTTP.ReqInfo.Headers["Host"]; ok {
   147  			s.Host = strings.Join(host, "")
   148  		}
   149  		cSlice := strings.Split(t.HTTP.ReqInfo.Client, ":")
   150  		s.Client = cSlice[0]
   151  	}
   152  
   153  	return s
   154  }