万字长文讲解Golang pprof 的使用

往期好文推荐 ⭐️⭐️⭐️:

# golang pprof 监控系列(1) —— go trace 统计原理与使用

# golang pprof监控系列(2) —— memory,block,mutex 使用

# golang pprof 监控系列(3) —— memory,block,mutex 统计原理

# golang pprof 监控系列(4) —— goroutine thread 统计原理

# golang pprof 监控系列(5) —— cpu 占用率 统计原理

大家好,我是蓝胖子,说到golang的性能分析,不得不提的就是其自身拥有的pprof,它提供了从cpu,内存,阻塞情况,协程,线程,乃至程序运行轨迹trace的分析,可以说相当强大了。

今天我将会用较长的篇幅阐述 应该如何使用pprof工具来 对cpu,内存,阻塞情况,协程,线程 这几个维度去进行分析。

其实总结出来Golang pprof的使用方式,可以用下面的思维导图来表示,

Pasted image 20240307144806.png
要么在程序中通过http接口的方式暴露相应的pprof的采集控制界面,要么就是在程序中通过代码开启对应指标的采集样本功能,采集一段时间的样本后生成二进制文件,最后通过 go tool pprof 命令去对样本的数据进行分析。

分析的方式也有两种,

  • 通过命令行开启一个交互终端
  • 通过浏览器将二进制文件的信息展示出来

我们可以通过下述的代码通过http接口的方式暴露相应的pprof的采集控制界面,

import (  
    "log"  
    "net/http"    
    "net/http/pprof"    
    "time")  
  
func main() {  
    mux := http.NewServeMux()  
    mux.HandleFunc("/debug/pprof/", pprof.Index)  
    mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)  
    mux.HandleFunc("/debug/pprof/profile", pprof.Profile)  
    mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)  
    mux.HandleFunc("/debug/pprof/trace", pprof.Trace)  
    go func() { log.Fatal(http.ListenAndServe(":8080", mux)) }()  
    time.Sleep(time.Hour)  
}

接着访问下http://localhost:8080/debug/pprof/ 便会出现各种维度指标,可以点击进去查看具体的指标信息。如下所示,

Pasted image 20240307150346.png

接下来我们来分析下各个指标在 http接口或者生成的二进制文件 输出的各种信息究竟代表什么含义

在下面的分析中我会分别的从http接口暴露指标的方式和二进制文件的方式分别介绍如何看懂各种指标的输出信息。

cpu

cpu在http接口暴露的分析界面中有个profile的链接,点击后,应用程序会采集30s的cpu运行信息后然后生成一个二进制文件,所以本质上对cpu的分析都是通过一个二进制文件进行的。

除了上述提到http接口暴露出cpu指标外,我们还可以通过下面的代码,在程序中显示的开启cpu指标的采集。在这个例子中,我通过busyCpu 方法不断的进行加法运算来模拟频繁的cpu操作。

import (  
    "github.com/pkg/profile"  
    "log"  
    "os"    
    "os/signal"    
    "syscall")  
func main() {  
    p := profile.Start(profile.CPUProfile,  
       profile.ProfilePath("profile"),  
       profile.NoShutdownHook,  
    )  
    defer p.Stop()  
    go busyCpu()  
    c := make(chan os.Signal, 1)  
    signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)  
    defer signal.Stop(c)  
    <-c  
}  
  
func busyCpu() {  
    i := uint(1000000)  
    for {  
       log.Println("sum number", i, Add(i, i+1))  
       i++  
    }  
}  
  
func Add(a, b uint) uint {  
    return a + b  
}

启动终端分析

在得到二进制文件后,我们可以使用如下命令来启动一个交互终端来分析cpu的使用情况,

go tool  pprof cpuprofile/cpu.pprof

输入top命令得到如下结果

