blob: 3292446a1ff5e6fa95f433d8bb51b755d8bb8eca [file] [log] [blame]
// 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)
}