当前位置:网站首页>Go language core 36 lectures (go language practice and application 27) -- learning notes

Go language core 36 lectures (go language practice and application 27) -- learning notes

2022-06-23 21:33:00 Zhengziming

49 | Fundamentals of program performance analysis ( Next )

In the last article , We're all around “ How to make the program right CPU Sample profiles ” This question is discussed , today , Let's take a look at its expansion .

Knowledge expansion

problem 1: How to set the sampling frequency of memory profile ?

The sampling of memory profile information will be collected according to a certain proportion Go Heap memory usage during program operation . The method of setting the sampling frequency of memory profile is very simple , Just for runtime.MemProfileRate Variable assignment .

The meaning of this variable is , How many bytes are allocated on average , Take a sample of the usage of heap memory . If you set the value of this variable to 0, that ,Go When the language is running, the system will completely stop sampling the memory profile . The default value of this variable is 512 KB, That is to say 512 kilobytes .

Be careful , If you want to set this sampling frequency , Then the sooner you set it, the better , And should only be set once , Otherwise, you may be right Go The sampling work of the language runtime system , Cause adverse effects . such as , Only in main Set once at the beginning of the function .

After this , When we want to get the memory profile , You also need to call runtime/pprof In bag WriteHeapProfile function . This function will the collected memory summary information , Write to the writer we specify .

Be careful , We go through WriteHeapProfile The memory profile obtained by the function is not real-time , It's a snapshot , It is generated when the last memory garbage collection is completed . If you want real-time information , Then you can call runtime.ReadMemStats function . But pay special attention to , This function causes Go Brief pause of language scheduler .

above , This is a brief answer to the question of setting the sampling frequency of memory profile information .

package main

import (
	"048 Fundamentals of program performance analysis /common"
	"048 Fundamentals of program performance analysis /common/op"
	"errors"
	"fmt"
	"os"
	"runtime"
	"runtime/pprof"
)

var (
	profileName    = "memprofile.out"
	memProfileRate = 8
)

func main() {
	f, err := common.CreateFile("", profileName)
	if err != nil {
		fmt.Printf("memory profile creation error: %v\n", err)
		return
	}
	defer f.Close()
	startMemProfile()
	if err = common.Execute(op.MemProfile, 10); err != nil {
		fmt.Printf("execute error: %v\n", err)
		return
	}
	if err := stopMemProfile(f); err != nil {
		fmt.Printf("memory profile stop error: %v\n", err)
		return
	}
}

func startMemProfile() {
	runtime.MemProfileRate = memProfileRate
}

func stopMemProfile(f *os.File) error {
	if f == nil {
		return errors.New("nil file")
	}
	return pprof.WriteHeapProfile(f)
}

problem 2: How to get the blocking profile ?

We call runtime In bag SetBlockProfileRate function , You can set the sampling frequency of blocking profile . This function has a name rate Parameters of , It is int Type of .

The meaning of this parameter is , Just find out how many nanoseconds a blocking event lasts , You can sample it . If the value of this parameter is less than or equal to 0, So it means Go When the language is running, the system will completely stop sampling the blocking profile .

stay runtime In bag , There's another one called blockprofilerate Package level private variables , It is uint64 Type of . The meaning of this variable is , How many times does the duration of a blocking event span CPU Clock cycle , You can sample it . Its meaning is the same as that we just mentioned rate The meaning of parameters is very similar , isn't it? ?

actually , The only difference between the two is that the units are different .runtime.SetBlockProfileRate The function will set the parameters first rate Perform unit conversion and necessary type conversion for the value of , then , It assigns the conversion result to the user by atomic operation blockprofilerate Variable . Because the default value of this variable is 0, therefore Go By default, the language runtime system does not record any blocking events in the program .

On the other hand , When we need to get the blocking profile , You need to call runtime/pprof In bag Lookup Function and pass in the parameter value "block", To get a *runtime/pprof.Profile Type value ( hereinafter referred to as Profile value ). After this , We also need to call this Profile It's worth it WriteTo Method , To drive it to write the profile into the writer we specify .

This WriteTo Method has two parameters , One parameter is the writer we just mentioned , It is io.Writer Type of . The other parameter represents the level of detail of the profile int Type parameter debug.

debug There are two main optional values of the parameter , namely :0 and 1. When debug The value of is 0 when , adopt WriteTo The profile of the method written into the writer will only contain go tool pprof Memory address required by the tool , These memory addresses will be displayed in hexadecimal form .