(base) ➜  cpu go tool  pprof cpuprofile/cpu.pprof
Type: cpu
Time: Mar 4, 2024 at 3:14pm (CST)
Duration: 4.35s, Total samples = 200ms ( 4.60%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 200ms, 100% of 200ms total
Showing top 10 nodes out of 16
      flat  flat%   sum%        cum   cum%
     190ms 95.00% 95.00%      190ms 95.00%  syscall.syscall
      10ms  5.00%   100%       10ms  5.00%  runtime.pthread_cond_signal
         0     0%   100%      190ms 95.00%  internal/poll.(*FD).Write
         0     0%   100%      190ms 95.00%  internal/poll.ignoringEINTRIO (inline)
         0     0%   100%      190ms 95.00%  log.(*Logger).output
         0     0%   100%      190ms 95.00%  log.Println (inline)
         0     0%   100%      190ms 95.00%  main.busyCpu
         0     0%   100%      190ms 95.00%  os.(*File).Write
         0     0%   100%      190ms 95.00%  os.(*File).write (inline)
         0     0%   100%       10ms  5.00%  runtime.exitsyscallfast.func1

每个列的含义如下:

flat:函数自身的运行耗时(排除了子函数的调用耗时)

flat%:flat运行耗时占用总的采集样本的时间和的比例,这里所有节点运行的flat时间和为200ms。

sum%:命令行返回结果中函数自身和其之上的函数运行的flat时间占所有采集样本时间总和的比例。

cum:当前函数和其子函数的调用耗时总的运行时间

cum%:cum耗时占总的采集样本的时间和的比例。

分析中可以看出主要是打印日志占用了比较大的cpu。

在命令行中可以输入svg,来输出cpu性能剖析的profile 图。
(pprof) svg 
Generating report in profile001.svg

Pasted image 20240304155555.png

图中显示了函数的调用关系,函数框中显示了函数自身的运行时长flat和其自身及其子函数运行时长cum,箭头上的时间则是其子函数的cum运行时长,时间越大,箭头越粗。

将二进制文件信息输出到浏览器上

可以通过如下命令来启动浏览器查看cpu使用情况,

go tool pprof -http=:8082  cpuprofile/cpu.pprof 
火焰图分析性能问题

通过web界面,我们除了按刚才的top和调用耗时图像外,甚至还能通过火焰图的方式,来查看cpu调用耗时,火焰图上,函数的调用顺序是从上往下,函数占用cpu越长,那么在火焰图上的区块就会越大。 火焰图中显示的是cum值。

Pasted image 20240304163822.png

peek

Pasted image 20240304165203.png

除了像top那样输出函数的耗时情况,还会输出调用函数和被调用函数,调用顺序从上到下。

source

Pasted image 20240304175712.png

在web界面可以通过source视图去查看函数节点的耗时以及它的子调用函数中耗时的地方,第一栏时间是flat耗时,第二栏时间是cum耗时。 耗时百分比是cum耗时占样本总和的百分比。

内存

http接口暴露的控制台查看

通过点击heap或者allocs 链接可以查看内存的分配情况,它们的输出都是一致的,

heap profile: 7: 5536 [110: 2178080] @ heap/1048576
2: 2304 [2: 2304] @ 0x100d7e0ec 0x100d7ea78 0x100d7f260 0x100d7f78c 0x100d811cc 0x100d817d4 0x100d7d6dc 0x100d7d5e4 0x100daba20
#	0x100d7e0eb	runtime.allocm+0x8b		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:1881
#	0x100d7ea77	runtime.newm+0x37		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:2207
#	0x100d7f25f	runtime.startm+0x11f		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:2491
#	0x100d7f78b	runtime.wakep+0xab		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:2590
#	0x100d811cb	runtime.resetspinning+0x7b	/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:3222
#	0x100d817d3	runtime.schedule+0x2d3		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:3383
#	0x100d7d6db	runtime.mstart1+0xcb		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:1419
#	0x100d7d5e3	runtime.mstart0+0x73		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:1367
#	0x100daba1f	runtime.mstart+0xf		/Users/lanpangzi/goproject/src/go/src/runtime/asm_arm64.s:117

下面来讲下网页输出内容的含义

heap profile: 7: 5536 [110: 2178080] @ heap/1048576

输出的第一行含义分别是:

7 代表 当前活跃的对象个数

5536 代表 当前活跃对象占用的字节数

110 代表 所有(包含历史的对象)对象个数

2178080 代表 所有对象(包含历史的对象)占用的对象字节数

1048576 控制了内存采样的频率,1048576 是两倍的内存采样频率的大小,默认采样频率是512kb 即平均每512kb就会采样一次,注意这个值512kb不是绝对的达到512kb就会进行采样,而是从一段时间内的采样来看的一个平均值。

接下来就是函数调用堆栈信息,来看第一行

2: 2304 [2: 2304] @ 0x100d7e0ec 0x100d7ea78 0x100d7f260 0x100d7f78c 0x100d811cc 0x100d817d4 0x100d7d6dc 0x100d7d5e4 0x100daba20

从左往右看:

2 代表 在该函数栈上当前活跃的对象个数

2304 代表 在该函数栈上当前活跃的对象所占用的字节数

方括号内的2 代表 在该函数栈上所有(包含历史的对象)对象个数

方括号内的2304 代表 在该函数栈上所有(包含历史的对象)对象所占用的字节数

然后是栈上pc寄存器的值。再往后就是具体的栈函数名信息了。

通过二进制文件查看

我们可以使用如下代码生成一个关于内存使用情况的二进制profile文件,下述代码中我用
allocMem 函数不断对一个字节数组进行append操作来模拟内存不断增大的情况。

package main  
  
import (  
    "github.com/pkg/profile"  
    "log"   
     "os"    
     "os/signal"    
     "syscall"    
     "time")  
  
func main() {  
    p := profile.Start(profile.MemProfile,  
       profile.ProfilePath("profile"),  
       profile.NoShutdownHook,  
    )  
    defer p.Stop()  
    go allocMem()  
    c := make(chan os.Signal, 1)  
    signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)  
    defer signal.Stop(c)  
    <-c  
  
}  
  
