| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //go:build !build_with_native_toolchain |
| // +build !build_with_native_toolchain |
| |
| package pprof |
| |
| import ( |
| "bytes" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "runtime/pprof" |
| "sort" |
| "sync" |
| "syscall/zx" |
| "time" |
| |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/inspect" |
| "go.fuchsia.dev/fuchsia/src/lib/component" |
| syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go" |
| ) |
| |
| const pprofName = "pprof" |
| |
| func Setup(path string) (component.Node, func() error, error) { |
| mapDir, err := mapDirFromPath(path) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| return &component.DirectoryWrapper{ |
| Directory: mapDir, |
| }, func() error { |
| t := time.NewTicker(time.Minute) |
| defer t.Stop() |
| |
| for { |
| if err := func() error { |
| mapDir.mu.Lock() |
| defer mapDir.mu.Unlock() |
| // Prune all but the most recent profiles. The number retained is |
| // chosen arbitrarily. |
| if maxProfiles := 3; len(mapDir.mu.m) > maxProfiles { |
| filenames := make([]string, 0, len(mapDir.mu.m)) |
| for filename := range mapDir.mu.m { |
| filenames = append(filenames, filename) |
| } |
| sort.Strings(filenames) |
| for _, filename := range filenames[:len(filenames)-maxProfiles] { |
| if err := mapDir.mu.m[filename].File.(*sliceFile).Close(); err != nil { |
| return err |
| } |
| delete(mapDir.mu.m, filename) |
| if err := os.Remove(filepath.Join(path, filename)); err != nil { |
| _ = syslog.Warnf("failed to remove %s: %s", filename, err) |
| } |
| } |
| } |
| return nil |
| }(); err != nil { |
| return err |
| } |
| |
| filename := (<-t.C).UTC().Format(time.RFC3339) + ".inspect" |
| |
| b, err := getProfilesInspectVMOBytes() |
| if err != nil { |
| return err |
| } |
| mapDir.mu.Lock() |
| mapDir.mu.m[filename] = &component.FileWrapper{ |
| File: &sliceFile{ |
| b: b, |
| }, |
| } |
| mapDir.mu.Unlock() |
| |
| if err := ioutil.WriteFile(filepath.Join(path, filename), b, os.ModePerm); err != nil { |
| return err |
| } |
| } |
| }, nil |
| } |
| |
| var _ component.Directory = (*mapDirectory)(nil) |
| |
| type mapDirectory struct { |
| mu struct { |
| sync.Mutex |
| m map[string]*component.FileWrapper |
| } |
| } |
| |
| func mapDirFromPath(path string) (*mapDirectory, error) { |
| dir, err := os.Open(path) |
| if err != nil { |
| return nil, err |
| } |
| filenames, err := dir.Readdirnames(0) |
| if err != nil { |
| return nil, err |
| } |
| if err := dir.Close(); err != nil { |
| return nil, err |
| } |
| |
| m := make(map[string]*component.FileWrapper) |
| for _, filename := range filenames { |
| f, err := os.Open(filepath.Join(path, filename)) |
| if err != nil { |
| return nil, err |
| } |
| b, err := ioutil.ReadAll(f) |
| if err != nil { |
| return nil, err |
| } |
| if err := f.Close(); err != nil { |
| return nil, err |
| } |
| m[filename] = &component.FileWrapper{ |
| File: &sliceFile{ |
| b: b, |
| }, |
| } |
| } |
| var d mapDirectory |
| d.mu.m = m |
| return &d, nil |
| } |
| |
| const nowName = "now.inspect" |
| |
| var nowFile = component.FileWrapper{File: &pprofFile{}} |
| |
| func (md *mapDirectory) Get(nodeName string) (component.Node, bool) { |
| if nodeName == nowName { |
| return &nowFile, true |
| } |
| md.mu.Lock() |
| value, ok := md.mu.m[nodeName] |
| md.mu.Unlock() |
| return value, ok |
| } |
| |
| func (md *mapDirectory) ForEach(fn func(string, component.Node)) { |
| fn(nowName, &nowFile) |
| md.mu.Lock() |
| for nodeName, node := range md.mu.m { |
| fn(nodeName, node) |
| } |
| md.mu.Unlock() |
| } |
| |
| var _ component.File = (*sliceFile)(nil) |
| |
| type sliceFile struct { |
| b []byte |
| vmo zx.VMO |
| } |
| |
| func (sf *sliceFile) GetReader() (component.Reader, uint64) { |
| r := bytes.NewReader(sf.b) |
| return r, uint64(r.Len()) |
| } |
| |
| func (sf *sliceFile) GetVMO() zx.VMO { |
| if !sf.vmo.Handle().IsValid() { |
| vmo, err := zx.NewVMO(uint64(len(sf.b)), 0) |
| if err != nil { |
| return zx.VMO(zx.HandleInvalid) |
| } |
| if err := vmo.Write(sf.b, 0); err != nil { |
| return zx.VMO(zx.HandleInvalid) |
| } |
| sf.vmo = vmo |
| } |
| return sf.vmo |
| } |
| |
| func (sf *sliceFile) Close() error { |
| if sf.vmo.Handle().IsValid() { |
| return sf.vmo.Close() |
| } |
| return nil |
| } |
| |
| var _ component.File = (*pprofFile)(nil) |
| |
| type pprofFile struct{} |
| |
| func getProfilesInspectVMOBytes() ([]byte, error) { |
| var b bytes.Buffer |
| w, err := inspect.NewWriter(&b) |
| if err != nil { |
| return nil, err |
| } |
| nodeValueIndex, err := w.WriteNodeValueBlock(0, pprofName) |
| if err != nil { |
| return nil, err |
| } |
| for _, p := range pprof.Profiles() { |
| var b bytes.Buffer |
| if err := p.WriteTo(&b, 0); err != nil { |
| return nil, err |
| } |
| if err := w.WriteBinary(nodeValueIndex, p.Name(), uint32(b.Len()), &b); err != nil { |
| return nil, err |
| } |
| } |
| return b.Bytes(), nil |
| } |
| |
| func (p *pprofFile) GetReader() (component.Reader, uint64) { |
| b, err := getProfilesInspectVMOBytes() |
| if err != nil { |
| panic(err) |
| } |
| return bytes.NewReader(b), uint64(len(b)) |
| } |
| |
| func (*pprofFile) GetVMO() zx.VMO { |
| return zx.VMO(zx.HandleInvalid) |
| } |