When the value is 1 when , Corresponding package name 、 Function name 、 Source file path 、 The code line number and other information will be added as comments . in addition ,debug by 0 Summary information when , Through protocol buffers Convert to byte stream . And in the debug by 1 When ,WriteTo The summary information output by the method is ordinary text that we can understand .

besides ,debug The value of can also be 2. At this time , The output profile will also be plain text , And usually contain more details . As for what these details contain , It depends on us calling runtime/pprof.Lookup What parameter values are passed in when the function is used . below , Let's take a look at this function .

package main

import (
	"048 Fundamentals of program performance analysis /common"
	"048 Fundamentals of program performance analysis /common/op"
	"errors"
	"fmt"
	"os"
	"runtime"
	"runtime/pprof"
)

var (
	profileName      = "blockprofile.out"
	blockProfileRate = 2
	debug            = 0
)

func main() {
	f, err := common.CreateFile("", profileName)
	if err != nil {
		fmt.Printf("block profile creation error: %v\n", err)
		return
	}
	defer f.Close()
	startBlockProfile()
	if err = common.Execute(op.BlockProfile, 10); err != nil {
		fmt.Printf("execute error: %v\n", err)
		return
	}
	if err := stopBlockProfile(f); err != nil {
		fmt.Printf("block profile stop error: %v\n", err)
		return
	}
}

func startBlockProfile() {
	runtime.SetBlockProfileRate(blockProfileRate)
}

func stopBlockProfile(f *os.File) error {
	if f == nil {
		return errors.New("nil file")
	}
	return pprof.Lookup("block").WriteTo(f, debug)
}

problem 3:runtime/pprof.Lookup What is the correct way to call a function ?

runtime/pprof.Lookup function ( hereinafter referred to as Lookup function ) The function is , Provide the profile information corresponding to the given name . This profile will be provided by a Profile Values represent . If the function returns a nil, Then it means that there is no profile corresponding to the given name .

runtime/pprof The package has been predefined for us 6 A profile name . Their corresponding summary information collection methods and output methods are also ready . We can use it directly . They are :goroutine、heap、allocs、threadcreate、block and mutex.

When we put "goroutine" Pass in Lookup Function , The function will use the corresponding method , Collect all currently in use goroutine Stack trace information for . Be careful , Such a collection will cause Go Brief pause of language scheduler .

When this function is called, it returns Profile It's worth it WriteTo When the method is used , If parameters debug The value of is greater than or equal to 2, Then the method will output all goroutine Stack trace information for . This information can be very much .

If they take up more space than 64 MB( That is to say 64 Megabytes ), Then the corresponding method will cut off the excess . If Lookup The parameter value received by the function is "heap", Then it will collect sampling information related to the allocation and release of heap memory . This is actually the memory profile we discussed earlier .

In our incoming "allocs" When , The subsequent operation will be very similar . In both cases ,Lookup Function return Profile Values will also be very similar . It's just , In both cases Profile It's worth it WriteTo Method is called , The summary information they output will be slightly different , And this is only reflected in the parameters debug be equal to 0 When .

"heap" This will make the output memory profile default to “ In use space ”(inuse_space) From the perspective of , and "allocs" The corresponding default perspective is “ Allocated space ”(alloc_space).

“ In use space ” Refer to , Memory space that has been allocated but has not been freed . From this perspective ,go tool pprof The tool does not ignore the part of information related to the released space . And in the “ Allocated space ” From the perspective of , All memory allocation information will be displayed , Whether or not these memory spaces have been freed at the time of sampling .

Besides , Whether it's "heap" still "allocs", After we call Profile It's worth it WriteTo Method time , Just give debug The value of the parameter is greater than 0, Then the specification of the output content of this method will be the same .

Parameter values "threadcreate" Can make Lookup Function to collect some stack trace information . Each of these stack traces depicts a code call chain , The code on these call chains leads to new operating system threads . In this way Profile There are only two output specifications of value , It's up to us to pass it on WriteTo Whether the parameter value of the method is greater than 0.

Besides, "block" and "mutex"."block" It stands for , Stack trace information of those codes blocked due to contention for synchronization primitives . Do you remember? ? This is the blocking profile we talked about earlier .

Corresponding to it ,"mutex" It stands for , The code that used to be the holder of synchronization primitives , Their stack trace information . There are only two output specifications , Depending on debug Is it greater than 0.

The synchronization primitives mentioned here , It means to exist in Go Language runtime is an underlying synchronization tool in the system , Or a synchronization mechanism .

