github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/debug/info.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package debug exports debug information for gopls.
     6  package debug
     7  
     8  import (
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"reflect"
    14  	"runtime"
    15  	"runtime/debug"
    16  	"sort"
    17  	"strings"
    18  
    19  	"github.com/powerman/golang-tools/internal/lsp/source"
    20  )
    21  
    22  type PrintMode int
    23  
    24  const (
    25  	PlainText = PrintMode(iota)
    26  	Markdown
    27  	HTML
    28  	JSON
    29  )
    30  
    31  // Version is a manually-updated mechanism for tracking versions.
    32  const Version = "master"
    33  
    34  // ServerVersion is the format used by gopls to report its version to the
    35  // client. This format is structured so that the client can parse it easily.
    36  type ServerVersion struct {
    37  	*BuildInfo
    38  	Version string
    39  }
    40  
    41  type Module struct {
    42  	ModuleVersion
    43  	Replace *ModuleVersion `json:"replace,omitempty"`
    44  }
    45  
    46  type ModuleVersion struct {
    47  	Path    string `json:"path,omitempty"`
    48  	Version string `json:"version,omitempty"`
    49  	Sum     string `json:"sum,omitempty"`
    50  }
    51  
    52  // VersionInfo returns the build info for the gopls process. If it was not
    53  // built in module mode, we return a GOPATH-specific message with the
    54  // hardcoded version.
    55  func VersionInfo() *ServerVersion {
    56  	if info, ok := readBuildInfo(); ok {
    57  		return getVersion(info)
    58  	}
    59  	buildInfo := &BuildInfo{}
    60  	// go1.17 or earlier, part of s.BuildInfo are embedded fields.
    61  	buildInfo.Path = "gopls, built in GOPATH mode"
    62  	buildInfo.GoVersion = runtime.Version()
    63  	return &ServerVersion{
    64  		Version:   Version,
    65  		BuildInfo: buildInfo,
    66  	}
    67  }
    68  
    69  func getVersion(info *BuildInfo) *ServerVersion {
    70  	return &ServerVersion{
    71  		Version:   Version,
    72  		BuildInfo: info,
    73  	}
    74  }
    75  
    76  // PrintServerInfo writes HTML debug info to w for the Instance.
    77  func (i *Instance) PrintServerInfo(ctx context.Context, w io.Writer) {
    78  	section(w, HTML, "Server Instance", func() {
    79  		fmt.Fprintf(w, "Start time: %v\n", i.StartTime)
    80  		fmt.Fprintf(w, "LogFile: %s\n", i.Logfile)
    81  		fmt.Fprintf(w, "Working directory: %s\n", i.Workdir)
    82  		fmt.Fprintf(w, "Address: %s\n", i.ServerAddress)
    83  		fmt.Fprintf(w, "Debug address: %s\n", i.DebugAddress())
    84  	})
    85  	PrintVersionInfo(ctx, w, true, HTML)
    86  	section(w, HTML, "Command Line", func() {
    87  		fmt.Fprintf(w, "<a href=/debug/pprof/cmdline>cmdline</a>")
    88  	})
    89  }
    90  
    91  // PrintVersionInfo writes version information to w, using the output format
    92  // specified by mode. verbose controls whether additional information is
    93  // written, including section headers.
    94  func PrintVersionInfo(_ context.Context, w io.Writer, verbose bool, mode PrintMode) error {
    95  	info := VersionInfo()
    96  	if mode == JSON {
    97  		return printVersionInfoJSON(w, info)
    98  	}
    99  
   100  	if !verbose {
   101  		printBuildInfo(w, info, false, mode)
   102  		return nil
   103  	}
   104  	section(w, mode, "Build info", func() {
   105  		printBuildInfo(w, info, true, mode)
   106  	})
   107  	return nil
   108  }
   109  
   110  func printVersionInfoJSON(w io.Writer, info *ServerVersion) error {
   111  	js, err := json.MarshalIndent(info, "", "\t")
   112  	if err != nil {
   113  		return err
   114  	}
   115  	_, err = fmt.Fprint(w, string(js))
   116  	return err
   117  }
   118  
   119  func section(w io.Writer, mode PrintMode, title string, body func()) {
   120  	switch mode {
   121  	case PlainText:
   122  		fmt.Fprintln(w, title)
   123  		fmt.Fprintln(w, strings.Repeat("-", len(title)))
   124  		body()
   125  	case Markdown:
   126  		fmt.Fprintf(w, "#### %s\n\n```\n", title)
   127  		body()
   128  		fmt.Fprintf(w, "```\n")
   129  	case HTML:
   130  		fmt.Fprintf(w, "<h3>%s</h3>\n<pre>\n", title)
   131  		body()
   132  		fmt.Fprint(w, "</pre>\n")
   133  	}
   134  }
   135  
   136  func printBuildInfo(w io.Writer, info *ServerVersion, verbose bool, mode PrintMode) {
   137  	fmt.Fprintf(w, "%v %v\n", info.Path, Version)
   138  	printModuleInfo(w, info.Main, mode)
   139  	if !verbose {
   140  		return
   141  	}
   142  	for _, dep := range info.Deps {
   143  		printModuleInfo(w, *dep, mode)
   144  	}
   145  	fmt.Fprintf(w, "go: %v\n", info.GoVersion)
   146  }
   147  
   148  func printModuleInfo(w io.Writer, m debug.Module, _ PrintMode) {
   149  	fmt.Fprintf(w, "    %s@%s", m.Path, m.Version)
   150  	if m.Sum != "" {
   151  		fmt.Fprintf(w, " %s", m.Sum)
   152  	}
   153  	if m.Replace != nil {
   154  		fmt.Fprintf(w, " => %v", m.Replace.Path)
   155  	}
   156  	fmt.Fprintf(w, "\n")
   157  }
   158  
   159  type field struct {
   160  	index []int
   161  }
   162  
   163  var fields []field
   164  
   165  // find all the options. The presumption is that the Options are nested structs
   166  // and that pointers don't need to be dereferenced
   167  func swalk(t reflect.Type, ix []int, indent string) {
   168  	switch t.Kind() {
   169  	case reflect.Struct:
   170  		for i := 0; i < t.NumField(); i++ {
   171  			fld := t.Field(i)
   172  			ixx := append(append([]int{}, ix...), i)
   173  			swalk(fld.Type, ixx, indent+". ")
   174  		}
   175  	default:
   176  		// everything is either a struct or a field (that's an assumption about Options)
   177  		fields = append(fields, field{ix})
   178  	}
   179  }
   180  
   181  type sessionOption struct {
   182  	Name    string
   183  	Type    string
   184  	Current string
   185  	Default string
   186  }
   187  
   188  func showOptions(o *source.Options) []sessionOption {
   189  	var out []sessionOption
   190  	t := reflect.TypeOf(*o)
   191  	swalk(t, []int{}, "")
   192  	v := reflect.ValueOf(*o)
   193  	do := reflect.ValueOf(*source.DefaultOptions())
   194  	for _, f := range fields {
   195  		val := v.FieldByIndex(f.index)
   196  		def := do.FieldByIndex(f.index)
   197  		tx := t.FieldByIndex(f.index)
   198  		is := strVal(val)
   199  		was := strVal(def)
   200  		out = append(out, sessionOption{
   201  			Name:    tx.Name,
   202  			Type:    tx.Type.String(),
   203  			Current: is,
   204  			Default: was,
   205  		})
   206  	}
   207  	sort.Slice(out, func(i, j int) bool {
   208  		rd := out[i].Current == out[i].Default
   209  		ld := out[j].Current == out[j].Default
   210  		if rd != ld {
   211  			return ld
   212  		}
   213  		return out[i].Name < out[j].Name
   214  	})
   215  	return out
   216  }
   217  
   218  func strVal(val reflect.Value) string {
   219  	switch val.Kind() {
   220  	case reflect.Bool:
   221  		return fmt.Sprintf("%v", val.Interface())
   222  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   223  		return fmt.Sprintf("%v", val.Interface())
   224  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   225  		return fmt.Sprintf("%v", val.Interface())
   226  	case reflect.Uintptr, reflect.UnsafePointer:
   227  		return fmt.Sprintf("0x%x", val.Pointer())
   228  	case reflect.Complex64, reflect.Complex128:
   229  		return fmt.Sprintf("%v", val.Complex())
   230  	case reflect.Array, reflect.Slice:
   231  		ans := []string{}
   232  		for i := 0; i < val.Len(); i++ {
   233  			ans = append(ans, strVal(val.Index(i)))
   234  		}
   235  		sort.Strings(ans)
   236  		return fmt.Sprintf("%v", ans)
   237  	case reflect.Chan, reflect.Func, reflect.Ptr:
   238  		return val.Kind().String()
   239  	case reflect.Struct:
   240  		var x source.Analyzer
   241  		if val.Type() != reflect.TypeOf(x) {
   242  			return val.Kind().String()
   243  		}
   244  		// this is sort of ugly, but usable
   245  		str := val.FieldByName("Analyzer").Elem().FieldByName("Doc").String()
   246  		ix := strings.Index(str, "\n")
   247  		if ix == -1 {
   248  			ix = len(str)
   249  		}
   250  		return str[:ix]
   251  	case reflect.String:
   252  		return fmt.Sprintf("%q", val.Interface())
   253  	case reflect.Map:
   254  		ans := []string{}
   255  		iter := val.MapRange()
   256  		for iter.Next() {
   257  			k := iter.Key()
   258  			v := iter.Value()
   259  			ans = append(ans, fmt.Sprintf("%s:%s, ", strVal(k), strVal(v)))
   260  		}
   261  		sort.Strings(ans)
   262  		return fmt.Sprintf("%v", ans)
   263  	}
   264  	return fmt.Sprintf("??%s??", val.Type())
   265  }