func allocMem() {  
    buf := []byte{}  
    mb := 1024 * 1024  
  
    for {  
       buf = append(buf, make([]byte, mb)...)  
       log.Println("total allocated memory", len(buf))  
       time.Sleep(time.Second)  
    }  
}

生成的二进制文件,和cpu分析一致,可以通过启动交互终端或者web浏览器去进行查看,所以启动终端的方式我这里就暂时略去了, 讲讲不同点, 针对于内存,pprof在web 浏览器界面提供了几个维度去分析。

inuse_space: 正在使用,尚未释放的空间

inuse_object: 正在使用,尚未释放的对象

alloc_space: 所有分配的空间,包含已释放的

alloc_objects: 所有分配的对象,包含已释放的

inuse_object举例,性能剖析图是这样的,箭头显示了正在使用的对象个数,其中 main.allocMem函数自身有一个4MB多的对象,其子函数有4个对象

Pasted image 20240306163756.png

我们可以根据source 选项也能得到同样的分析结果,

Pasted image 20240306164117.png

block

block可用于分析程序锁mutex,select ,channel通道, wait group 的阻塞行为。

但是block通常是关闭的,需要通过下面代码进行开启,

runtime.SetBlockProfileRate(1)

其中,SetBlockProfileRate 的参数名为rate,rate不同,对block事件的采样频率不同

  • 1 代表 始终对阻塞事件进行采样
  • <= 0 代表关闭阻塞事件的采样
  • 除了上述两种情况,SetBlockProfileRate 的参数传递的将会是一个纳秒值。

如果阻塞的时间大于了rate值则直接进行采样,如果阻塞的时间小于rate,那么阻塞事件将会有(阻塞时间)/rate 的可能性被采集到。

