Skip to content

Commit

Permalink
Add mc admin trace command. (#2655)
Browse files Browse the repository at this point in the history
  • Loading branch information
poornas authored and kannappanr committed Jun 11, 2019
1 parent 5d0a9c0 commit ca26dac
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 141 deletions.
1 change: 1 addition & 0 deletions cmd/admin-main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var adminCmd = cli.Command{
adminProfileCmd,
adminTopCmd,
adminMonitorCmd,
adminTraceCmd,
},
}

Expand Down
299 changes: 299 additions & 0 deletions cmd/admin-trace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
/*
* MinIO Client (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cmd

import (
"bytes"
"fmt"
"hash/fnv"
"net/http"
"regexp"
"strconv"
"strings"
"time"

"github.com/fatih/color"
"github.com/minio/cli"
json "github.com/minio/mc/pkg/colorjson"
"github.com/minio/mc/pkg/console"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio/pkg/madmin"
)

var adminTraceFlags = []cli.Flag{
cli.BoolFlag{
Name: "verbose, v",
Usage: "print verbose trace",
},
cli.BoolFlag{
Name: "all, a",
Usage: "trace all traffic",
},
}

var adminTraceCmd = cli.Command{
Name: "trace",
Usage: "show http trace for minio server",
Action: mainAdminTrace,
Before: setGlobalsFromContext,
Flags: append(adminTraceFlags, globalFlags...),
HideHelpCommand: true,
CustomHelpTemplate: `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} [FLAGS] TARGET
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}
EXAMPLES:
1. Show console trace for a Minio server with alias 'play'
$ {{.HelpName}} play -v -a
`,
}
var funcNameRegex = regexp.MustCompile(`^.*?\\.([^\\-]*?)Handler\\-.*?$`)

const timeFormat = "15:04:05.000000000"

var colors = []color.Attribute{color.FgCyan, color.FgWhite, color.FgYellow, color.FgGreen}

func checkAdminTraceSyntax(ctx *cli.Context) {
if len(ctx.Args()) != 1 {
cli.ShowCommandHelpAndExit(ctx, "trace", 1) // last argument is exit code
}
}

// mainAdminTrace - the entry function of trace command
func mainAdminTrace(ctx *cli.Context) error {
// Check for command syntax
checkAdminTraceSyntax(ctx)
verbose := ctx.Bool("verbose")
all := ctx.Bool("all")
aliasedURL := ctx.Args().Get(0)
console.SetColor("Request", color.New(color.FgCyan))
console.SetColor("Method", color.New(color.Bold, color.FgWhite))
console.SetColor("Host", color.New(color.Bold, color.FgGreen))
console.SetColor("FuncName", color.New(color.Bold, color.FgGreen))

console.SetColor("ReqHeaderKey", color.New(color.Bold, color.FgWhite))
console.SetColor("RespHeaderKey", color.New(color.Bold, color.FgCyan))
console.SetColor("HeaderValue", color.New(color.FgWhite))
console.SetColor("ResponseStatus", color.New(color.Bold, color.FgYellow))

console.SetColor("Response", color.New(color.FgGreen))
console.SetColor("Body", color.New(color.FgYellow))
for _, c := range colors {
console.SetColor(fmt.Sprintf("Node%d", c), color.New(c))
}
// Create a new Minio Admin Client
client, err := newAdminClient(aliasedURL)
if err != nil {
fatalIf(err.Trace(aliasedURL), "Cannot initialize admin client.")
return nil
}
doneCh := make(chan struct{})
defer close(doneCh)

// Start listening on all trace activity.
traceCh := client.Trace(all, doneCh)
for traceInfo := range traceCh {
if traceInfo.Err != nil {
fatalIf(probe.NewError(traceInfo.Err), "Cannot listen to http trace")
}
if verbose {
printMsg(traceMessage{traceInfo})
continue
}
printMsg(shortTrace(traceInfo))
}
return nil
}

// Short trace record
type shortTraceMsg struct {
NodeName string
Time time.Time
FuncName string
Host string
Path string
Query string
StatusCode int
StatusMsg string
}

type traceMessage struct {
madmin.TraceInfo
}
type requestInfo struct {
Time time.Time `json:"time"`
Method string `json:"method"`
Path string `json:"path,omitempty"`
RawQuery string `json:"rawquery,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
}
type responseInfo struct {
Time time.Time `json:"time"`
Headers map[string]string `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
StatusCode int `json:"statuscode,omitempty"`
}
type trace struct {
NodeName string `json:"nodename"`
FuncName string `json:"funcname"`
RequestInfo requestInfo `json:"request"`
ResponseInfo responseInfo `json:"response"`
}

// parse Operation name from function name
func getOpName(fname string) (op string) {
res := funcNameRegex.FindStringSubmatch(fname)
op = fname
if len(res) == 2 {
op = res[1]
}
return
}

// return a struct with minimal trace info.
func shortTrace(ti madmin.TraceInfo) shortTraceMsg {
s := shortTraceMsg{}
t := ti.Trace
s.NodeName = t.NodeName
s.Time = t.ReqInfo.Time
if host, ok := t.ReqInfo.Headers["Host"]; ok {
s.Host = strings.Join(host, "")
}
s.Path = t.ReqInfo.Path
s.Query = t.ReqInfo.RawQuery
s.FuncName = getOpName(t.FuncName)
s.StatusCode = t.RespInfo.StatusCode
s.StatusMsg = http.StatusText(t.RespInfo.StatusCode)

return s
}
func (s shortTraceMsg) JSON() string {
traceJSONBytes, e := json.MarshalIndent(s, "", " ")
fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
return string(traceJSONBytes)
}
func (s shortTraceMsg) String() string {
var hostStr string
var b strings.Builder

if s.Host != "" {
hostStr = colorizedNodeName(s.Host)
}
fmt.Fprintf(&b, "%s %s ", s.Time.Format(timeFormat), console.Colorize("FuncName", s.FuncName))
fmt.Fprintf(&b, "%s%s", hostStr, s.Path)

if s.Query != "" {
fmt.Fprintf(&b, "?%s", s.Query)
}
fmt.Fprintf(&b, " %s", console.Colorize("ResponseStatus", fmt.Sprintf("\t%s %s", strconv.Itoa(s.StatusCode), s.StatusMsg)))
return b.String()
}

// colorize node name
func colorizedNodeName(nodeName string) string {
nodeHash := fnv.New32a()
nodeHash.Write([]byte(nodeName))
nHashSum := nodeHash.Sum32()
idx := int(nHashSum) % len(colors)
return console.Colorize(fmt.Sprintf("Node%d", colors[idx]), nodeName)
}

func (t traceMessage) JSON() string {
rqHdrs := make(map[string]string)
rspHdrs := make(map[string]string)
rq := t.Trace.ReqInfo
rs := t.Trace.RespInfo
for k, v := range rq.Headers {
rqHdrs[k] = strings.Join(v, " ")
}
for k, v := range rs.Headers {
rspHdrs[k] = strings.Join(v, " ")
}
trc := trace{
NodeName: t.Trace.NodeName,
FuncName: t.Trace.FuncName,
RequestInfo: requestInfo{
Time: rq.Time,
Method: rq.Method,
Path: rq.Path,
RawQuery: rq.RawQuery,
Body: string(rq.Body),
Headers: rqHdrs,
},
ResponseInfo: responseInfo{
Time: rs.Time,
Body: string(rs.Body),
Headers: rspHdrs,
StatusCode: rs.StatusCode,
},
}
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetIndent("", " ")
// Disable escaping special chars to display XML tags correctly
enc.SetEscapeHTML(false)
enc.Encode(trc)
// strip off extra newline added by json encoder
return strings.TrimSuffix(buf.String(), "\n")
}

func (t traceMessage) String() string {
var nodeNameStr string
var b strings.Builder

trc := t.Trace
if trc.NodeName != "" {
nodeNameStr = fmt.Sprintf("%s ", colorizedNodeName(trc.NodeName))
}

ri := trc.ReqInfo
rs := trc.RespInfo
fmt.Fprintf(&b, "%s%s", nodeNameStr, console.Colorize("Request", fmt.Sprintf("[REQUEST %s] ", trc.FuncName)))

fmt.Fprintf(&b, "[%s]\n", ri.Time.Format(timeFormat))
fmt.Fprintf(&b, "%s%s", nodeNameStr, console.Colorize("Method", fmt.Sprintf("%s %s", ri.Method, ri.Path)))
if ri.RawQuery != "" {
fmt.Fprintf(&b, "?%s", ri.RawQuery)
}
fmt.Fprint(&b, "\n")
host, ok := ri.Headers["Host"]
if ok {
delete(ri.Headers, "Host")
}
hostStr := strings.Join(host, "")
fmt.Fprintf(&b, "%s%s", nodeNameStr, console.Colorize("Host", fmt.Sprintf("Host: %s\n", hostStr)))
for k, v := range ri.Headers {
fmt.Fprintf(&b, "%s%s", nodeNameStr, console.Colorize("ReqHeaderKey", fmt.Sprintf("%s: ", k))+console.Colorize("HeaderValue", fmt.Sprintf("%s\n", strings.Join(v, ""))))
}

fmt.Fprintf(&b, "%s%s", nodeNameStr, console.Colorize("Body", fmt.Sprintf("%s\n", string(ri.Body))))
fmt.Fprintf(&b, "%s%s", nodeNameStr, console.Colorize("Response", fmt.Sprintf("[RESPONSE] ")))
fmt.Fprintf(&b, "[%s]\n", rs.Time.Format(timeFormat))
fmt.Fprintf(&b, "%s%s", nodeNameStr, console.Colorize("ResponseStatus", fmt.Sprintf("%d %s\n", rs.StatusCode, http.StatusText(rs.StatusCode))))
for k, v := range rs.Headers {
fmt.Fprintf(&b, "%s%s", nodeNameStr, console.Colorize("RespHeaderKey", fmt.Sprintf("%s: ", k))+console.Colorize("HeaderValue", fmt.Sprintf("%s\n", strings.Join(v, ""))))
}
fmt.Fprintf(&b, "%s%s", nodeNameStr, console.Colorize("Body", string(rs.Body)))
fmt.Fprint(&b, nodeNameStr)
return b.String()
}
2 changes: 2 additions & 0 deletions cmd/complete.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ var completeCmds = map[string]complete.Predictor{
"/admin/service/restart": aliasCompleter,
"/admin/service/stop": aliasCompleter,

"/admin/trace": aliasCompleter,

"/admin/profile/start": aliasCompleter,
"/admin/profile/stop": aliasCompleter,

Expand Down
38 changes: 38 additions & 0 deletions docs/minio-admin-complete-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,41 @@ COMMANDS:
```sh
mc admin top locks myminio
```

<a name="trace"></a>
### Command `trace` - Display Minio server http trace
`trace` command displays server http trace of one or many Minio servers (under distributed cluster)

```sh
NAME:
mc admin trace - get minio server http trace

FLAGS:
--help, -h show help
```

*Example: Display Minio server http trace.*

```sh
mc admin trace myminio
172.16.238.1 [REQUEST (objectAPIHandlers).ListBucketsHandler-fm] [154828542.525557] [2019-01-23 23:17:05 +0000]
172.16.238.1 GET /
172.16.238.1 Host: 172.16.238.3:9000
172.16.238.1 X-Amz-Date: 20190123T231705Z
172.16.238.1 Authorization: AWS4-HMAC-SHA256 Credential=minio/20190123/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=8385097f264efaf1b71a9b56514b8166bb0a03af8552f83e2658f877776c46b3
172.16.238.1 User-Agent: Minio (linux; amd64) minio-go/v6.0.8 mc/2019-01-23T23:15:38Z
172.16.238.1 X-Amz-Content-Sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
172.16.238.1
172.16.238.1 <BODY>
172.16.238.1 [RESPONSE] [154828542.525557] [2019-01-23 23:17:05 +0000]
172.16.238.1 200 OK
172.16.238.1 X-Amz-Request-Id: 157C9D641F42E547
172.16.238.1 X-Minio-Deployment-Id: 5f20fd91-6880-455f-a26d-07804b6821ca
172.16.238.1 X-Xss-Protection: 1; mode=block
172.16.238.1 Accept-Ranges: bytes
172.16.238.1 Content-Security-Policy: block-all-mixed-content
172.16.238.1 Content-Type: application/xml
172.16.238.1 Server: Minio/DEVELOPMENT.2019-01-23T23-14-14Z
172.16.238.1 Vary: Origin
...
```
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/mattn/go-isatty v0.0.7
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/minio/cli v1.20.0
github.com/minio/minio v0.0.0-20190601051138-1ce2d29bbbf1
github.com/minio/minio v0.0.0-20190611004433-002a205c9ce5
github.com/minio/minio-go v0.0.0-20190327203652-5325257a208f // indirect
github.com/minio/minio-go/v6 v6.0.28
github.com/minio/sha256-simd v0.1.0
Expand All @@ -36,14 +36,13 @@ require (
github.com/segmentio/go-prompt v1.2.1-0.20161017233205-f0d19b6901ad
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect
github.com/soheilhy/cmux v0.1.4 // indirect
github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
github.com/ugorji/go v1.1.5-pre // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.etcd.io/bbolt v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
golang.org/x/net v0.0.0-20190603091049-60506f45cf65
golang.org/x/text v0.3.2
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
Expand Down
Loading

0 comments on commit ca26dac

Please sign in to comment.