It is directly oriented to the memory address , Asynchronous semaphores and atomic operations are used as the means of implementation . The channel we are already familiar with 、 The mutex 、 Condition variables, 、”WaitGroup“, as well as Go The language runtime system itself , Will use it to realize their own functions .

Okay , On this question , We've talked a lot . I Believe , You have to Lookup We have a deep understanding of the calling mode of functions and the meaning behind them .demo99.go The file contains some sample code , For your reference .

package main

import (
	"048 Fundamentals of program performance analysis /common"
	"048 Fundamentals of program performance analysis /common/op"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"runtime/pprof"
	"time"
)

// profileNames  Represents a list of profile names .
var profileNames = []string{
	"goroutine",
	"heap",
	"allocs",
	"threadcreate",
	"block",
	"mutex",
}

// profileOps  A dictionary representing load functions prepared to generate different profiles .
var profileOps = map[string]common.OpFunc{
	"goroutine":    op.BlockProfile,
	"heap":         op.MemProfile,
	"allocs":       op.MemProfile,
	"threadcreate": op.BlockProfile,
	"block":        op.BlockProfile,
	"mutex":        op.BlockProfile,
}

// debugOpts  representative debug A list of optional values for the parameter .
var debugOpts = []int{
	0,
	1,
	2,
}

func main() {
	prepare()
	dir, err := createDir()
	if err != nil {
		fmt.Printf("dir creation error: %v\n", err)
		return
	}
	for _, name := range profileNames {
		for _, debug := range debugOpts {
			err = genProfile(dir, name, debug)
			if err != nil {
				return
			}
			time.Sleep(time.Millisecond)
		}
	}
}

func genProfile(dir string, name string, debug int) error {
	fmt.Printf("Generate %s profile (debug: %d) ...\n", name, debug)
	fileName := fmt.Sprintf("%s_%d.out", name, debug)
	f, err := common.CreateFile(dir, fileName)
	if err != nil {
		fmt.Printf("create error: %v (%s)\n", err, fileName)
		return err
	}
	defer f.Close()
	if err = common.Execute(profileOps[name], 10); err != nil {
		fmt.Printf("execute error: %v (%s)\n", err, fileName)
		return err
	}
	profile := pprof.Lookup(name)
	err = profile.WriteTo(f, debug)
	if err != nil {
		fmt.Printf("write error: %v (%s)\n", err, fileName)
		return err
	}
	return nil
}

func createDir() (string, error) {
	currDir, err := os.Getwd()
	if err != nil {
		return "", err
	}
	path := filepath.Join(currDir, "profiles")
	err = os.Mkdir(path, 0766)
	if err != nil && !os.IsExist(err) {
		return "", err
	}
	return path, nil
}

func prepare() {
	runtime.MemProfileRate = 8
	runtime.SetBlockProfileRate(2)
}

problem 4: How to HTTP Add a performance analysis interface to the network service of the protocol ?

This question is still very simple . This is because in general, we only need to import net/http/pprof Just code package , Just like this. :

import _ "net/http/pprof"

then , Start the network service and start listening , such as :

log.Println(http.ListenAndServe("localhost:8082", nil))

After running this program , We can access... In a web browser http://localhost:8082/debug/pprof This address sees a simple web page . If you look at the last question carefully , Then you can quickly understand the meaning of each part of this web page .

stay /debug/pprof/ This URL There are many sub paths available under the path , You can learn this by clicking the link in the web page . image allocs、block、goroutine、heap、mutex、threadcreate this 6 Subpath , At the bottom, it's all through Lookup Function . About this function , You should already be familiar with .

These sub paths can accept query parameters debug. It is used to control the format and level of detail of the profile . As for its optional value , I won't go back to . Its default value is 0. in addition , There's another one called gc The query parameters of . It is used to control whether to force a garbage collection before getting the profile information . As long as its value is greater than 0, The program will do this . however , This parameter is only in /debug/pprof/heap Valid under path .

once /debug/pprof/profile The path is accessed , The program will execute on CPU Sampling of profile information . It accepts a name called seconds The query parameters of . The meaning of this parameter is , How many seconds does the sampling work take . If this parameter is not explicitly specified , Then the sampling will continue 30 second . Be careful , In this way , The program will only respond to protocol buffers Converted byte stream . We can go through go tool pprof The tool directly reads such HTTP Respond to , for example :

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=60

besides , There is another path worthy of our attention , namely :/debug/pprof/trace. In this way , The program will mainly use runtime/trace In the code package API To deal with our requests .