📢📢📢 注意下block事件的记录时机为获取锁资源或者channel 资源时,进行记录,也就是发生在lock函数中。

http接口暴露的控制台查看

通过点击http接口暴露的pprof控制的block链接,可以查看程序中block的情况,浏览器输出如下,

--- contention:
cycles/second=1000000000
180001216583 1 @ 0x1002a1198 0x1005159b8 0x100299fc4
#	0x1002a1197	sync.(*Mutex).Lock+0xa7	/Users/lanpangzi/goproject/src/go/src/sync/mutex.go:81
#	0x1005159b7	main.main.func2+0x27	/Users/lanpangzi/goproject/src/go/main/main.go:33

contention 是为这个profile文本信息取的名字,总之中文翻译是争用。

cycles/second 是cpu时钟每秒钟的周期数,用它来表示时间也是为了更精确,其实你可以发现在我的机器上每秒是10的9次方个周期,换算成纳秒就是1ns一个周期时间了。

接着的180001216583 是阻塞的周期数,其实周期就是cputicks,那么180001216583除以 cycles/second 即1000000000得到的就是阻塞的秒数了。

接着 1代表阻塞的次数。

无论是阻塞周期时长还是次数,都是一个累加值,即在相同的地方阻塞会导致这个值变大,并且次数会增加。剩下的部分就是函数堆栈信息了。

使用二进制文件查看

可以通过如下代码生成block的二进制文件,通过在主协程中使用lock方法后,不释放锁资源,启动子协程,让子协程同样使用lock方法模拟系统阻塞的情况。

import (  
    "fmt"  
    "github.com/pkg/profile"   
    "runtime"    
    "sync"    
    "time")  
  
/*  
*  
每次程序锁阻塞发生时,select 阻塞,channel通道阻塞,  
wait group 产生阻塞时就会记录一次阻塞行为  
*/  
func main() {  
  
    runtime.SetBlockProfileRate(1)  
    p := profile.Start(profile.BlockProfile,  
       profile.ProfilePath("profile"),  
       profile.NoShutdownHook)  
    defer p.Stop()  
    mLock := sync.Mutex{}  
    mLock.Lock()  
    go mockBlock(&mLock)  
    go mockBlock(&mLock)  
    time.Sleep(time.Second * 2)  
    mLock.Unlock()  
    time.Sleep(time.Second)  
    fmt.Println("End")  
}  
  
func mockBlock(l *sync.Mutex) {  
    l.Lock()  
    defer l.Unlock()  
    fmt.Println("获取锁")  
}

生成的profile文件,同样可以通过命令行和web浏览器的方式进行查看。

这里我直接采样浏览器的方式浏览,

 go tool pprof -http=:8083  ./profile/block.pprof

block提供了两个维度去观察阻塞事件 ,contention 发生阻塞的事件个数,delay 发生阻塞的时间。

contention 生成的profile图如下,可以看到发生阻塞的函数是main.mockBlock ,其自身包括其调用子函数的阻塞次数是2次。

Pasted image 20240307100213.png

delay生成的profile 图如下所示,main.mockBlock其自身包括其调用子函数的阻塞时间是4s。

Pasted image 20240307100441.png

📢📢📢 无论是阻塞次数还是阻塞时间,如果同一个函数发生了阻塞,那么次数和阻塞时间都会累加上去。

使用 阻塞时间/阻塞次数 能得到平均每次阻塞多长时间。

mutex

mutex 记录的是持有锁的时间,注意下它和block的区别,block记录的是在获取锁之前阻塞的时间。并且block还可以记录channel

mutex 的采集默认情况下也是关闭的,需要用以下代码进行开启,

runtime.SetMutexProfileFraction(1)

其中,SetMutexProfileFraction 的参数名为rate,rate不同,对block事件的采样频率不同

  • 1 代表 始终对持有锁事件进行采样
  • = 0 代表关闭阻塞事件的采样
  • < 0 将会把当前的rate值读取出来
  • > 1 将会有1/rate 的持有锁事件被采集

