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 }