More specifically , The program will call trace.Start function , Query parameters in seconds Call... After the specified duration trace.Stop function . there seconds The default value of is 1 second . as for runtime/trace The function of the code package , I'll leave it to you to check and explore .

What I said earlier URL Paths are fixed . This is the default access rule . We can also customize them , Just like this. :

mux := http.NewServeMux()
pathPrefix := "/d/pprof/"
mux.HandleFunc(pathPrefix,
  func(w http.ResponseWriter, r *http.Request) {
    name := strings.TrimPrefix(r.URL.Path, pathPrefix)
    if name != "" {
      pprof.Handler(name).ServeHTTP(w, r)
      return
    }
    pprof.Index(w, r)
  })
mux.HandleFunc(pathPrefix+"cmdline", pprof.Cmdline)
mux.HandleFunc(pathPrefix+"profile", pprof.Profile)
mux.HandleFunc(pathPrefix+"symbol", pprof.Symbol)
mux.HandleFunc(pathPrefix+"trace", pprof.Trace)

server := http.Server{
  Addr:    "localhost:8083",
  Handler: mux,
}

You can see , We almost only used net/http/pprof Several program entities in the code package , This customization is completed . This is especially useful when we use a third-party Web service development framework .

We customize HTTP Request multiplexer mux The included access rules are similar to the default rules , It's just URL The prefix of the path is shorter .

We customize mux With the process of net/http/pprof In bag init The function does something similar . This init The existence of function , In fact, we just import "net/http/pprof" The reason why the code package can access the relevant path .

When we write the network service program , Use net/http/pprof Packages are better than using them directly runtime/pprof The bag is convenient and practical . Through rational use , This code package can provide strong support for the monitoring of network services . Knowledge about this package , I'll introduce it here first .

package main

import (
	"log"
	"net/http"
	_ "net/http/pprof"
)

func main() {
	log.Println(http.ListenAndServe("localhost:8082", nil))
}
package main

import (
	"log"
	"net/http"
	"net/http/pprof"
	"strings"
)

func main() {
	mux := http.NewServeMux()
	pathPrefix := "/d/pprof/"
	mux.HandleFunc(pathPrefix,
		func(w http.ResponseWriter, r *http.Request) {
			name := strings.TrimPrefix(r.URL.Path, pathPrefix)
			if name != "" {
				pprof.Handler(name).ServeHTTP(w, r)
				return
			}
			pprof.Index(w, r)
		})
	mux.HandleFunc(pathPrefix+"cmdline", pprof.Cmdline)
	mux.HandleFunc(pathPrefix+"profile", pprof.Profile)
	mux.HandleFunc(pathPrefix+"symbol", pprof.Symbol)
	mux.HandleFunc(pathPrefix+"trace", pprof.Trace)

	server := http.Server{
		Addr:    "localhost:8083",
		Handler: mux,
	}

	if err := server.ListenAndServe(); err != nil {
		if err == http.ErrServerClosed {
			log.Println("HTTP server closed.")
		} else {
			log.Printf("HTTP server error: %v\n", err)
		}
	}
}

summary

In these two articles , We mainly talked about Go Program performance analysis , Many of the contents mentioned are your necessary knowledge and skills . These will help you really understand to sample 、 collect 、 A series of operation steps represented by output .

I mentioned several issues related to summary information . What you need to remember is , What does each profile represent , What kind of content do they contain .

You also need to know the right way to get them , Including how to start and stop sampling 、 How to set the sampling frequency , And how to control the format and detail of the output content .

Besides ,runtime/pprof In bag Lookup The correct way to call functions is also important . With the exception of CPU Profiles other than profiles , We can all get... By calling this function .

besides , I also mentioned an upper application , namely : Based on HTTP Protocol network services , Add performance analysis interface . This is also a very practical part .

although net/http/pprof The package does not provide many program entities , But it can let us use different ways , Implement the embedding of performance analysis interface . Some of these methods are minimalist 、 Out of the box , While others are used to meet various customization needs .

All of these , That's what I told you today Go language knowledge , They are the basis of program performance analysis . If you put Go Language programs are used in production environments , Then it will certainly involve them . For all the contents and questions mentioned here , I hope you can seriously think and understand . Only in this way can you use them easily when you really use them .

Thinking questions

The thinking question I left you today has actually been revealed earlier , That's it :runtime/trace What is the function of the code package ?

Note source code

https://github.com/MingsonZheng/go-core-demo

原网站

版权声明
本文为[Zhengziming]所创,转载请带上原文链接,感谢
https://yzsam.com/2021/12/202112230846357481.html