http接口暴露的控制台查看

通过点击http接口暴露的pprof控制的mutex链接,可以查看程序中锁持有的的情况,浏览器输出如下,

--- mutex:
cycles/second=1000000812
sampling period=1
180001727833 1 @ 0x100b9175c 0x100e05840 0x100b567ec 0x100b89fc4
#	0x100b9175b	sync.(*Mutex).Unlock+0x8b	/Users/lanpangzi/goproject/src/go/src/sync/mutex.go:190
#	0x100e0583f	main.main+0x19f			/Users/lanpangzi/goproject/src/go/main/main.go:39
#	0x100b567eb	runtime.main+0x25b		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:255

第一行mutex就是profile文本信息的名称了,同样也和block一样,采用cpu周期数计时,但是多了一个sampling period ,这个就是我们设置的采用频率。

接下来的数据都和block类似,180001727833就是锁持有的周期, 1为解锁的次数。然后是解锁的堆栈信息。

使用二进制文件查看

我们可以通过如下代码生成mutex的二进制文件,模拟阻塞的情况和block一致,代码不同的点是将样本采集的对象从block替换成了mutex。

package main  
  
import (  
    "fmt"  
    "github.com/pkg/profile"    
    "runtime"    
    "sync"    
    "time")  
  
func main() {  
  
    runtime.SetMutexProfileFraction(1)  
    p := profile.Start(profile.MutexProfile,  
       profile.ProfilePath("profile"),  
       profile.NoShutdownHook)  
    defer p.Stop()  
    mLock := sync.Mutex{}  
    mLock.Lock()  
    go mockBlock(&mLock)  
    go mockBlock(&mLock)  
    time.Sleep(time.Second * 2)  
    mLock.Unlock()  
    time.Sleep(time.Second)  
    fmt.Println("End")  
}  
  
func mockBlock(l *sync.Mutex) {  
    l.Lock()  
    defer l.Unlock()  
    fmt.Println("获取锁")  
}

这里我同样采用web浏览器的方式去查看mutext的剖析grapgh,mutex提供了两个维度去观察阻塞事件 ,contention 发生持有锁的事件个数,delay 持有锁的时间。

以下是contention 维度的图,可以看到在main方法中进行中持有了一次锁,在mockBlock方法中也持有了一次锁。

Pasted image 20240307130205.png

通过delay 维度可以发现main方法以及其子调用函数,持有锁的时间一共是2s。

Pasted image 20240307130432.png

goroutine

http接口暴露的控制台查看

点击控制界面的goroutine链接,我们会看到在程序中goroutine正在执行的函数堆栈信息,界面如下,

Pasted image 20240307161809.png

首先地址栏 /debug/pprof/goroutine?debug= 1 代表这是在访问goroutine指标信息,debug =1 代表访问的内容将会以文本可读的形式展现出来。 debug=0 则是会下载一个goroutine指标信息的二进制文件。 debug = 2 将会把当前所有协程的堆栈信息以文本可读形式展示在网页上。如下图所示:

Pasted image 20240307161931.png

debug =2 时的 如上图所示,41代表协程的id,方括号内running代表了协程的状态是运行中,接着就是该协程此时的堆栈信息了。

让我们再回到debug = 1的分析上面去,刚才分析完了地址栏里的debug参数,接着,我们看输出的第一行

goroutine profile: total 6 1 @ 0x102ad6c60 0x102acf7f4 0x102b04de0 0x102b6e850 0x102b6e8dc 0x102b6f79c 0x102c27d04 0x102c377c8 0x102d0fc74 0x102bea72c 0x102bebec0 0x102bebf4c 0x102ca4af0 0x102ca49dc 0x102d0b084 0x102d10f30 0x102d176a4 0x102b09fc4 #	0x102b04ddf	internal/poll.runtime_pollWait+0x5f		/Users/xiongchuanhong/goproject/src/go/src/runtime/netpoll.go:303 #	0x102b6e84f	internal/poll.(*pollDesc).wait+0x8f		/Users/xiongchuanhong/goproject/src/go/src/internal/poll/fd_poll_runtime.go:84 ......

