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