goroutine profile 表明了这个profile的类型。

total 6 代表此时一共有6个协程。

接着是下面一行,1 代表了在这个堆栈上,只有一个协程在执行。但其实在计算出数字1时,并不仅仅按堆栈去做区分,还依据了协程labels值,也就是 协程的堆栈和lebels标签值 共同构成了一个key,而数字1就是在遍历所有协程信息时,对相同key进行累加计数得来的。

我们可以通过下面的方式为协程设置labels。

pprof.SetGoroutineLabels(pprof.WithLabels(context.Background(), pprof.Labels("name", "lanpangzi", "age", "18")))

通过上述代码,我可以为当前协程设置了两个标签值,分别是name和age,设置label值之后,再来看debug=1后的网页输出,可以发现 设置的labels出现了。

1 @ 0x104f86c60 0x104fb7358 0x105236368 0x104f867ec 0x104fba024 # labels: {"age":"18", "name":"lanpangzi"} #	0x104fb7357	time.Sleep+0x137	/Users/xiongchuanhong/goproject/src/go/src/runtime/time.go:193 #	0x105236367	main.main+0x437		/Users/xiongchuanhong/goproject/src/go/main/main.go:46 #	0x104f867eb	runtime.main+0x25b	/Users/xiongchuanhong/goproject/src/go/src/runtime/proc.go:255

而数字1之后,就是协程正在执行的函数堆栈信息了。

使用二进制文件查看

我们可以通过如下代码生成profile的二进制文件,在下述代码中,我生成了两个协程,然后开启对goroutine指标的采集,

package main  
  
import (  
    "fmt"  
    "github.com/pkg/profile"    
    "time")  
  
func main() {  
    go mockGo()  
    go mockGo()  
    p := profile.Start(profile.GoroutineProfile,  
       profile.ProfilePath("profile"),  
       profile.NoShutdownHook)  
    p.Stop()  
}  
  
func mockGo() {  
    time.Sleep(time.Second * 3)  
    fmt.Println("获取锁")  
}

生成的goroutine二进制文件后,查看goroutine 方式也和其他profile指标一致,这里我直接放上通过web浏览器查看的graph。 显示的堆栈信息为当前程序中协程正在执行的堆栈,可以看到main.mockGo 函数中有两个协程在执行这个方法,main方法中一个协程在执行。

Pasted image 20240307143449.png

thread

http接口暴露的控制台查看

介绍完goroutine指标的输出信息后,再来看看threadcreate 线程创建指标的 输出信息。先来看下在控制台中通过点击thread链接看到的输出,

image.png 老规矩,先看地址栏,debug=1代表 输出的是文本可读的信息,threadcreate 就没有debug=2的特别输出了,debug=0时 同样也会下载一个可供go tool pprof分析的二进制文件。

接着threadcreate pfofile表明了profile的类型。

total 12 代表了此时总共有12个线程被创建。

11 代表了在这个总共有11个线程是在这个堆栈的代码段上被创建的,注意这里后面没有堆栈内容,说明runtime在创建线程时,并没有把此时的堆栈记录下来,原因有可能是 这个线程是runtime自己使用的,堆栈没有必要展示给用户,所以干脆不记录了,具体原因这里就不深入研究了。

上面输出的内容可以看到在main方法里面创建了一个线程,runtime.newm 方法内部,runtime会启动一个系统线程。

使用二进制文件查看

在代码中开始采集thread的信息,生成二进制文件可以采用以下代码,和goroutine示例代码类似,不同的是改变采集的指标对象,改成了ThreadcreationProfile。

package main  
  
import (  
    "fmt"  
    "github.com/pkg/profile"    
    "time")  
  
func main() {  
    go mockGo()  
    go mockGo()  
    p := profile.Start(profile.ThreadcreationProfile,  
       profile.ProfilePath("profile"),  
       profile.NoShutdownHook)  
    p.Stop()  
}  
  
func mockGo() {  
    time.Sleep(time.Second * 3)  
    fmt.Println("获取锁")  
}

生成的二进制文件通过 go tool pprof -http=:8083 ../profile/threadcreation.pprof 启动浏览器窗口查看其graph,如下是线程的graph,图中的堆栈信息是创建线程时的堆栈信息。

Pasted image 20240307143831.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/437868.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第三百八十八回

文章目录 概念介绍使用方法示例代码 我们在上一章回中介绍了DateRangePickerDialog Widget相关的内容,本章回中将介绍Radio Widget.闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 我们在这里说的Radio Widget是指单选按钮&#xff0c;没有选中时是圆形边框&#x…

小白在VMware Workstation Pro上安装部署SinoDB V16.8

一、安装环境说明 CPU&#xff1a;2核或以上&#xff0c;内存&#xff1a;2G或以上&#xff1b;磁盘10G或以上&#xff1b;网卡&#xff1a;千兆 1.1检查服务器内存大小 命令&#xff1a;free -m 1.2检查服务器磁盘空间大小 命令&#xff1a;df -h 1.3检查服务器网络配置信息 命…

Python Tkinter GUI 基本概念

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd;如果停止&#xff0c;就是低谷&#xf…

VM内存结构和垃圾回收机制

引言 在计算机科学中&#xff0c;虚拟机&#xff08;VM&#xff09;是一个重要的概念&#xff0c;它允许程序在硬件平台之上运行。虚拟机模拟真实机器的行为&#xff0c;为程序提供了一个独立的运行环境。本文将深入探讨VM的内存结构和垃圾回收机制&#xff0c;以帮助读者更好…

“python -m experiments.cifar10_test“是什么意思

具体解释如下&#xff1a; "python" 是运行 Python 解释器的命令。"-m" 是一个选项&#xff0c;用于指定要运行的模块。"experiments.cifar10_test" 是要运行的 Python 模块的名称。 其中 虽说main.py文件在上一级目录中&#xff0c;仍然可以在…

计算阶梯数 Python

题目描述 爱因斯坦曾出过这样一道有趣的数学题&#xff1a; 有一个长阶梯&#xff0c; 若每步上2阶&#xff0c;最后剩1阶&#xff1b; 若每步上3阶&#xff0c;最后剩2阶&#xff1b; 若每步上5阶&#xff0c;最后剩4阶&#xff1b; 若每步上6阶&#xff0c;最后剩5阶&#xf…

一文教会你Jenkins 主从模式实现分布式自动化测试

背景 日常构建Jenkins任务中&#xff0c;会经常出现下面的情况&#xff1a; &#xff08;1&#xff09;自动化测试执行需要消耗大量的 CPU 和内存资源&#xff0c;如果服务器上还有其他的服务&#xff0c;可能会造成卡顿或者宕机这样的情况&#xff1b; &#xff08;2&#…

基于R语言lavaan的SEM在复杂统计建模中的科研技术新突破

此外&#xff0c;我们还将深入探讨R语言的基础知识、结构方程模型的基本原理、lavaan程序包的使用方法等内容。无论是潜变量分析、复合变量分析&#xff0c;还是非线性/非正态/缺失数据处理、分类变量分析、分组数据处理等复杂问题&#xff0c;我们都将一一为您解析。 希望通过…

redis未设置密码被植入挖矿脚本

最近一台测试linux响应速度贼慢&#xff0c;检查发现cpu消耗高达100%&#xff01;查看进程杀死后过段时间又重启了&#xff0c;一时间也摸不到头绪。无意间发现启动redis的时候cpu瞬间拉到了100%&#xff0c;主要就是zzh和newinit.sh两个脚本。百度了一下说是被植入了挖矿脚本&…

腾讯云学生服务器详细介绍_学生服务器价格_学生机申请流程

2024年腾讯云学生服务器优惠活动「云校园」&#xff0c;学生服务器优惠价格&#xff1a;轻量应用服务器2核2G学生价30元3个月、58元6个月、112元一年&#xff0c;轻量应用服务器4核8G配置191.1元3个月、352.8元6个月、646.8元一年&#xff0c;CVM云服务器2核4G配置842.4元一年&…

代码随想录算法训练营第三十九天|动态规划|62.不同路径、63. 不同路径 II

62.不同路径 文章 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&…

什么样的项目适合Web自动化测试

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

Java引用传递及基本应用

在 Java 中&#xff0c;传递参数的方式主要有两种&#xff1a;值传递&#xff08;传递的是对象的引用值&#xff09;和引用传递。本教程将重点介绍 Java 中的引用传递以及其基本应用。 1. 引用传递概念 在 Java 中&#xff0c;所有的方法参数都是通过值传递的。对于对象类型的…

好物周刊#43:设计素材下载

https://yuque.com/cunyu1943 村雨遥的好物周刊&#xff0c;记录每周看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;每周五发布。 一、项目 1. frp 一个专注于内网穿透的高性能的反向代理应用&#xff0c;支持 TCP、UDP、HTTP、HTTPS 等多种协议&#xff0c…

FreeRTOS day2

使用ADC采样光敏电阻数值&#xff0c;如何根据这个数值调节LED灯亮度 总结DMA空闲中断接收数据的使用方法 首先要要选择串口然后配置串口的参数&#xff0c;配置MDA通道选择接受数据&#xff0c;配置空闲中断&#xff0c;定义一个数据接收的容器&#xff0c;启动MDA传输当串口…

【RabbitMQ】WorkQueue

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;MQ ⛺️稳中求进&#xff0c;晒太阳 Work Queues Work queues任务模型&#xff0c;简单来说就是让多个消费者绑定到一个队列&#xff0c;共同消费队列中的消息 当消息处理比较耗时的时候&…

电销平台架构的演变与升级

简介 信也科技电销平台承载了公司400多坐席的日常外呼任务&#xff0c;随着公司业务规模不断增长&#xff0c;业务复杂度不断提升&#xff0c;营销模式需要多样化&#xff0c;营销流程需要更加灵活。为了更好地赋能业务、提高客户转化率&#xff0c;电销平台不断升级优化&#…

不伤耳朵的蓝牙耳机推荐,骨传导耳机选购前必知的几大要点

在目前的蓝牙耳机市场上&#xff0c;骨传导蓝牙耳机以其独特的设计和不伤耳朵的好处而受到广泛关注。骨传导蓝牙耳机通过骨头传导声音&#xff0c;无需进入耳道&#xff0c;从而减少了耳朵的不适和潜在伤害。 骨传导蓝牙耳机这种开放式的设计允许用户在享受音乐的同时&#xf…

国家积极推进长城国家文化公园建设

长城脚下&#xff0c;文化绽放——国家积极推进长城国家文化公园建设 在中华大地的北方&#xff0c;横亘着一条巨龙&#xff0c;它见证着中华民族的沧桑岁月&#xff0c;承载着我们的民族记忆&#xff0c;它就是——长城。这座千年的雄关&#xff0c;不仅是中国的象征&#xf…

Android使用WebView打开网页链接(内嵌H5网页)的两种方式之一

发布Android应用&#xff0c;除了用原生开发外&#xff0c;更多是采用内嵌H5网页的方式来做&#xff0c;便于更新以及多平台使用。 一、第一种方式是直接通过WebView打开外部H5链接。 新建Android工程 直接创建一个工程&#xff0c;点击运行就可以了&#xff0c;打开是个空页…