From 8bef4cc76426c263212df7ea13dd7823914c4c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=A5=E5=88=A9=E5=97=B7=E5=97=B7=E5=97=B7=E5=8F=AB?= <53102695+aoliaoaoaojiao@users.noreply.github.com> Date: Tue, 9 Aug 2022 22:27:14 +0800 Subject: [PATCH 01/20] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=80=A7=E8=83=BD?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=96=B9=E6=B3=95=20(#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加OpenglServer和SysmontapServer分别获取GPU/FPS/CPU/MEM相关数据 * 修复接收CPU数据uint64转换失败问题 * 添加GetPerfmon方法 * 增加获取系统网络上发/下载方法;todo GetPerfmon、iterFps、iterGPU、iterNetWork方法优化,增加单应用网络状态获取方法 * 增加單個應用網絡狀態監控 * 优化方法 * 优化输出流 * 优化方法 * 增加相关结束函数 * 增加参数类型 * 优化方法,开放CPU和MEM、系统FPS和GPU、系统networking性能数据相关方法 * 优化输出 * 优化输出 * 优化输出 * 优化输出 * 优化代码,隐藏iter方法 * 细节优化 * 优化,尝试关闭chan * 添加性能数据对应的实体类和输出数据的方法 * 初步确定优化方式,后续继续更改 * 优化性能数据 * toString方法优化 * 优化chan关闭 * 优化无参数时异常情况、优化实体类、优化测试方法 * 1.删除perfmorance包,性能数据struct放到instruments中,里面的性能数据struct删除ToString、ToJ…son、ToFormat方法 2.隐藏StopPerfmon方法,调用GetPerfmon返回的context.CancelFunc直接关闭 3.弃用PerfMonData通道处理数据,原始性能数据统一输送到interface通道里,由用户自行决定数据的处理 4.CPU、MEM优化异常PID信息提示 5.GetPerfmon参数优化,默认展示所有性能数据 6.fix cpu、mem plist Unknown parameter Co-authored-by: 奥利嗷嗷嗷叫 <53102695+alliaoaoo@users.noreply.github.com> --- device.go | 138 ++++++++- device_test.go | 48 ++- idevice.go | 24 ++ instruments.go | 419 ++++++++++++++++++++++++++ instruments_test.go | 2 +- pkg/libimobiledevice/keyedarchiver.go | 13 +- 6 files changed, 634 insertions(+), 10 deletions(-) diff --git a/device.go b/device.go index 6d3d83e..5df66d9 100644 --- a/device.go +++ b/device.go @@ -5,16 +5,15 @@ import ( "context" "errors" "fmt" - "os" - "path" - "strings" - "time" - "github.com/electricbubble/gidevice/pkg/ipa" "github.com/electricbubble/gidevice/pkg/libimobiledevice" "github.com/electricbubble/gidevice/pkg/nskeyedarchiver" uuid "github.com/satori/go.uuid" "howett.net/plist" + "os" + "path" + "strings" + "time" ) const LockdownPort = 62078 @@ -591,6 +590,133 @@ func (d *device) MoveCrashReport(hostDir string, opts ...CrashReportMoverOption) return d.crashReportMover.Move(hostDir, opts...) } +func (d *device) GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCancel context.CancelFunc, perfErr error) { + if d.lockdown == nil { + if _, err := d.lockdownService(); err != nil { + return nil, nil, err + } + } + if opts==nil { + return nil, nil, fmt.Errorf("parameter is empty") + } + if !opts.OpenChanCPU &&!opts.OpenChanMEM &&!opts.OpenChanGPU &&!opts.OpenChanFPS &&!opts.OpenChanNetWork { + opts.OpenChanCPU = true + opts.OpenChanMEM = true + opts.OpenChanGPU = true + opts.OpenChanFPS = true + opts.OpenChanNetWork = true + } + + var err error + + var instruments Instruments + ctx, cancel := context.WithCancel(context.Background()) + + chanCPU := make(chan CPUInfo) + chanMEM := make(chan MEMInfo) + var cancelSysmontap context.CancelFunc + + if opts.OpenChanCPU || opts.OpenChanMEM { + instruments, err = d.lockdown.InstrumentsService() + if err != nil { + return nil, cancel, err + } + chanCPU, chanMEM, cancelSysmontap, err = instruments.StartSysmontapServer(opts.PID, ctx) + if err != nil { + cancelSysmontap() + return nil, cancel, err + } + } + + chanFPS := make(chan FPSInfo) + chanGPU := make(chan GPUInfo) + var cancelOpengl context.CancelFunc + + if opts.OpenChanGPU || opts.OpenChanFPS { + instruments, err = d.lockdown.InstrumentsService() + if err != nil { + return nil, cancel, err + } + chanFPS, chanGPU, cancelOpengl, err = instruments.StartOpenglServer(ctx) + if err != nil { + cancelOpengl() + return nil, cancel, err + } + } + + chanNetWork := make(chan NetWorkingInfo) + var cancelNetWork context.CancelFunc + if opts.OpenChanNetWork { + instruments, err = d.lockdown.InstrumentsService() + if err != nil { + return nil, cancel, err + } + chanNetWork, cancelNetWork, err = instruments.StartNetWorkingServer(ctx) + if err != nil { + cancelNetWork() + return nil, cancel, err + } + } + // 弃用之前的PerfMonData ,统一汇总到interface里,由用户自行决定处理数据 + result := make(chan interface{}) + go func() { + for { + select { + case v, ok := <-chanCPU: + if opts.OpenChanCPU && ok { + result<-v + } + case v, ok := <-chanMEM: + if opts.OpenChanMEM && ok { + result<-v + } + case v, ok := <-chanFPS: + if opts.OpenChanFPS && ok { + result<-v + } + case v, ok := <-chanGPU: + if opts.OpenChanGPU && ok { + result<-v + } + case v, ok := <-chanNetWork: + if opts.OpenChanNetWork && ok { + result<-v + } + case <-ctx.Done(): + err:=d.stopPerfmon(opts) + if err!=nil { + fmt.Println(err) + } + close(result) + return + } + } + }() + return result, cancel, err +} + +func (d *device)stopPerfmon(opts *PerfmonOption)(err error) { + if _, err = d.instrumentsService(); err != nil { + return err + } + if opts.OpenChanCPU || opts.OpenChanMEM { + if err = d.instruments.StopSysmontapServer(); err != nil { + return err + } + } + if opts.OpenChanGPU || opts.OpenChanFPS { + if err = d.instruments.StopOpenglServer(); err != nil { + return err + } + } + if opts.OpenChanNetWork { + if err = d.instruments.StopNetWorkingServer(); err != nil { + return err + } + } + return nil +} + func (d *device) XCTest(bundleID string, opts ...XCTestOption) (out <-chan string, cancel context.CancelFunc, err error) { xcTestOpt := defaultXCTestOption() for _, fn := range opts { @@ -836,4 +962,4 @@ func (d *device) _uploadXCTestConfiguration(bundleID string, sessionId uuid.UUID } return -} +} \ No newline at end of file diff --git a/device_test.go b/device_test.go index ac18e95..45bebff 100644 --- a/device_test.go +++ b/device_test.go @@ -1,6 +1,7 @@ package giDevice import ( + "encoding/json" "fmt" "os" "os/signal" @@ -68,7 +69,7 @@ func Test_device_SavePairRecord(t *testing.T) { func Test_device_XCTest(t *testing.T) { setupLockdownSrv(t) - bundleID = "com.leixipaopao.WebDriverAgentRunner.xctrunner" + bundleID = "com.DataMesh.CheckList" out, cancel, err := dev.XCTest(bundleID) // out, cancel, err := dev.XCTest(bundleID, WithXCTestEnv(map[string]interface{}{"USE_PORT": 8222, "MJPEG_SERVER_PORT": 8333})) if err != nil { @@ -154,6 +155,51 @@ func Test_device_Shutdown(t *testing.T) { dev.Shutdown() } +func Test_device_Perf(t *testing.T) { + //setupDevice(t) + //SetDebug(true,true) + setupLockdownSrv(t) + + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, os.Kill) + + var opts = &PerfmonOption{ + //PID: "0", + //OpenChanNetWork: true, + //OpenChanFPS: true, + OpenChanMEM: true, + //OpenChanCPU: true, + //OpenChanGPU: true, + } + + outData, cannel, err := dev.GetPerfmon(opts) + if err != nil { + fmt.Println(err) + os.Exit(0) + } + var timeLast = time.Now().Unix() + for { + select { + case <-c: + if cannel!=nil { + cannel() + } + default: + data ,ok := <-outData + if !ok { + fmt.Println("end get perfmon data") + return + } + result, _ := json.MarshalIndent(data, "", "\t") + fmt.Println(string(result)) + if time.Now().Unix()-timeLast >60 { + cannel() + } + } + } + +} + func Test_device_InstallationProxyBrowse(t *testing.T) { setupDevice(t) diff --git a/idevice.go b/idevice.go index 26c5df1..93a2753 100644 --- a/idevice.go +++ b/idevice.go @@ -77,6 +77,8 @@ type Device interface { springBoardService() (springBoard SpringBoard, err error) GetIconPNGData(bundleId string) (raw *bytes.Buffer, err error) GetInterfaceOrientation() (orientation OrientationState, err error) + + GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCancel context.CancelFunc, perfErr error) } type DeviceProperties = libimobiledevice.DeviceProperties @@ -153,6 +155,19 @@ type Instruments interface { // SysMonStart(cfg ...interface{}) (_ interface{}, err error) registerCallback(obj string, cb func(m libimobiledevice.DTXMessageResult)) + + StartOpenglServer(ctx context.Context) (chanFPS chan FPSInfo, chanGPU chan GPUInfo, cancel context.CancelFunc, err error) + + StopOpenglServer() (err error) + + StartSysmontapServer(pid string, ctx context.Context) (chanCPU chan CPUInfo, chanMem chan MEMInfo, cancel context.CancelFunc, err error) + + StopSysmontapServer() (err error) + //ProcessNetwork(pid int) (out <-chan interface{}, cancel context.CancelFunc, err error) + + StartNetWorkingServer(ctx context.Context) (chanNetWorking chan NetWorkingInfo, cancel context.CancelFunc, err error) + + StopNetWorkingServer() (err error) } type Testmanagerd interface { @@ -357,6 +372,15 @@ func WithUpdateToken(updateToken string) AppListOption { } } +type PerfmonOption struct { + PID string + OpenChanGPU bool + OpenChanFPS bool + OpenChanCPU bool + OpenChanMEM bool + OpenChanNetWork bool +} + type Process struct { IsApplication bool `json:"isApplication"` Name string `json:"name"` diff --git a/instruments.go b/instruments.go index a07f09d..fc4c2d6 100644 --- a/instruments.go +++ b/instruments.go @@ -1,8 +1,11 @@ package giDevice import ( + "context" "encoding/json" "fmt" + "time" + "github.com/electricbubble/gidevice/pkg/libimobiledevice" ) @@ -257,6 +260,381 @@ func (i *instruments) registerCallback(obj string, cb func(m libimobiledevice.DT i.client.RegisterCallback(obj, cb) } +func (i *instruments) StartSysmontapServer(pid string, ctxParent context.Context) (chanCPU chan CPUInfo, chanMem chan MEMInfo, cancel context.CancelFunc, err error) { + var id uint32 + if ctxParent == nil { + return nil, nil, nil, fmt.Errorf("missing context") + } + ctx, cancelFunc := context.WithCancel(ctxParent) + _outMEM := make(chan MEMInfo) + _outCPU := make(chan CPUInfo) + if id, err = i.requestChannel("com.apple.instruments.server.services.sysmontap"); err != nil { + return nil, nil, cancelFunc, err + } + + selector := "setConfig:" + args := libimobiledevice.NewAuxBuffer() + + var config map[string]interface{} + config = make(map[string]interface{}) + { + config["bm"] = 0 + config["cpuUsage"] = true + + config["procAttrs"] = []string{ + "memVirtualSize", "cpuUsage", "ctxSwitch", "intWakeups", + "physFootprint", "memResidentSize", "memAnon", "pid"} + + config["sampleInterval"] = 1000000000 + // 系统信息字段 + config["sysAttrs"] = []string{ + "vmExtPageCount", "vmFreeCount", "vmPurgeableCount", + "vmSpeculativeCount", "physMemSize"} + // 刷新频率 + config["ur"] = 1000 + } + + args.AppendObject(config) + if _, err = i.client.Invoke(selector, args, id, true); err != nil { + return nil, nil, cancelFunc, err + } + selector = "start" + args = libimobiledevice.NewAuxBuffer() + + if _, err = i.client.Invoke(selector, args, id, true); err != nil { + return nil, nil, cancelFunc, err + } + + i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { + select { + case <-ctx.Done(): + return + default: + mess := m.Obj + chanCPUAndMEMData(mess, _outMEM, _outCPU, pid) + } + }) + + go func() { + i.registerCallback("_Golang-iDevice_Over", func(_ libimobiledevice.DTXMessageResult) { + cancelFunc() + }) + select { + case <-ctx.Done(): + var isOpen bool + if _outCPU != nil { + _, isOpen = <-_outMEM + if isOpen { + close(_outMEM) + } + } + if _outMEM != nil { + _, isOpen = <-_outCPU + if isOpen { + close(_outCPU) + } + } + } + return + }() + return _outCPU, _outMEM, cancelFunc, err +} + +func chanCPUAndMEMData(mess interface{}, _outMEM chan MEMInfo, _outCPU chan CPUInfo, pid string) { + switch mess.(type) { + case []interface{}: + var infoCPU CPUInfo + var infoMEM MEMInfo + messArray := mess.([]interface{}) + if len(messArray) == 2 { + var sinfo = messArray[0].(map[string]interface{}) + var pinfolist = messArray[1].(map[string]interface{}) + if sinfo["CPUCount"] == nil { + var temp = sinfo + sinfo = pinfolist + pinfolist = temp + } + if sinfo["CPUCount"] != nil && pinfolist["Processes"] != nil { + var cpuCount = sinfo["CPUCount"] + var sysCpuUsage = sinfo["SystemCPUUsage"].(map[string]interface{}) + var cpuTotalLoad = sysCpuUsage["CPU_TotalLoad"] + // 构建返回信息 + infoCPU.CPUCount = int(cpuCount.(uint64)) + infoCPU.SysCpuUsage = cpuTotalLoad.(float64) + //finalCpuInfo["attrCpuTotal"] = cpuTotalLoad + + var cpuUsage = 0.0 + pidMess := pinfolist["Processes"].(map[string]interface{})[pid] + if pidMess != nil { + processInfo := sysmonPorceAttrs(pidMess) + cpuUsage = processInfo["cpuUsage"].(float64) + infoCPU.CPUUsage = cpuUsage + infoCPU.Pid = pid + infoCPU.AttrCtxSwitch = uIntToInt64(processInfo["ctxSwitch"]) + infoCPU.AttrIntWakeups = uIntToInt64(processInfo["intWakeups"]) + + infoMEM.Vss = uIntToInt64(processInfo["memVirtualSize"]) + infoMEM.Rss = uIntToInt64(processInfo["memResidentSize"]) + infoMEM.Anon = uIntToInt64(processInfo["memAnon"]) + infoMEM.PhysMemory = uIntToInt64(processInfo["physFootprint"]) + + } else { + infoCPU.Mess = "invalid PID" + infoMEM.Mess = "invalid PID" + + infoMEM.Vss = -1 + infoMEM.Rss = -1 + infoMEM.Anon = -1 + infoMEM.PhysMemory = -1 + } + + infoMEM.TimeStamp = time.Now().UnixNano() + infoCPU.TimeStamp = time.Now().UnixNano() + + _outMEM <- infoMEM + _outCPU <- infoCPU + } + } + } +} + +// 获取进程相关信息 +func sysmonPorceAttrs(cpuMess interface{}) (outCpuInfo map[string]interface{}) { + if cpuMess == nil { + return nil + } + cpuMessArray, ok := cpuMess.([]interface{}) + if !ok { + return nil + } + if len(cpuMessArray) != 8 { + return nil + } + if outCpuInfo == nil { + outCpuInfo = map[string]interface{}{} + } + // 虚拟内存 + outCpuInfo["memVirtualSize"] = cpuMessArray[0] + // CPU + outCpuInfo["cpuUsage"] = cpuMessArray[1] + // 每秒进程的上下文切换次数 + outCpuInfo["ctxSwitch"] = cpuMessArray[2] + // 每秒进程唤醒的线程数 + outCpuInfo["intWakeups"] = cpuMessArray[3] + // 物理内存 + outCpuInfo["physFootprint"] = cpuMessArray[4] + + outCpuInfo["memResidentSize"] = cpuMessArray[5] + // 匿名内存 + outCpuInfo["memAnon"] = cpuMessArray[6] + + outCpuInfo["PID"] = cpuMessArray[7] + return +} + +func (i *instruments) StopSysmontapServer() (err error) { + id, err := i.requestChannel("com.apple.instruments.server.services.sysmontap") + if err != nil { + return err + } + selector := "stop" + args := libimobiledevice.NewAuxBuffer() + if _, err = i.client.Invoke(selector, args, id, true); err != nil { + return err + } + return nil +} + +// todo 获取单进程流量情况,看情况做不做 +// 目前只获取到系统全局的流量情况,单进程需要用到set,go没有,并且实际用python测试单进程的流量情况不准 +func (i *instruments) StartNetWorkingServer(ctxParent context.Context) (chanNetWorking chan NetWorkingInfo, cancel context.CancelFunc, err error) { + var id uint32 + if ctxParent == nil { + return nil, nil, fmt.Errorf("missing context") + } + ctx, cancelFunc := context.WithCancel(ctxParent) + _outNetWork := make(chan NetWorkingInfo) + if id, err = i.requestChannel("com.apple.instruments.server.services.networking"); err != nil { + return nil, cancelFunc, err + } + selector := "startMonitoring" + args := libimobiledevice.NewAuxBuffer() + + if _, err = i.client.Invoke(selector, args, id, true); err != nil { + return nil, cancelFunc, err + } + i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { + select { + case <-ctx.Done(): + return + default: + receData, ok := m.Obj.([]interface{}) + if ok && len(receData) == 2 { + sendAndReceiveData, ok := receData[1].([]interface{}) + if ok { + var netData NetWorkingInfo + // 有时候是uint8,有时候是uint64。。。恶心 + netData.RxBytes = uIntToInt64(sendAndReceiveData[0]) + netData.RxPackets = uIntToInt64(sendAndReceiveData[1]) + netData.TxBytes = uIntToInt64(sendAndReceiveData[2]) + netData.TxPackets = uIntToInt64(sendAndReceiveData[3]) + netData.TimeStamp = time.Now().UnixNano() + _outNetWork <- netData + } + } + } + }) + go func() { + i.registerCallback("_Golang-iDevice_Over", func(_ libimobiledevice.DTXMessageResult) { + cancelFunc() + }) + select { + case <-ctx.Done(): + _, isOpen := <-_outNetWork + if isOpen { + close(_outNetWork) + } + } + return + }() + return _outNetWork, cancelFunc, err +} + +func uIntToInt64(num interface{}) (cnum int64) { + switch num.(type) { + case uint64: + return int64(num.(uint64)) + case uint32: + return int64(num.(uint32)) + case uint16: + return int64(num.(uint16)) + case uint8: + return int64(num.(uint8)) + case uint: + return int64(num.(uint)) + } + return -1 +} + +func (i *instruments) StopNetWorkingServer() (err error) { + var id uint32 + id, err = i.requestChannel("com.apple.instruments.server.services.networking") + if err != nil { + return err + } + selector := "stopMonitoring" + args := libimobiledevice.NewAuxBuffer() + + if _, err = i.client.Invoke(selector, args, id, true); err != nil { + return err + } + return nil +} + +func (i *instruments) StartOpenglServer(ctxParent context.Context) (chanFPS chan FPSInfo, chanGPU chan GPUInfo, cancel context.CancelFunc, err error) { + var id uint32 + if ctxParent == nil { + return nil, nil, nil, fmt.Errorf("missing context") + } + ctx, cancelFunc := context.WithCancel(ctxParent) + _outFPS := make(chan FPSInfo) + _outGPU := make(chan GPUInfo) + if id, err = i.requestChannel("com.apple.instruments.server.services.graphics.opengl"); err != nil { + return nil, nil, cancelFunc, err + } + + selector := "availableStatistics" + args := libimobiledevice.NewAuxBuffer() + + if _, err = i.client.Invoke(selector, args, id, true); err != nil { + return nil, nil, cancelFunc, err + } + + selector = "setSamplingRate:" + if err = args.AppendObject(0.0); err != nil { + return nil, nil, cancelFunc, err + } + if _, err = i.client.Invoke(selector, args, id, true); err != nil { + return nil, nil, cancelFunc, err + } + + selector = "startSamplingAtTimeInterval:processIdentifier:" + args = libimobiledevice.NewAuxBuffer() + if err = args.AppendObject(0); err != nil { + return nil, nil, cancelFunc, err + } + if err = args.AppendObject(0); err != nil { + return nil, nil, cancelFunc, err + } + if _, err = i.client.Invoke(selector, args, id, true); err != nil { + return nil, nil, cancelFunc, err + } + + i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { + select { + case <-ctx.Done(): + return + default: + mess := m.Obj + var deviceUtilization = mess.(map[string]interface{})["Device Utilization %"] // Device Utilization + var tilerUtilization = mess.(map[string]interface{})["Tiler Utilization %"] // Tiler Utilization + var rendererUtilization = mess.(map[string]interface{})["Renderer Utilization %"] // Renderer Utilization + + var infoGPU GPUInfo + + infoGPU.DeviceUtilization = uIntToInt64(deviceUtilization) + infoGPU.TilerUtilization = uIntToInt64(tilerUtilization) + infoGPU.RendererUtilization = uIntToInt64(rendererUtilization) + infoGPU.TimeStamp = time.Now().UnixNano() + _outGPU <- infoGPU + + var infoFPS FPSInfo + var fps = mess.(map[string]interface{})["CoreAnimationFramesPerSecond"] + infoFPS.FPS = int(uIntToInt64(fps)) + infoFPS.TimeStamp = time.Now().UnixNano() + _outFPS <- infoFPS + } + }) + go func() { + i.registerCallback("_Golang-iDevice_Over", func(_ libimobiledevice.DTXMessageResult) { + cancelFunc() + }) + select { + case <-ctx.Done(): + var isOpen bool + if _outGPU != nil { + _, isOpen = <-_outGPU + if isOpen { + close(_outGPU) + } + } + if _outFPS != nil { + _, isOpen = <-_outFPS + if isOpen { + close(_outFPS) + } + } + } + return + }() + return _outFPS, _outGPU, cancelFunc, err +} + +func (i *instruments) StopOpenglServer() (err error) { + + id, err := i.requestChannel("com.apple.instruments.server.services.graphics.opengl") + if err != nil { + return err + } + selector := "stop" + args := libimobiledevice.NewAuxBuffer() + + if _, err = i.client.Invoke(selector, args, id, true); err != nil { + return err + } + return nil +} + type Application struct { AppExtensionUUIDs []string `json:"AppExtensionUUIDs,omitempty"` BundlePath string `json:"BundlePath"` @@ -282,3 +660,44 @@ type DeviceInfo struct { ProductVersion string `json:"_productVersion"` XRDeviceClassName string `json:"_xrdeviceClassName"` } + +type CPUInfo struct { + Pid string `json:"PID"` // 线程 + CPUCount int `json:"cpuCount"` // CPU总数 + TimeStamp int64 `json:"timeStamp"` // 时间戳 + CPUUsage float64 `json:"cpuUsage,omitempty"` // 单个进程的CPU使用率 + SysCpuUsage float64 `json:"sysCpuUsage,omitempty"` // 系统总体CPU占用 + AttrCtxSwitch int64 `json:"attrCtxSwitch,omitempty"` // 上下文切换数 + AttrIntWakeups int64 `json:"attrIntWakeups,omitempty"` // 唤醒数 + Mess string `json:"mess,omitempty"` // 提示信息,当PID没输入或者信息错误时提示 +} + +type FPSInfo struct { + FPS int `json:"fps"` + TimeStamp int64 `json:"timeStamp"` +} + +type GPUInfo struct { + TilerUtilization int64 `json:"tilerUtilization"` // 处理顶点的GPU时间占比 + TimeStamp int64 `json:"timeStamp"` + Mess string `json:"mess,omitempty"` // 提示信息,当PID没输入时提示 + DeviceUtilization int64 `json:"deviceUtilization"` // 设备利用率 + RendererUtilization int64 `json:"rendererUtilization"` // 渲染器利用率 +} + +type MEMInfo struct { + Anon int64 `json:"anon"` // 虚拟内存 + PhysMemory int64 `json:"physMemory"` // 物理内存 + Rss int64 `json:"rss"` // 总内存 + Vss int64 `json:"vss"` // 虚拟内存 + TimeStamp int64 `json:"timeStamp"` // + Mess string `json:"mess,omitempty"` // 提示信息,当PID没输入或者信息错误时提示 +} + +type NetWorkingInfo struct { + RxBytes int64 `json:"rxBytes"` + RxPackets int64 `json:"rxPackets"` + TxBytes int64 `json:"txBytes"` + TxPackets int64 `json:"txPackets"` + TimeStamp int64 `json:"timeStamp"` +} diff --git a/instruments_test.go b/instruments_test.go index 8e208e6..98bf203 100644 --- a/instruments_test.go +++ b/instruments_test.go @@ -22,7 +22,7 @@ func setupInstrumentsSrv(t *testing.T) { func Test_instruments_AppLaunch(t *testing.T) { setupInstrumentsSrv(t) - // bundleID = "com.leixipaopao.WebDriverAgentRunner.xctrunner" + bundleID = "com.DataMesh.CheckList" // pid, err := dev.AppLaunch(bundleID) pid, err := instrumentsSrv.AppLaunch(bundleID) diff --git a/pkg/libimobiledevice/keyedarchiver.go b/pkg/libimobiledevice/keyedarchiver.go index 1cb2e52..f942c5c 100644 --- a/pkg/libimobiledevice/keyedarchiver.go +++ b/pkg/libimobiledevice/keyedarchiver.go @@ -2,6 +2,7 @@ package libimobiledevice import ( "reflect" + "strconv" "time" "howett.net/plist" @@ -202,9 +203,17 @@ func (ka *NSKeyedArchiver) convertValue(v interface{}) interface{} { values := m["NS.objects"].([]interface{}) for i := 0; i < len(keys); i++ { - key := ka.objRefVal[keys[i].(plist.UID)].(string) + var keyValue string + key := ka.objRefVal[keys[i].(plist.UID)] + switch key.(type) { + case uint64: + keyValue = strconv.Itoa(int(key.(uint64))) + break + default: + keyValue = key.(string) + } val := ka.convertValue(ka.objRefVal[values[i].(plist.UID)]) - ret[key] = val + ret[keyValue] = val } return ret case NSMutableArrayClass.Classes[0], NSArrayClass.Classes[0]: From aee45be4847dd764a6382ee2805e6cd3fe457bac Mon Sep 17 00:00:00 2001 From: debugtalk Date: Mon, 3 Oct 2022 16:18:38 +0800 Subject: [PATCH 02/20] feat: get performance data for cpu and memory --- device.go | 83 ++++++++--- idevice.go | 13 +- instruments.go | 163 ++++++++-------------- perfd.go | 368 +++++++++++++++++++++++++++++++++++++++++++++++++ perfd_test.go | 29 ++++ 5 files changed, 526 insertions(+), 130 deletions(-) create mode 100644 perfd.go create mode 100644 perfd_test.go diff --git a/device.go b/device.go index 5df66d9..315e5d3 100644 --- a/device.go +++ b/device.go @@ -5,15 +5,17 @@ import ( "context" "errors" "fmt" - "github.com/electricbubble/gidevice/pkg/ipa" - "github.com/electricbubble/gidevice/pkg/libimobiledevice" - "github.com/electricbubble/gidevice/pkg/nskeyedarchiver" - uuid "github.com/satori/go.uuid" - "howett.net/plist" "os" "path" "strings" "time" + + uuid "github.com/satori/go.uuid" + "howett.net/plist" + + "github.com/electricbubble/gidevice/pkg/ipa" + "github.com/electricbubble/gidevice/pkg/libimobiledevice" + "github.com/electricbubble/gidevice/pkg/nskeyedarchiver" ) const LockdownPort = 62078 @@ -46,6 +48,7 @@ type device struct { springBoard SpringBoard crashReportMover CrashReportMover pcapd Pcapd + perfd Perfd } func (d *device) Properties() DeviceProperties { @@ -569,6 +572,50 @@ func (d *device) PcapStop() { d.pcapd.Stop() } +func (d *device) getPidByBundleID(bundleID string) (pid int, err error) { + mapper := make(map[string]interface{}) + apps, err := d.AppList() + if err != nil { + fmt.Printf("get app list error: %v\n", err) + return -1, err + } + for _, app := range apps { + mapper[app.ExecutableName] = app.CFBundleIdentifier + } + + processes, err := d.AppRunningProcesses() + if err != nil { + fmt.Printf("get running app processes error: %v\n", err) + return -1, err + } + for _, proc := range processes { + b, ok := mapper[proc.Name] + if ok && bundleID == b { + fmt.Printf("get pid %d by bundleId %s\n", proc.Pid, bundleID) + return proc.Pid, nil + } + } + + fmt.Printf("can't find pid by bundleID: %s\n", bundleID) + return -1, fmt.Errorf("can't find pid by bundleID: %s", bundleID) +} + +func (d *device) PerfStart(opts ...PerfOption) (data <-chan []byte, err error) { + if _, err = d.instrumentsService(); err != nil { + return nil, err + } + + d.perfd = d.newPerfdClient(d.instruments, opts...) + return d.perfd.Start() +} + +func (d *device) PerfStop() { + if d.perfd == nil { + return + } + d.perfd.Stop() +} + func (d *device) crashReportMoverService() (crashReportMover CrashReportMover, err error) { if d.crashReportMover != nil { return d.crashReportMover, nil @@ -596,10 +643,10 @@ func (d *device) GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCance return nil, nil, err } } - if opts==nil { + if opts == nil { return nil, nil, fmt.Errorf("parameter is empty") } - if !opts.OpenChanCPU &&!opts.OpenChanMEM &&!opts.OpenChanGPU &&!opts.OpenChanFPS &&!opts.OpenChanNetWork { + if !opts.OpenChanCPU && !opts.OpenChanMEM && !opts.OpenChanGPU && !opts.OpenChanFPS && !opts.OpenChanNetWork { opts.OpenChanCPU = true opts.OpenChanMEM = true opts.OpenChanGPU = true @@ -612,8 +659,8 @@ func (d *device) GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCance var instruments Instruments ctx, cancel := context.WithCancel(context.Background()) - chanCPU := make(chan CPUInfo) - chanMEM := make(chan MEMInfo) + chanCPU := make(chan CPUData) + chanMEM := make(chan MemData) var cancelSysmontap context.CancelFunc if opts.OpenChanCPU || opts.OpenChanMEM { @@ -664,27 +711,27 @@ func (d *device) GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCance select { case v, ok := <-chanCPU: if opts.OpenChanCPU && ok { - result<-v + result <- v } case v, ok := <-chanMEM: if opts.OpenChanMEM && ok { - result<-v + result <- v } case v, ok := <-chanFPS: if opts.OpenChanFPS && ok { - result<-v + result <- v } case v, ok := <-chanGPU: if opts.OpenChanGPU && ok { - result<-v + result <- v } case v, ok := <-chanNetWork: if opts.OpenChanNetWork && ok { - result<-v + result <- v } case <-ctx.Done(): - err:=d.stopPerfmon(opts) - if err!=nil { + err := d.stopPerfmon(opts) + if err != nil { fmt.Println(err) } close(result) @@ -695,7 +742,7 @@ func (d *device) GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCance return result, cancel, err } -func (d *device)stopPerfmon(opts *PerfmonOption)(err error) { +func (d *device) stopPerfmon(opts *PerfmonOption) (err error) { if _, err = d.instrumentsService(); err != nil { return err } @@ -962,4 +1009,4 @@ func (d *device) _uploadXCTestConfiguration(bundleID string, sessionId uuid.UUID } return -} \ No newline at end of file +} diff --git a/idevice.go b/idevice.go index 93a2753..b05d2e3 100644 --- a/idevice.go +++ b/idevice.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "log" "time" "github.com/electricbubble/gidevice/pkg/libimobiledevice" @@ -79,6 +78,9 @@ type Device interface { GetInterfaceOrientation() (orientation OrientationState, err error) GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCancel context.CancelFunc, perfErr error) + + PerfStart(opts ...PerfOption) (data <-chan []byte, err error) + PerfStop() } type DeviceProperties = libimobiledevice.DeviceProperties @@ -160,7 +162,7 @@ type Instruments interface { StopOpenglServer() (err error) - StartSysmontapServer(pid string, ctx context.Context) (chanCPU chan CPUInfo, chanMem chan MEMInfo, cancel context.CancelFunc, err error) + StartSysmontapServer(pid string, ctx context.Context) (chanCPU chan CPUData, chanMem chan MemData, cancel context.CancelFunc, err error) StopSysmontapServer() (err error) //ProcessNetwork(pid int) (out <-chan interface{}, cancel context.CancelFunc, err error) @@ -232,6 +234,11 @@ type Pcapd interface { Stop() } +type Perfd interface { + Start() (data <-chan []byte, err error) + Stop() +} + type DiagnosticsRelay interface { Reboot() error Shutdown() error @@ -493,5 +500,5 @@ func debugLog(msg string) { if !debugFlag { return } - log.Println(fmt.Sprintf("[go-iDevice-debug] %s", msg)) + fmt.Printf("[go-iDevice-debug] %s\n", msg) } diff --git a/instruments.go b/instruments.go index fc4c2d6..02c10a7 100644 --- a/instruments.go +++ b/instruments.go @@ -9,6 +9,19 @@ import ( "github.com/electricbubble/gidevice/pkg/libimobiledevice" ) +// instruments services +const ( + instrumentsServiceDeviceInfo = "com.apple.instruments.server.services.deviceinfo" + instrumentsServiceProcessControl = "com.apple.instruments.server.services.processcontrol" + instrumentsServiceDeviceApplictionListing = "com.apple.instruments.server.services.device.applictionListing" + instrumentsServiceGraphicsOpengl = "com.apple.instruments.server.services.graphics.opengl" // 获取FPS + instrumentsServiceSysmontap = "com.apple.instruments.server.services.sysmontap" // 获取 CPU/Mem 性能数据 + instrumentsServiceXcodeNetworkStatistics = "com.apple.xcode.debug-gauge-data-providers.NetworkStatistics" // 获取单进程网络数据 + instrumentsServiceXcodeEnergyStatistics = "com.apple.xcode.debug-gauge-data-providers.Energy" // 获取功耗数据 + instrumentsServiceNetworking = "com.apple.instruments.server.services.networking" // 获取全局网络数据 + instrumentsServiceMobileNotifications = "com.apple.instruments.server.services.mobilenotifications" // 监控应用状态 +) + var _ Instruments = (*instruments)(nil) func newInstruments(client *libimobiledevice.InstrumentsClient) *instruments { @@ -44,7 +57,7 @@ func (i *instruments) AppLaunch(bundleID string, opts ...AppLaunchOption) (pid i } var id uint32 - if id, err = i.requestChannel("com.apple.instruments.server.services.processcontrol"); err != nil { + if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil { return 0, err } @@ -80,7 +93,7 @@ func (i *instruments) AppLaunch(bundleID string, opts ...AppLaunchOption) (pid i func (i *instruments) appProcess(bundleID string) (err error) { var id uint32 - if id, err = i.requestChannel("com.apple.instruments.server.services.processcontrol"); err != nil { + if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil { return err } @@ -99,7 +112,7 @@ func (i *instruments) appProcess(bundleID string) (err error) { func (i *instruments) startObserving(pid int) (err error) { var id uint32 - if id, err = i.requestChannel("com.apple.instruments.server.services.processcontrol"); err != nil { + if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil { return err } @@ -122,7 +135,7 @@ func (i *instruments) startObserving(pid int) (err error) { func (i *instruments) AppKill(pid int) (err error) { var id uint32 - if id, err = i.requestChannel("com.apple.instruments.server.services.processcontrol"); err != nil { + if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil { return err } @@ -141,7 +154,7 @@ func (i *instruments) AppKill(pid int) (err error) { func (i *instruments) AppRunningProcesses() (processes []Process, err error) { var id uint32 - if id, err = i.requestChannel("com.apple.instruments.server.services.deviceinfo"); err != nil { + if id, err = i.requestChannel(instrumentsServiceDeviceInfo); err != nil { return nil, err } @@ -190,7 +203,7 @@ func (i *instruments) AppList(opts ...AppListOption) (apps []Application, err er } var id uint32 - if id, err = i.requestChannel("com.apple.instruments.server.services.device.applictionListing"); err != nil { + if id, err = i.requestChannel(instrumentsServiceDeviceApplictionListing); err != nil { return nil, err } @@ -235,7 +248,7 @@ func (i *instruments) AppList(opts ...AppListOption) (apps []Application, err er func (i *instruments) DeviceInfo() (devInfo *DeviceInfo, err error) { var id uint32 - if id, err = i.requestChannel("com.apple.instruments.server.services.deviceinfo"); err != nil { + if id, err = i.requestChannel(instrumentsServiceDeviceInfo); err != nil { return nil, err } @@ -260,15 +273,17 @@ func (i *instruments) registerCallback(obj string, cb func(m libimobiledevice.DT i.client.RegisterCallback(obj, cb) } -func (i *instruments) StartSysmontapServer(pid string, ctxParent context.Context) (chanCPU chan CPUInfo, chanMem chan MEMInfo, cancel context.CancelFunc, err error) { +func (i *instruments) StartSysmontapServer(pid string, ctxParent context.Context) ( + chanCPU chan CPUData, chanMem chan MemData, cancel context.CancelFunc, err error) { + var id uint32 if ctxParent == nil { return nil, nil, nil, fmt.Errorf("missing context") } ctx, cancelFunc := context.WithCancel(ctxParent) - _outMEM := make(chan MEMInfo) - _outCPU := make(chan CPUInfo) - if id, err = i.requestChannel("com.apple.instruments.server.services.sysmontap"); err != nil { + _outMEM := make(chan MemData) + _outCPU := make(chan CPUData) + if id, err = i.requestChannel(instrumentsServiceSysmontap); err != nil { return nil, nil, cancelFunc, err } @@ -340,11 +355,11 @@ func (i *instruments) StartSysmontapServer(pid string, ctxParent context.Context return _outCPU, _outMEM, cancelFunc, err } -func chanCPUAndMEMData(mess interface{}, _outMEM chan MEMInfo, _outCPU chan CPUInfo, pid string) { +func chanCPUAndMEMData(mess interface{}, _outMEM chan MemData, _outCPU chan CPUData, pid string) { switch mess.(type) { case []interface{}: - var infoCPU CPUInfo - var infoMEM MEMInfo + var infoCPU CPUData + var infoMEM MemData messArray := mess.([]interface{}) if len(messArray) == 2 { var sinfo = messArray[0].(map[string]interface{}) @@ -360,27 +375,27 @@ func chanCPUAndMEMData(mess interface{}, _outMEM chan MEMInfo, _outCPU chan CPUI var cpuTotalLoad = sysCpuUsage["CPU_TotalLoad"] // 构建返回信息 infoCPU.CPUCount = int(cpuCount.(uint64)) - infoCPU.SysCpuUsage = cpuTotalLoad.(float64) + infoCPU.SysCPUUsageTotalLoad = cpuTotalLoad.(float64) //finalCpuInfo["attrCpuTotal"] = cpuTotalLoad var cpuUsage = 0.0 pidMess := pinfolist["Processes"].(map[string]interface{})[pid] if pidMess != nil { - processInfo := sysmonPorceAttrs(pidMess) + processInfo := convertProcessData(pidMess) cpuUsage = processInfo["cpuUsage"].(float64) - infoCPU.CPUUsage = cpuUsage - infoCPU.Pid = pid - infoCPU.AttrCtxSwitch = uIntToInt64(processInfo["ctxSwitch"]) - infoCPU.AttrIntWakeups = uIntToInt64(processInfo["intWakeups"]) + infoCPU.ProcCPUUsage = cpuUsage + infoCPU.ProcPID = pid + infoCPU.ProcAttrCtxSwitch = convert2Int64(processInfo["ctxSwitch"]) + infoCPU.ProcAttrIntWakeups = convert2Int64(processInfo["intWakeups"]) - infoMEM.Vss = uIntToInt64(processInfo["memVirtualSize"]) - infoMEM.Rss = uIntToInt64(processInfo["memResidentSize"]) - infoMEM.Anon = uIntToInt64(processInfo["memAnon"]) - infoMEM.PhysMemory = uIntToInt64(processInfo["physFootprint"]) + infoMEM.Vss = convert2Int64(processInfo["memVirtualSize"]) + infoMEM.Rss = convert2Int64(processInfo["memResidentSize"]) + infoMEM.Anon = convert2Int64(processInfo["memAnon"]) + infoMEM.PhysMemory = convert2Int64(processInfo["physFootprint"]) } else { - infoCPU.Mess = "invalid PID" - infoMEM.Mess = "invalid PID" + infoCPU.Msg = "invalid PID" + infoMEM.Msg = "invalid PID" infoMEM.Vss = -1 infoMEM.Rss = -1 @@ -398,42 +413,8 @@ func chanCPUAndMEMData(mess interface{}, _outMEM chan MEMInfo, _outCPU chan CPUI } } -// 获取进程相关信息 -func sysmonPorceAttrs(cpuMess interface{}) (outCpuInfo map[string]interface{}) { - if cpuMess == nil { - return nil - } - cpuMessArray, ok := cpuMess.([]interface{}) - if !ok { - return nil - } - if len(cpuMessArray) != 8 { - return nil - } - if outCpuInfo == nil { - outCpuInfo = map[string]interface{}{} - } - // 虚拟内存 - outCpuInfo["memVirtualSize"] = cpuMessArray[0] - // CPU - outCpuInfo["cpuUsage"] = cpuMessArray[1] - // 每秒进程的上下文切换次数 - outCpuInfo["ctxSwitch"] = cpuMessArray[2] - // 每秒进程唤醒的线程数 - outCpuInfo["intWakeups"] = cpuMessArray[3] - // 物理内存 - outCpuInfo["physFootprint"] = cpuMessArray[4] - - outCpuInfo["memResidentSize"] = cpuMessArray[5] - // 匿名内存 - outCpuInfo["memAnon"] = cpuMessArray[6] - - outCpuInfo["PID"] = cpuMessArray[7] - return -} - func (i *instruments) StopSysmontapServer() (err error) { - id, err := i.requestChannel("com.apple.instruments.server.services.sysmontap") + id, err := i.requestChannel(instrumentsServiceSysmontap) if err != nil { return err } @@ -454,7 +435,7 @@ func (i *instruments) StartNetWorkingServer(ctxParent context.Context) (chanNetW } ctx, cancelFunc := context.WithCancel(ctxParent) _outNetWork := make(chan NetWorkingInfo) - if id, err = i.requestChannel("com.apple.instruments.server.services.networking"); err != nil { + if id, err = i.requestChannel(instrumentsServiceNetworking); err != nil { return nil, cancelFunc, err } selector := "startMonitoring" @@ -474,10 +455,10 @@ func (i *instruments) StartNetWorkingServer(ctxParent context.Context) (chanNetW if ok { var netData NetWorkingInfo // 有时候是uint8,有时候是uint64。。。恶心 - netData.RxBytes = uIntToInt64(sendAndReceiveData[0]) - netData.RxPackets = uIntToInt64(sendAndReceiveData[1]) - netData.TxBytes = uIntToInt64(sendAndReceiveData[2]) - netData.TxPackets = uIntToInt64(sendAndReceiveData[3]) + netData.RxBytes = convert2Int64(sendAndReceiveData[0]) + netData.RxPackets = convert2Int64(sendAndReceiveData[1]) + netData.TxBytes = convert2Int64(sendAndReceiveData[2]) + netData.TxPackets = convert2Int64(sendAndReceiveData[3]) netData.TimeStamp = time.Now().UnixNano() _outNetWork <- netData } @@ -500,25 +481,9 @@ func (i *instruments) StartNetWorkingServer(ctxParent context.Context) (chanNetW return _outNetWork, cancelFunc, err } -func uIntToInt64(num interface{}) (cnum int64) { - switch num.(type) { - case uint64: - return int64(num.(uint64)) - case uint32: - return int64(num.(uint32)) - case uint16: - return int64(num.(uint16)) - case uint8: - return int64(num.(uint8)) - case uint: - return int64(num.(uint)) - } - return -1 -} - func (i *instruments) StopNetWorkingServer() (err error) { var id uint32 - id, err = i.requestChannel("com.apple.instruments.server.services.networking") + id, err = i.requestChannel(instrumentsServiceNetworking) if err != nil { return err } @@ -539,7 +504,7 @@ func (i *instruments) StartOpenglServer(ctxParent context.Context) (chanFPS chan ctx, cancelFunc := context.WithCancel(ctxParent) _outFPS := make(chan FPSInfo) _outGPU := make(chan GPUInfo) - if id, err = i.requestChannel("com.apple.instruments.server.services.graphics.opengl"); err != nil { + if id, err = i.requestChannel(instrumentsServiceGraphicsOpengl); err != nil { return nil, nil, cancelFunc, err } @@ -582,15 +547,15 @@ func (i *instruments) StartOpenglServer(ctxParent context.Context) (chanFPS chan var infoGPU GPUInfo - infoGPU.DeviceUtilization = uIntToInt64(deviceUtilization) - infoGPU.TilerUtilization = uIntToInt64(tilerUtilization) - infoGPU.RendererUtilization = uIntToInt64(rendererUtilization) + infoGPU.DeviceUtilization = convert2Int64(deviceUtilization) + infoGPU.TilerUtilization = convert2Int64(tilerUtilization) + infoGPU.RendererUtilization = convert2Int64(rendererUtilization) infoGPU.TimeStamp = time.Now().UnixNano() _outGPU <- infoGPU var infoFPS FPSInfo var fps = mess.(map[string]interface{})["CoreAnimationFramesPerSecond"] - infoFPS.FPS = int(uIntToInt64(fps)) + infoFPS.FPS = int(convert2Int64(fps)) infoFPS.TimeStamp = time.Now().UnixNano() _outFPS <- infoFPS } @@ -622,7 +587,7 @@ func (i *instruments) StartOpenglServer(ctxParent context.Context) (chanFPS chan func (i *instruments) StopOpenglServer() (err error) { - id, err := i.requestChannel("com.apple.instruments.server.services.graphics.opengl") + id, err := i.requestChannel(instrumentsServiceGraphicsOpengl) if err != nil { return err } @@ -661,17 +626,6 @@ type DeviceInfo struct { XRDeviceClassName string `json:"_xrdeviceClassName"` } -type CPUInfo struct { - Pid string `json:"PID"` // 线程 - CPUCount int `json:"cpuCount"` // CPU总数 - TimeStamp int64 `json:"timeStamp"` // 时间戳 - CPUUsage float64 `json:"cpuUsage,omitempty"` // 单个进程的CPU使用率 - SysCpuUsage float64 `json:"sysCpuUsage,omitempty"` // 系统总体CPU占用 - AttrCtxSwitch int64 `json:"attrCtxSwitch,omitempty"` // 上下文切换数 - AttrIntWakeups int64 `json:"attrIntWakeups,omitempty"` // 唤醒数 - Mess string `json:"mess,omitempty"` // 提示信息,当PID没输入或者信息错误时提示 -} - type FPSInfo struct { FPS int `json:"fps"` TimeStamp int64 `json:"timeStamp"` @@ -685,15 +639,6 @@ type GPUInfo struct { RendererUtilization int64 `json:"rendererUtilization"` // 渲染器利用率 } -type MEMInfo struct { - Anon int64 `json:"anon"` // 虚拟内存 - PhysMemory int64 `json:"physMemory"` // 物理内存 - Rss int64 `json:"rss"` // 总内存 - Vss int64 `json:"vss"` // 虚拟内存 - TimeStamp int64 `json:"timeStamp"` // - Mess string `json:"mess,omitempty"` // 提示信息,当PID没输入或者信息错误时提示 -} - type NetWorkingInfo struct { RxBytes int64 `json:"rxBytes"` RxPackets int64 `json:"rxPackets"` diff --git a/perfd.go b/perfd.go new file mode 100644 index 0000000..12f2663 --- /dev/null +++ b/perfd.go @@ -0,0 +1,368 @@ +package giDevice + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/electricbubble/gidevice/pkg/libimobiledevice" +) + +type perfOption struct { + bundleID string + pid string + gpu bool + cpu bool + mem bool + fps bool + network bool +} + +func defaulPerfOption() *perfOption { + return &perfOption{ + gpu: false, + cpu: true, // default on + mem: true, // default on + fps: false, + network: false, + } +} + +type PerfOption func(*perfOption) + +func WithPerfBundleID(bundleID string) PerfOption { + return func(opt *perfOption) { + opt.bundleID = bundleID + } +} + +func WithPerfPID(pid string) PerfOption { + return func(opt *perfOption) { + opt.pid = pid + } +} + +func WithPerfGPU(b bool) PerfOption { + return func(opt *perfOption) { + opt.gpu = b + } +} + +func WithPerfCPU(b bool) PerfOption { + return func(opt *perfOption) { + opt.cpu = b + } +} + +func WithPerfMem(b bool) PerfOption { + return func(opt *perfOption) { + opt.mem = b + } +} + +func WithPerfFPS(b bool) PerfOption { + return func(opt *perfOption) { + opt.fps = b + } +} + +func WithPerfNetwork(b bool) PerfOption { + return func(opt *perfOption) { + opt.network = b + } +} + +type perfdClient struct { + option *perfOption + i *instruments + stop chan struct{} // used to stop perf client + cancels []context.CancelFunc // used to cancel all iterators + chanCPU chan []byte // cpu channel + chanMem chan []byte // mem channel +} + +func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient { + perfOption := defaulPerfOption() + for _, fn := range opts { + fn(perfOption) + } + + if perfOption.bundleID != "" { + pid, err := d.getPidByBundleID(perfOption.bundleID) + if err == nil { + perfOption.pid = strconv.Itoa(pid) + } + } + + return &perfdClient{ + i: i.(*instruments), + option: perfOption, + chanCPU: make(chan []byte, 10), + chanMem: make(chan []byte, 10), + } +} + +func (c *perfdClient) Start() (data <-chan []byte, err error) { + outCh := make(chan []byte, 100) + + if c.option.cpu || c.option.mem { + cancel, err := c.iterCPUMem(c.option.pid, context.Background()) + if err != nil { + return nil, err + } + c.cancels = append(c.cancels, cancel) + } + + go func() { + for { + select { + case <-c.stop: + return + case cpuBytes, ok := <-c.chanCPU: + if ok && c.option.cpu { + fmt.Println("cpu: ", string(cpuBytes)) + outCh <- cpuBytes + } + case memBytes, ok := <-c.chanMem: + if ok && c.option.mem { + fmt.Println("mem: ", string(memBytes)) + outCh <- memBytes + } + } + } + }() + + return outCh, nil +} + +func (c *perfdClient) Stop() { + close(c.stop) + for _, cancel := range c.cancels { + cancel() + } +} + +func (c *perfdClient) iterCPUMem(pid string, ctx context.Context) ( + cancel context.CancelFunc, err error) { + + chanID, err := c.i.requestChannel(instrumentsServiceSysmontap) + if err != nil { + return nil, err + } + + // set config + args := libimobiledevice.NewAuxBuffer() + config := map[string]interface{}{ + "bm": 0, + "cpuUsage": true, + "sampleInterval": time.Second * 1, // 1s + "ur": 1000, // 刷新频率 + "procAttrs": []string{ + "memVirtualSize", // vss + "cpuUsage", + "ctxSwitch", // the number of context switches by process each second + "intWakeups", // the number of threads wakeups by process each second + "physFootprint", // real memory (物理内存) + "memResidentSize", // rss + "memAnon", // anonymous memory + "pid", + }, + "sysAttrs": []string{ // 系统信息字段 + "vmExtPageCount", + "vmFreeCount", + "vmPurgeableCount", + "vmSpeculativeCount", + "physMemSize", + }, + } + args.AppendObject(config) + if _, err = c.i.client.Invoke("setConfig:", args, chanID, true); err != nil { + return nil, err + } + + // start + args = libimobiledevice.NewAuxBuffer() + if _, err = c.i.client.Invoke("start", args, chanID, true); err != nil { + return nil, err + } + + // register listener + ctx, cancel = context.WithCancel(ctx) + c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { + select { + case <-ctx.Done(): + return + default: + c.convertCPUMem(m.Obj, pid) + } + }) + + return cancel, err +} + +func (c *perfdClient) convertCPUMem(data interface{}, pid string) { + messArray, ok := data.([]interface{}) + if !ok || len(messArray) != 2 { + return + } + + var systemInfo = messArray[0].(map[string]interface{}) + var processInfoList = messArray[1].(map[string]interface{}) + if systemInfo["CPUCount"] == nil { + systemInfo, processInfoList = processInfoList, systemInfo + } + if systemInfo["CPUCount"] == nil { + fmt.Printf("invalid system info: %v\n", systemInfo) + return + } + // systemInfo example: + // map[ + // CPUCount:2 + // EnabledCPUs:2 + // PerCPUUsage:[ + // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:4.587155963302749 CPU_UserLoad:-1] + // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:0.9174311926605441 CPU_UserLoad:-1] + // ] + // System:[70117 2850 465 1579 128643] + // SystemCPUUsage:map[ + // CPU_NiceLoad:0 + // CPU_SystemLoad:-1 + // CPU_TotalLoad:5.504587155963293 + // CPU_UserLoad:-1 + // ] + // StartMachAbsTime:2514085834016 + // EndMachAbsTime:2514111855034 + // Type:41 + // ] + var cpuCount = systemInfo["CPUCount"] + var sysCpuUsage = systemInfo["SystemCPUUsage"].(map[string]interface{}) + + if processInfoList["Processes"] == nil { + fmt.Printf("invalid process info list: %v\n", processInfoList) + return + } + // processInfoList example: + // map[ + // Processes:map[ + // 0:[108940918784 0.35006396059439243 11867680 6069179 147456 294600704 167346176 0] + // 100:[417741438976 0 65418 21019 1819088 7045120 1671168 100] + // 107:[417746960384 0.06996075980775063 71187 21226 3342800 9420800 3178496 107] + // ] + // StartMachAbsTime:2514086593642 + // EndMachAbsTime:2514112708690 + // Type:5 + // ] + processes := processInfoList["Processes"].(map[string]interface{}) + + cpuInfo := CPUData{ + Type: "cpu", + TimeStamp: time.Now().Unix(), + CPUCount: int(cpuCount.(uint64)), + SysCPUUsageTotalLoad: sysCpuUsage["CPU_TotalLoad"].(float64), + ProcPID: pid, + } + memInfo := MemData{ + Type: "mem", + TimeStamp: time.Now().Unix(), + } + + procData, ok := processes[pid] + processInfo := convertProcessData(procData) + if ok && processInfo != nil { + // cpu + cpuInfo.ProcCPUUsage = processInfo["cpuUsage"].(float64) + cpuInfo.ProcAttrCtxSwitch = convert2Int64(processInfo["ctxSwitch"]) + cpuInfo.ProcAttrIntWakeups = convert2Int64(processInfo["intWakeups"]) + // mem + memInfo.Vss = convert2Int64(processInfo["memVirtualSize"]) + memInfo.Rss = convert2Int64(processInfo["memResidentSize"]) + memInfo.Anon = convert2Int64(processInfo["memAnon"]) + memInfo.PhysMemory = convert2Int64(processInfo["physFootprint"]) + } else { + // cpu + cpuInfo.Msg = "invalid PID" + // mem + memInfo.Msg = "invalid PID" + memInfo.Vss = -1 + memInfo.Rss = -1 + memInfo.Anon = -1 + memInfo.PhysMemory = -1 + } + + cpuBytes, _ := json.Marshal(cpuInfo) + memBytes, _ := json.Marshal(memInfo) + c.chanCPU <- cpuBytes + c.chanMem <- memBytes +} + +type CPUData struct { + Type string `json:"type"` // cpu + TimeStamp int64 `json:"timestamp"` + Msg string `json:"msg,omitempty"` // 提示信息 + // system + CPUCount int `json:"cpuCount"` // CPU总数 + SysCPUUsageTotalLoad float64 `json:"sysCpuUsageTotalLoad"` // 系统总体CPU占用 + // process + ProcPID string `json:"procPID"` // 进程 PID + ProcCPUUsage float64 `json:"procCpuUsage,omitempty"` // 单个进程的CPU使用率 + ProcAttrCtxSwitch int64 `json:"procAttrCtxSwitch,omitempty"` // 上下文切换数 + ProcAttrIntWakeups int64 `json:"procAttrIntWakeups,omitempty"` // 唤醒数 +} + +type MemData struct { + Type string `json:"type"` // mem + TimeStamp int64 `json:"timestamp"` + Anon int64 `json:"anon"` // 虚拟内存 + PhysMemory int64 `json:"physMemory"` // 物理内存 + Rss int64 `json:"rss"` // 总内存 + Vss int64 `json:"vss"` // 虚拟内存 + Msg string `json:"msg,omitempty"` // 提示信息 +} + +func convertProcessData(procData interface{}) map[string]interface{} { + if procData == nil { + return nil + } + procDataArray, ok := procData.([]interface{}) + if !ok { + return nil + } + if len(procDataArray) != 8 { + return nil + } + + // procDataArray example: + // [417741438976 0 65418 21019 1819088 7045120 1671168 100] + // corresponds to procAttrs: + // ["memVirtualSize", "cpuUsage", "ctxSwitch", "intWakeups", + // "physFootprint", "memResidentSize", "memAnon", "pid"] + return map[string]interface{}{ + "memVirtualSize": procDataArray[0], + "cpuUsage": procDataArray[1], + "ctxSwitch": procDataArray[2], + "intWakeups": procDataArray[3], + "physFootprint": procDataArray[4], + "memResidentSize": procDataArray[5], + "memAnon": procDataArray[6], + "PID": procDataArray[7], + } +} + +func convert2Int64(num interface{}) int64 { + switch value := num.(type) { + case uint64: + return int64(value) + case uint32: + return int64(value) + case uint16: + return int64(value) + case uint8: + return int64(value) + case uint: + return int64(value) + } + fmt.Printf("convert2Int64 failed: %+v", num) + return -1 +} diff --git a/perfd_test.go b/perfd_test.go new file mode 100644 index 0000000..819330e --- /dev/null +++ b/perfd_test.go @@ -0,0 +1,29 @@ +package giDevice + +import ( + "testing" + "time" +) + +func TestPerf(t *testing.T) { + setupLockdownSrv(t) + + data, err := dev.PerfStart( + WithPerfCPU(true), + WithPerfMem(true), + WithPerfBundleID("com.apple.mobilesafari"), + ) + if err != nil { + t.Fatal(err) + } + + timer := time.NewTimer(time.Duration(time.Second * 10)) + for { + select { + case <-timer.C: + return + case d := <-data: + t.Log(string(d)) + } + } +} From a9a8fdcb7436831a1718fc186e2f5c4a64e9a60d Mon Sep 17 00:00:00 2001 From: debugtalk Date: Mon, 3 Oct 2022 22:50:48 +0800 Subject: [PATCH 03/20] change: rename functions --- instruments.go | 15 +++++++++------ perfd.go | 10 ++++++---- perfd_test.go | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/instruments.go b/instruments.go index 02c10a7..dd0f5d6 100644 --- a/instruments.go +++ b/instruments.go @@ -14,12 +14,15 @@ const ( instrumentsServiceDeviceInfo = "com.apple.instruments.server.services.deviceinfo" instrumentsServiceProcessControl = "com.apple.instruments.server.services.processcontrol" instrumentsServiceDeviceApplictionListing = "com.apple.instruments.server.services.device.applictionListing" - instrumentsServiceGraphicsOpengl = "com.apple.instruments.server.services.graphics.opengl" // 获取FPS - instrumentsServiceSysmontap = "com.apple.instruments.server.services.sysmontap" // 获取 CPU/Mem 性能数据 - instrumentsServiceXcodeNetworkStatistics = "com.apple.xcode.debug-gauge-data-providers.NetworkStatistics" // 获取单进程网络数据 - instrumentsServiceXcodeEnergyStatistics = "com.apple.xcode.debug-gauge-data-providers.Energy" // 获取功耗数据 - instrumentsServiceNetworking = "com.apple.instruments.server.services.networking" // 获取全局网络数据 - instrumentsServiceMobileNotifications = "com.apple.instruments.server.services.mobilenotifications" // 监控应用状态 + instrumentsServiceGraphicsOpengl = "com.apple.instruments.server.services.graphics.opengl" // 获取FPS + instrumentsServiceSysmontap = "com.apple.instruments.server.services.sysmontap" // 获取 CPU/Mem 性能数据 + instrumentsServiceNetworking = "com.apple.instruments.server.services.networking" // 获取全局网络数据 + instrumentsServiceMobileNotifications = "com.apple.instruments.server.services.mobilenotifications" // 监控应用状态 +) + +const ( + instrumentsServiceXcodeNetworkStatistics = "com.apple.xcode.debug-gauge-data-providers.NetworkStatistics" // 获取单进程网络数据 + instrumentsServiceXcodeEnergyStatistics = "com.apple.xcode.debug-gauge-data-providers.Energy" // 获取功耗数据 ) var _ Instruments = (*instruments)(nil) diff --git a/perfd.go b/perfd.go index 12f2663..6635498 100644 --- a/perfd.go +++ b/perfd.go @@ -108,7 +108,7 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { outCh := make(chan []byte, 100) if c.option.cpu || c.option.mem { - cancel, err := c.iterCPUMem(c.option.pid, context.Background()) + cancel, err := c.registerCPUMem(c.option.pid, context.Background()) if err != nil { return nil, err } @@ -144,7 +144,7 @@ func (c *perfdClient) Stop() { } } -func (c *perfdClient) iterCPUMem(pid string, ctx context.Context) ( +func (c *perfdClient) registerCPUMem(pid string, ctx context.Context) ( cancel context.CancelFunc, err error) { chanID, err := c.i.requestChannel(instrumentsServiceSysmontap) @@ -195,14 +195,14 @@ func (c *perfdClient) iterCPUMem(pid string, ctx context.Context) ( case <-ctx.Done(): return default: - c.convertCPUMem(m.Obj, pid) + c.parseCPUMem(m.Obj, pid) } }) return cancel, err } -func (c *perfdClient) convertCPUMem(data interface{}, pid string) { +func (c *perfdClient) parseCPUMem(data interface{}, pid string) { messArray, ok := data.([]interface{}) if !ok || len(messArray) != 2 { return @@ -266,6 +266,7 @@ func (c *perfdClient) convertCPUMem(data interface{}, pid string) { memInfo := MemData{ Type: "mem", TimeStamp: time.Now().Unix(), + ProcPID: pid, } procData, ok := processes[pid] @@ -318,6 +319,7 @@ type MemData struct { PhysMemory int64 `json:"physMemory"` // 物理内存 Rss int64 `json:"rss"` // 总内存 Vss int64 `json:"vss"` // 虚拟内存 + ProcPID string `json:"procPID"` // 进程 PID Msg string `json:"msg,omitempty"` // 提示信息 } diff --git a/perfd_test.go b/perfd_test.go index 819330e..c899695 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -11,7 +11,7 @@ func TestPerf(t *testing.T) { data, err := dev.PerfStart( WithPerfCPU(true), WithPerfMem(true), - WithPerfBundleID("com.apple.mobilesafari"), + WithPerfBundleID("com.apple.Spotlight"), ) if err != nil { t.Fatal(err) From 0b788b858a3845f25539c2ccd7427ab17d1be68c Mon Sep 17 00:00:00 2001 From: debugtalk Date: Tue, 4 Oct 2022 08:10:12 +0800 Subject: [PATCH 04/20] feat: get performance data for gpu and fps --- device.go | 4 +- idevice.go | 2 +- instruments.go | 23 ++------- perfd.go | 127 ++++++++++++++++++++++++++++++++++++++++++++++++- perfd_test.go | 48 ++++++++++++++++++- 5 files changed, 180 insertions(+), 24 deletions(-) diff --git a/device.go b/device.go index 315e5d3..f48f92d 100644 --- a/device.go +++ b/device.go @@ -675,8 +675,8 @@ func (d *device) GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCance } } - chanFPS := make(chan FPSInfo) - chanGPU := make(chan GPUInfo) + chanFPS := make(chan FPSData) + chanGPU := make(chan GPUData) var cancelOpengl context.CancelFunc if opts.OpenChanGPU || opts.OpenChanFPS { diff --git a/idevice.go b/idevice.go index b05d2e3..01bdd2d 100644 --- a/idevice.go +++ b/idevice.go @@ -158,7 +158,7 @@ type Instruments interface { registerCallback(obj string, cb func(m libimobiledevice.DTXMessageResult)) - StartOpenglServer(ctx context.Context) (chanFPS chan FPSInfo, chanGPU chan GPUInfo, cancel context.CancelFunc, err error) + StartOpenglServer(ctx context.Context) (chanFPS chan FPSData, chanGPU chan GPUData, cancel context.CancelFunc, err error) StopOpenglServer() (err error) diff --git a/instruments.go b/instruments.go index dd0f5d6..516eb85 100644 --- a/instruments.go +++ b/instruments.go @@ -499,14 +499,14 @@ func (i *instruments) StopNetWorkingServer() (err error) { return nil } -func (i *instruments) StartOpenglServer(ctxParent context.Context) (chanFPS chan FPSInfo, chanGPU chan GPUInfo, cancel context.CancelFunc, err error) { +func (i *instruments) StartOpenglServer(ctxParent context.Context) (chanFPS chan FPSData, chanGPU chan GPUData, cancel context.CancelFunc, err error) { var id uint32 if ctxParent == nil { return nil, nil, nil, fmt.Errorf("missing context") } ctx, cancelFunc := context.WithCancel(ctxParent) - _outFPS := make(chan FPSInfo) - _outGPU := make(chan GPUInfo) + _outFPS := make(chan FPSData) + _outGPU := make(chan GPUData) if id, err = i.requestChannel(instrumentsServiceGraphicsOpengl); err != nil { return nil, nil, cancelFunc, err } @@ -548,7 +548,7 @@ func (i *instruments) StartOpenglServer(ctxParent context.Context) (chanFPS chan var tilerUtilization = mess.(map[string]interface{})["Tiler Utilization %"] // Tiler Utilization var rendererUtilization = mess.(map[string]interface{})["Renderer Utilization %"] // Renderer Utilization - var infoGPU GPUInfo + var infoGPU GPUData infoGPU.DeviceUtilization = convert2Int64(deviceUtilization) infoGPU.TilerUtilization = convert2Int64(tilerUtilization) @@ -556,7 +556,7 @@ func (i *instruments) StartOpenglServer(ctxParent context.Context) (chanFPS chan infoGPU.TimeStamp = time.Now().UnixNano() _outGPU <- infoGPU - var infoFPS FPSInfo + var infoFPS FPSData var fps = mess.(map[string]interface{})["CoreAnimationFramesPerSecond"] infoFPS.FPS = int(convert2Int64(fps)) infoFPS.TimeStamp = time.Now().UnixNano() @@ -629,19 +629,6 @@ type DeviceInfo struct { XRDeviceClassName string `json:"_xrdeviceClassName"` } -type FPSInfo struct { - FPS int `json:"fps"` - TimeStamp int64 `json:"timeStamp"` -} - -type GPUInfo struct { - TilerUtilization int64 `json:"tilerUtilization"` // 处理顶点的GPU时间占比 - TimeStamp int64 `json:"timeStamp"` - Mess string `json:"mess,omitempty"` // 提示信息,当PID没输入时提示 - DeviceUtilization int64 `json:"deviceUtilization"` // 设备利用率 - RendererUtilization int64 `json:"rendererUtilization"` // 渲染器利用率 -} - type NetWorkingInfo struct { RxBytes int64 `json:"rxBytes"` RxPackets int64 `json:"rxPackets"` diff --git a/perfd.go b/perfd.go index 6635498..a253093 100644 --- a/perfd.go +++ b/perfd.go @@ -81,6 +81,8 @@ type perfdClient struct { cancels []context.CancelFunc // used to cancel all iterators chanCPU chan []byte // cpu channel chanMem chan []byte // mem channel + chanGPU chan []byte // gpu channel + chanFPS chan []byte // fps channel } func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient { @@ -101,6 +103,8 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient option: perfOption, chanCPU: make(chan []byte, 10), chanMem: make(chan []byte, 10), + chanGPU: make(chan []byte, 10), + chanFPS: make(chan []byte, 10), } } @@ -108,7 +112,15 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { outCh := make(chan []byte, 100) if c.option.cpu || c.option.mem { - cancel, err := c.registerCPUMem(c.option.pid, context.Background()) + cancel, err := c.registerSysmontap(c.option.pid, context.Background()) + if err != nil { + return nil, err + } + c.cancels = append(c.cancels, cancel) + } + + if c.option.gpu || c.option.fps { + cancel, err := c.registerGraphicsOpengl(context.Background()) if err != nil { return nil, err } @@ -130,6 +142,16 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { fmt.Println("mem: ", string(memBytes)) outCh <- memBytes } + case gpuBytes, ok := <-c.chanGPU: + if ok && c.option.gpu { + fmt.Println("gpu: ", string(gpuBytes)) + outCh <- gpuBytes + } + case fpsBytes, ok := <-c.chanFPS: + if ok && c.option.fps { + fmt.Println("fps: ", string(fpsBytes)) + outCh <- fpsBytes + } } } }() @@ -144,7 +166,108 @@ func (c *perfdClient) Stop() { } } -func (c *perfdClient) registerCPUMem(pid string, ctx context.Context) ( +func (c *perfdClient) registerGraphicsOpengl(ctx context.Context) ( + cancel context.CancelFunc, err error) { + + chanID, err := c.i.requestChannel(instrumentsServiceGraphicsOpengl) + if err != nil { + return nil, err + } + + selector := "availableStatistics" + args := libimobiledevice.NewAuxBuffer() + if _, err = c.i.client.Invoke(selector, args, chanID, true); err != nil { + return nil, err + } + + selector = "setSamplingRate:" + if err = args.AppendObject(0.0); err != nil { + return nil, err + } + if _, err = c.i.client.Invoke(selector, args, chanID, true); err != nil { + return nil, err + } + + selector = "startSamplingAtTimeInterval:processIdentifier:" + args = libimobiledevice.NewAuxBuffer() + if err = args.AppendObject(0); err != nil { + return nil, err + } + if err = args.AppendObject(0); err != nil { + return nil, err + } + if _, err = c.i.client.Invoke(selector, args, chanID, true); err != nil { + return nil, err + } + + c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { + select { + case <-ctx.Done(): + return + default: + c.parseGpuFps(m.Obj) + } + }) + + return +} + +func (c *perfdClient) parseGpuFps(data interface{}) { + // data example: + // map[ + // Alloc system memory:50167808 + // Allocated PB Size:1179648 + // CoreAnimationFramesPerSecond:0 + // Device Utilization %:0 + // IOGLBundleName:Built-In + // In use system memory:10633216 + // Renderer Utilization %:0 + // SplitSceneCount:0 + // TiledSceneBytes:0 + // Tiler Utilization %:0 + // XRVideoCardRunTimeStamp:1010679 + // recoveryCount:0 + // ] + var deviceUtilization = data.(map[string]interface{})["Device Utilization %"] // Device Utilization + var tilerUtilization = data.(map[string]interface{})["Tiler Utilization %"] // Tiler Utilization + var rendererUtilization = data.(map[string]interface{})["Renderer Utilization %"] // Renderer Utilization + + gpuInfo := GPUData{ + Type: "gpu", + TimeStamp: time.Now().Unix(), + DeviceUtilization: convert2Int64(deviceUtilization), + TilerUtilization: convert2Int64(tilerUtilization), + RendererUtilization: convert2Int64(rendererUtilization), + } + gpuBytes, _ := json.Marshal(gpuInfo) + c.chanGPU <- gpuBytes + + var fps = data.(map[string]interface{})["CoreAnimationFramesPerSecond"] + fpsInfo := FPSData{ + Type: "fps", + TimeStamp: time.Now().Unix(), + FPS: int(convert2Int64(fps)), + } + fpsBytes, _ := json.Marshal(fpsInfo) + c.chanFPS <- fpsBytes +} + +type GPUData struct { + Type string `json:"type"` // gpu + TimeStamp int64 `json:"timeStamp"` + TilerUtilization int64 `json:"tilerUtilization"` // 处理顶点的GPU时间占比 + DeviceUtilization int64 `json:"deviceUtilization"` // 设备利用率 + RendererUtilization int64 `json:"rendererUtilization"` // 渲染器利用率 + Msg string `json:"msg,omitempty"` // 提示信息 +} + +type FPSData struct { + Type string `json:"type"` // fps + TimeStamp int64 `json:"timeStamp"` + FPS int `json:"fps"` +} + +func (c *perfdClient) registerSysmontap(pid string, ctx context.Context) ( cancel context.CancelFunc, err error) { chanID, err := c.i.requestChannel(instrumentsServiceSysmontap) diff --git a/perfd_test.go b/perfd_test.go index c899695..dd3b830 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -5,7 +5,7 @@ import ( "time" ) -func TestPerf(t *testing.T) { +func TestPerfCPUMem(t *testing.T) { setupLockdownSrv(t) data, err := dev.PerfStart( @@ -27,3 +27,49 @@ func TestPerf(t *testing.T) { } } } + +func TestPerfGPU(t *testing.T) { + setupLockdownSrv(t) + + data, err := dev.PerfStart( + WithPerfCPU(false), + WithPerfMem(false), + WithPerfGPU(true), + ) + if err != nil { + t.Fatal(err) + } + + timer := time.NewTimer(time.Duration(time.Second * 10)) + for { + select { + case <-timer.C: + return + case d := <-data: + t.Log(string(d)) + } + } +} + +func TestPerfFPS(t *testing.T) { + setupLockdownSrv(t) + + data, err := dev.PerfStart( + WithPerfCPU(false), + WithPerfMem(false), + WithPerfFPS(true), + ) + if err != nil { + t.Fatal(err) + } + + timer := time.NewTimer(time.Duration(time.Second * 10)) + for { + select { + case <-timer.C: + return + case d := <-data: + t.Log(string(d)) + } + } +} From 8afaf3697c2ef8494eb17c47d3bb711a18d57466 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Tue, 4 Oct 2022 09:24:47 +0800 Subject: [PATCH 05/20] feat: get performance data for networking --- device.go | 2 +- device_test.go | 14 +++--- idevice.go | 2 +- instruments.go | 14 ++---- perfd.go | 122 ++++++++++++++++++++++++++++++++++++++++++------- perfd_test.go | 23 ++++++++++ 6 files changed, 140 insertions(+), 37 deletions(-) diff --git a/device.go b/device.go index f48f92d..91aca98 100644 --- a/device.go +++ b/device.go @@ -691,7 +691,7 @@ func (d *device) GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCance } } - chanNetWork := make(chan NetWorkingInfo) + chanNetWork := make(chan NetworkData) var cancelNetWork context.CancelFunc if opts.OpenChanNetWork { instruments, err = d.lockdown.InstrumentsService() diff --git a/device_test.go b/device_test.go index 45bebff..716a4de 100644 --- a/device_test.go +++ b/device_test.go @@ -165,9 +165,9 @@ func Test_device_Perf(t *testing.T) { var opts = &PerfmonOption{ //PID: "0", - //OpenChanNetWork: true, - //OpenChanFPS: true, - OpenChanMEM: true, + OpenChanNetWork: true, + // OpenChanFPS: true, + // OpenChanMEM: true, //OpenChanCPU: true, //OpenChanGPU: true, } @@ -178,21 +178,21 @@ func Test_device_Perf(t *testing.T) { os.Exit(0) } var timeLast = time.Now().Unix() - for { + for { select { case <-c: - if cannel!=nil { + if cannel != nil { cannel() } default: - data ,ok := <-outData + data, ok := <-outData if !ok { fmt.Println("end get perfmon data") return } result, _ := json.MarshalIndent(data, "", "\t") fmt.Println(string(result)) - if time.Now().Unix()-timeLast >60 { + if time.Now().Unix()-timeLast > 60 { cannel() } } diff --git a/idevice.go b/idevice.go index 01bdd2d..286baf5 100644 --- a/idevice.go +++ b/idevice.go @@ -167,7 +167,7 @@ type Instruments interface { StopSysmontapServer() (err error) //ProcessNetwork(pid int) (out <-chan interface{}, cancel context.CancelFunc, err error) - StartNetWorkingServer(ctx context.Context) (chanNetWorking chan NetWorkingInfo, cancel context.CancelFunc, err error) + StartNetWorkingServer(ctx context.Context) (chanNetWorking chan NetworkData, cancel context.CancelFunc, err error) StopNetWorkingServer() (err error) } diff --git a/instruments.go b/instruments.go index 516eb85..d194dbc 100644 --- a/instruments.go +++ b/instruments.go @@ -431,13 +431,13 @@ func (i *instruments) StopSysmontapServer() (err error) { // todo 获取单进程流量情况,看情况做不做 // 目前只获取到系统全局的流量情况,单进程需要用到set,go没有,并且实际用python测试单进程的流量情况不准 -func (i *instruments) StartNetWorkingServer(ctxParent context.Context) (chanNetWorking chan NetWorkingInfo, cancel context.CancelFunc, err error) { +func (i *instruments) StartNetWorkingServer(ctxParent context.Context) (chanNetWorking chan NetworkData, cancel context.CancelFunc, err error) { var id uint32 if ctxParent == nil { return nil, nil, fmt.Errorf("missing context") } ctx, cancelFunc := context.WithCancel(ctxParent) - _outNetWork := make(chan NetWorkingInfo) + _outNetWork := make(chan NetworkData) if id, err = i.requestChannel(instrumentsServiceNetworking); err != nil { return nil, cancelFunc, err } @@ -456,7 +456,7 @@ func (i *instruments) StartNetWorkingServer(ctxParent context.Context) (chanNetW if ok && len(receData) == 2 { sendAndReceiveData, ok := receData[1].([]interface{}) if ok { - var netData NetWorkingInfo + var netData NetworkData // 有时候是uint8,有时候是uint64。。。恶心 netData.RxBytes = convert2Int64(sendAndReceiveData[0]) netData.RxPackets = convert2Int64(sendAndReceiveData[1]) @@ -628,11 +628,3 @@ type DeviceInfo struct { ProductVersion string `json:"_productVersion"` XRDeviceClassName string `json:"_xrdeviceClassName"` } - -type NetWorkingInfo struct { - RxBytes int64 `json:"rxBytes"` - RxPackets int64 `json:"rxPackets"` - TxBytes int64 `json:"txBytes"` - TxPackets int64 `json:"txPackets"` - TimeStamp int64 `json:"timeStamp"` -} diff --git a/perfd.go b/perfd.go index a253093..f4159b8 100644 --- a/perfd.go +++ b/perfd.go @@ -75,14 +75,15 @@ func WithPerfNetwork(b bool) PerfOption { } type perfdClient struct { - option *perfOption - i *instruments - stop chan struct{} // used to stop perf client - cancels []context.CancelFunc // used to cancel all iterators - chanCPU chan []byte // cpu channel - chanMem chan []byte // mem channel - chanGPU chan []byte // gpu channel - chanFPS chan []byte // fps channel + option *perfOption + i *instruments + stop chan struct{} // used to stop perf client + cancels []context.CancelFunc // used to cancel all iterators + chanCPU chan []byte // cpu channel + chanMem chan []byte // mem channel + chanGPU chan []byte // gpu channel + chanFPS chan []byte // fps channel + chanNetwork chan []byte // network channel } func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient { @@ -99,12 +100,13 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient } return &perfdClient{ - i: i.(*instruments), - option: perfOption, - chanCPU: make(chan []byte, 10), - chanMem: make(chan []byte, 10), - chanGPU: make(chan []byte, 10), - chanFPS: make(chan []byte, 10), + i: i.(*instruments), + option: perfOption, + chanCPU: make(chan []byte, 10), + chanMem: make(chan []byte, 10), + chanGPU: make(chan []byte, 10), + chanFPS: make(chan []byte, 10), + chanNetwork: make(chan []byte, 10), } } @@ -127,6 +129,14 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { c.cancels = append(c.cancels, cancel) } + if c.option.network { + cancel, err := c.registerNetworking(context.Background()) + if err != nil { + return nil, err + } + c.cancels = append(c.cancels, cancel) + } + go func() { for { select { @@ -152,6 +162,11 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { fmt.Println("fps: ", string(fpsBytes)) outCh <- fpsBytes } + case networkBytes, ok := <-c.chanNetwork: + if ok && c.option.network { + fmt.Println("network: ", string(networkBytes)) + outCh <- networkBytes + } } } }() @@ -166,6 +181,79 @@ func (c *perfdClient) Stop() { } } +func (c *perfdClient) registerNetworking(ctx context.Context) ( + cancel context.CancelFunc, err error) { + + chanID, err := c.i.requestChannel(instrumentsServiceNetworking) + if err != nil { + return nil, err + } + + selector := "startMonitoring" + args := libimobiledevice.NewAuxBuffer() + if _, err = c.i.client.Invoke(selector, args, chanID, true); err != nil { + return nil, err + } + c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { + select { + case <-ctx.Done(): + return + default: + c.parseNetworking(m.Obj) + } + }) + + return nil, nil +} + +func (c *perfdClient) parseNetworking(data interface{}) { + // data example (3 types): + // [ + // 2 + // [ + // 36756 // RxBytes + // 11180213 // RxPackets + // 32837 // TxBytes + // 8365982 // TxPackets + // map[$class:9] map[$class:9] map[$class:9] map[$class:9] map[$class:9] 144 -1] + // ] + // [2 [205 435704 0 0 0 0 0 0.005 0.005 95 166]] + // [1 [[16 2 197 19 192 168 100 103 0 0 0 0 0 0 0 0] [16 2 1 187 117 174 183 75 0 0 0 0 0 0 0 0] 14 -2 262144 0 21 1]] + raw, ok := data.([]interface{}) + if !ok || len(raw) != 2 { + return + } + if raw[0].(uint64) == 1 { + // TODO + return + } + + rtxData, ok := raw[1].([]interface{}) + if !ok { + return + } + + netData := NetworkData{ + Type: "network", + TimeStamp: time.Now().Unix(), + RxBytes: convert2Int64(rtxData[0]), + RxPackets: convert2Int64(rtxData[1]), + TxBytes: convert2Int64(rtxData[2]), + TxPackets: convert2Int64(rtxData[3]), + } + netBytes, _ := json.Marshal(netData) + c.chanNetwork <- netBytes +} + +type NetworkData struct { + Type string `json:"type"` // network + TimeStamp int64 `json:"timestamp"` + RxBytes int64 `json:"rxBytes"` + RxPackets int64 `json:"rxPackets"` + TxBytes int64 `json:"txBytes"` + TxPackets int64 `json:"txPackets"` +} + func (c *perfdClient) registerGraphicsOpengl(ctx context.Context) ( cancel context.CancelFunc, err error) { @@ -254,7 +342,7 @@ func (c *perfdClient) parseGpuFps(data interface{}) { type GPUData struct { Type string `json:"type"` // gpu - TimeStamp int64 `json:"timeStamp"` + TimeStamp int64 `json:"timestamp"` TilerUtilization int64 `json:"tilerUtilization"` // 处理顶点的GPU时间占比 DeviceUtilization int64 `json:"deviceUtilization"` // 设备利用率 RendererUtilization int64 `json:"rendererUtilization"` // 渲染器利用率 @@ -263,7 +351,7 @@ type GPUData struct { type FPSData struct { Type string `json:"type"` // fps - TimeStamp int64 `json:"timeStamp"` + TimeStamp int64 `json:"timestamp"` FPS int `json:"fps"` } @@ -488,6 +576,6 @@ func convert2Int64(num interface{}) int64 { case uint: return int64(value) } - fmt.Printf("convert2Int64 failed: %+v", num) + fmt.Printf("convert2Int64 failed: %v, %T\n", num, num) return -1 } diff --git a/perfd_test.go b/perfd_test.go index dd3b830..d966091 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -73,3 +73,26 @@ func TestPerfFPS(t *testing.T) { } } } + +func TestPerfNetwork(t *testing.T) { + setupLockdownSrv(t) + + data, err := dev.PerfStart( + WithPerfCPU(false), + WithPerfMem(false), + WithPerfNetwork(true), + ) + if err != nil { + t.Fatal(err) + } + + timer := time.NewTimer(time.Duration(time.Second * 10)) + for { + select { + case <-timer.C: + return + case d := <-data: + t.Log(string(d)) + } + } +} From 40b9b6bf4883eda81922984930f75419d3864481 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Tue, 4 Oct 2022 09:28:54 +0800 Subject: [PATCH 06/20] change: remove GetPerfmon --- device.go | 127 ----------------- device_test.go | 48 +------ idevice.go | 24 ---- instruments.go | 329 -------------------------------------------- instruments_test.go | 2 +- 5 files changed, 2 insertions(+), 528 deletions(-) diff --git a/device.go b/device.go index 91aca98..3a8c1c7 100644 --- a/device.go +++ b/device.go @@ -637,133 +637,6 @@ func (d *device) MoveCrashReport(hostDir string, opts ...CrashReportMoverOption) return d.crashReportMover.Move(hostDir, opts...) } -func (d *device) GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCancel context.CancelFunc, perfErr error) { - if d.lockdown == nil { - if _, err := d.lockdownService(); err != nil { - return nil, nil, err - } - } - if opts == nil { - return nil, nil, fmt.Errorf("parameter is empty") - } - if !opts.OpenChanCPU && !opts.OpenChanMEM && !opts.OpenChanGPU && !opts.OpenChanFPS && !opts.OpenChanNetWork { - opts.OpenChanCPU = true - opts.OpenChanMEM = true - opts.OpenChanGPU = true - opts.OpenChanFPS = true - opts.OpenChanNetWork = true - } - - var err error - - var instruments Instruments - ctx, cancel := context.WithCancel(context.Background()) - - chanCPU := make(chan CPUData) - chanMEM := make(chan MemData) - var cancelSysmontap context.CancelFunc - - if opts.OpenChanCPU || opts.OpenChanMEM { - instruments, err = d.lockdown.InstrumentsService() - if err != nil { - return nil, cancel, err - } - chanCPU, chanMEM, cancelSysmontap, err = instruments.StartSysmontapServer(opts.PID, ctx) - if err != nil { - cancelSysmontap() - return nil, cancel, err - } - } - - chanFPS := make(chan FPSData) - chanGPU := make(chan GPUData) - var cancelOpengl context.CancelFunc - - if opts.OpenChanGPU || opts.OpenChanFPS { - instruments, err = d.lockdown.InstrumentsService() - if err != nil { - return nil, cancel, err - } - chanFPS, chanGPU, cancelOpengl, err = instruments.StartOpenglServer(ctx) - if err != nil { - cancelOpengl() - return nil, cancel, err - } - } - - chanNetWork := make(chan NetworkData) - var cancelNetWork context.CancelFunc - if opts.OpenChanNetWork { - instruments, err = d.lockdown.InstrumentsService() - if err != nil { - return nil, cancel, err - } - chanNetWork, cancelNetWork, err = instruments.StartNetWorkingServer(ctx) - if err != nil { - cancelNetWork() - return nil, cancel, err - } - } - // 弃用之前的PerfMonData ,统一汇总到interface里,由用户自行决定处理数据 - result := make(chan interface{}) - go func() { - for { - select { - case v, ok := <-chanCPU: - if opts.OpenChanCPU && ok { - result <- v - } - case v, ok := <-chanMEM: - if opts.OpenChanMEM && ok { - result <- v - } - case v, ok := <-chanFPS: - if opts.OpenChanFPS && ok { - result <- v - } - case v, ok := <-chanGPU: - if opts.OpenChanGPU && ok { - result <- v - } - case v, ok := <-chanNetWork: - if opts.OpenChanNetWork && ok { - result <- v - } - case <-ctx.Done(): - err := d.stopPerfmon(opts) - if err != nil { - fmt.Println(err) - } - close(result) - return - } - } - }() - return result, cancel, err -} - -func (d *device) stopPerfmon(opts *PerfmonOption) (err error) { - if _, err = d.instrumentsService(); err != nil { - return err - } - if opts.OpenChanCPU || opts.OpenChanMEM { - if err = d.instruments.StopSysmontapServer(); err != nil { - return err - } - } - if opts.OpenChanGPU || opts.OpenChanFPS { - if err = d.instruments.StopOpenglServer(); err != nil { - return err - } - } - if opts.OpenChanNetWork { - if err = d.instruments.StopNetWorkingServer(); err != nil { - return err - } - } - return nil -} - func (d *device) XCTest(bundleID string, opts ...XCTestOption) (out <-chan string, cancel context.CancelFunc, err error) { xcTestOpt := defaultXCTestOption() for _, fn := range opts { diff --git a/device_test.go b/device_test.go index 716a4de..ac18e95 100644 --- a/device_test.go +++ b/device_test.go @@ -1,7 +1,6 @@ package giDevice import ( - "encoding/json" "fmt" "os" "os/signal" @@ -69,7 +68,7 @@ func Test_device_SavePairRecord(t *testing.T) { func Test_device_XCTest(t *testing.T) { setupLockdownSrv(t) - bundleID = "com.DataMesh.CheckList" + bundleID = "com.leixipaopao.WebDriverAgentRunner.xctrunner" out, cancel, err := dev.XCTest(bundleID) // out, cancel, err := dev.XCTest(bundleID, WithXCTestEnv(map[string]interface{}{"USE_PORT": 8222, "MJPEG_SERVER_PORT": 8333})) if err != nil { @@ -155,51 +154,6 @@ func Test_device_Shutdown(t *testing.T) { dev.Shutdown() } -func Test_device_Perf(t *testing.T) { - //setupDevice(t) - //SetDebug(true,true) - setupLockdownSrv(t) - - c := make(chan os.Signal) - signal.Notify(c, os.Interrupt, os.Kill) - - var opts = &PerfmonOption{ - //PID: "0", - OpenChanNetWork: true, - // OpenChanFPS: true, - // OpenChanMEM: true, - //OpenChanCPU: true, - //OpenChanGPU: true, - } - - outData, cannel, err := dev.GetPerfmon(opts) - if err != nil { - fmt.Println(err) - os.Exit(0) - } - var timeLast = time.Now().Unix() - for { - select { - case <-c: - if cannel != nil { - cannel() - } - default: - data, ok := <-outData - if !ok { - fmt.Println("end get perfmon data") - return - } - result, _ := json.MarshalIndent(data, "", "\t") - fmt.Println(string(result)) - if time.Now().Unix()-timeLast > 60 { - cannel() - } - } - } - -} - func Test_device_InstallationProxyBrowse(t *testing.T) { setupDevice(t) diff --git a/idevice.go b/idevice.go index 286baf5..a62a655 100644 --- a/idevice.go +++ b/idevice.go @@ -77,8 +77,6 @@ type Device interface { GetIconPNGData(bundleId string) (raw *bytes.Buffer, err error) GetInterfaceOrientation() (orientation OrientationState, err error) - GetPerfmon(opts *PerfmonOption) (out chan interface{}, outCancel context.CancelFunc, perfErr error) - PerfStart(opts ...PerfOption) (data <-chan []byte, err error) PerfStop() } @@ -157,19 +155,6 @@ type Instruments interface { // SysMonStart(cfg ...interface{}) (_ interface{}, err error) registerCallback(obj string, cb func(m libimobiledevice.DTXMessageResult)) - - StartOpenglServer(ctx context.Context) (chanFPS chan FPSData, chanGPU chan GPUData, cancel context.CancelFunc, err error) - - StopOpenglServer() (err error) - - StartSysmontapServer(pid string, ctx context.Context) (chanCPU chan CPUData, chanMem chan MemData, cancel context.CancelFunc, err error) - - StopSysmontapServer() (err error) - //ProcessNetwork(pid int) (out <-chan interface{}, cancel context.CancelFunc, err error) - - StartNetWorkingServer(ctx context.Context) (chanNetWorking chan NetworkData, cancel context.CancelFunc, err error) - - StopNetWorkingServer() (err error) } type Testmanagerd interface { @@ -379,15 +364,6 @@ func WithUpdateToken(updateToken string) AppListOption { } } -type PerfmonOption struct { - PID string - OpenChanGPU bool - OpenChanFPS bool - OpenChanCPU bool - OpenChanMEM bool - OpenChanNetWork bool -} - type Process struct { IsApplication bool `json:"isApplication"` Name string `json:"name"` diff --git a/instruments.go b/instruments.go index d194dbc..769873c 100644 --- a/instruments.go +++ b/instruments.go @@ -1,10 +1,8 @@ package giDevice import ( - "context" "encoding/json" "fmt" - "time" "github.com/electricbubble/gidevice/pkg/libimobiledevice" ) @@ -276,333 +274,6 @@ func (i *instruments) registerCallback(obj string, cb func(m libimobiledevice.DT i.client.RegisterCallback(obj, cb) } -func (i *instruments) StartSysmontapServer(pid string, ctxParent context.Context) ( - chanCPU chan CPUData, chanMem chan MemData, cancel context.CancelFunc, err error) { - - var id uint32 - if ctxParent == nil { - return nil, nil, nil, fmt.Errorf("missing context") - } - ctx, cancelFunc := context.WithCancel(ctxParent) - _outMEM := make(chan MemData) - _outCPU := make(chan CPUData) - if id, err = i.requestChannel(instrumentsServiceSysmontap); err != nil { - return nil, nil, cancelFunc, err - } - - selector := "setConfig:" - args := libimobiledevice.NewAuxBuffer() - - var config map[string]interface{} - config = make(map[string]interface{}) - { - config["bm"] = 0 - config["cpuUsage"] = true - - config["procAttrs"] = []string{ - "memVirtualSize", "cpuUsage", "ctxSwitch", "intWakeups", - "physFootprint", "memResidentSize", "memAnon", "pid"} - - config["sampleInterval"] = 1000000000 - // 系统信息字段 - config["sysAttrs"] = []string{ - "vmExtPageCount", "vmFreeCount", "vmPurgeableCount", - "vmSpeculativeCount", "physMemSize"} - // 刷新频率 - config["ur"] = 1000 - } - - args.AppendObject(config) - if _, err = i.client.Invoke(selector, args, id, true); err != nil { - return nil, nil, cancelFunc, err - } - selector = "start" - args = libimobiledevice.NewAuxBuffer() - - if _, err = i.client.Invoke(selector, args, id, true); err != nil { - return nil, nil, cancelFunc, err - } - - i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { - select { - case <-ctx.Done(): - return - default: - mess := m.Obj - chanCPUAndMEMData(mess, _outMEM, _outCPU, pid) - } - }) - - go func() { - i.registerCallback("_Golang-iDevice_Over", func(_ libimobiledevice.DTXMessageResult) { - cancelFunc() - }) - select { - case <-ctx.Done(): - var isOpen bool - if _outCPU != nil { - _, isOpen = <-_outMEM - if isOpen { - close(_outMEM) - } - } - if _outMEM != nil { - _, isOpen = <-_outCPU - if isOpen { - close(_outCPU) - } - } - } - return - }() - return _outCPU, _outMEM, cancelFunc, err -} - -func chanCPUAndMEMData(mess interface{}, _outMEM chan MemData, _outCPU chan CPUData, pid string) { - switch mess.(type) { - case []interface{}: - var infoCPU CPUData - var infoMEM MemData - messArray := mess.([]interface{}) - if len(messArray) == 2 { - var sinfo = messArray[0].(map[string]interface{}) - var pinfolist = messArray[1].(map[string]interface{}) - if sinfo["CPUCount"] == nil { - var temp = sinfo - sinfo = pinfolist - pinfolist = temp - } - if sinfo["CPUCount"] != nil && pinfolist["Processes"] != nil { - var cpuCount = sinfo["CPUCount"] - var sysCpuUsage = sinfo["SystemCPUUsage"].(map[string]interface{}) - var cpuTotalLoad = sysCpuUsage["CPU_TotalLoad"] - // 构建返回信息 - infoCPU.CPUCount = int(cpuCount.(uint64)) - infoCPU.SysCPUUsageTotalLoad = cpuTotalLoad.(float64) - //finalCpuInfo["attrCpuTotal"] = cpuTotalLoad - - var cpuUsage = 0.0 - pidMess := pinfolist["Processes"].(map[string]interface{})[pid] - if pidMess != nil { - processInfo := convertProcessData(pidMess) - cpuUsage = processInfo["cpuUsage"].(float64) - infoCPU.ProcCPUUsage = cpuUsage - infoCPU.ProcPID = pid - infoCPU.ProcAttrCtxSwitch = convert2Int64(processInfo["ctxSwitch"]) - infoCPU.ProcAttrIntWakeups = convert2Int64(processInfo["intWakeups"]) - - infoMEM.Vss = convert2Int64(processInfo["memVirtualSize"]) - infoMEM.Rss = convert2Int64(processInfo["memResidentSize"]) - infoMEM.Anon = convert2Int64(processInfo["memAnon"]) - infoMEM.PhysMemory = convert2Int64(processInfo["physFootprint"]) - - } else { - infoCPU.Msg = "invalid PID" - infoMEM.Msg = "invalid PID" - - infoMEM.Vss = -1 - infoMEM.Rss = -1 - infoMEM.Anon = -1 - infoMEM.PhysMemory = -1 - } - - infoMEM.TimeStamp = time.Now().UnixNano() - infoCPU.TimeStamp = time.Now().UnixNano() - - _outMEM <- infoMEM - _outCPU <- infoCPU - } - } - } -} - -func (i *instruments) StopSysmontapServer() (err error) { - id, err := i.requestChannel(instrumentsServiceSysmontap) - if err != nil { - return err - } - selector := "stop" - args := libimobiledevice.NewAuxBuffer() - if _, err = i.client.Invoke(selector, args, id, true); err != nil { - return err - } - return nil -} - -// todo 获取单进程流量情况,看情况做不做 -// 目前只获取到系统全局的流量情况,单进程需要用到set,go没有,并且实际用python测试单进程的流量情况不准 -func (i *instruments) StartNetWorkingServer(ctxParent context.Context) (chanNetWorking chan NetworkData, cancel context.CancelFunc, err error) { - var id uint32 - if ctxParent == nil { - return nil, nil, fmt.Errorf("missing context") - } - ctx, cancelFunc := context.WithCancel(ctxParent) - _outNetWork := make(chan NetworkData) - if id, err = i.requestChannel(instrumentsServiceNetworking); err != nil { - return nil, cancelFunc, err - } - selector := "startMonitoring" - args := libimobiledevice.NewAuxBuffer() - - if _, err = i.client.Invoke(selector, args, id, true); err != nil { - return nil, cancelFunc, err - } - i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { - select { - case <-ctx.Done(): - return - default: - receData, ok := m.Obj.([]interface{}) - if ok && len(receData) == 2 { - sendAndReceiveData, ok := receData[1].([]interface{}) - if ok { - var netData NetworkData - // 有时候是uint8,有时候是uint64。。。恶心 - netData.RxBytes = convert2Int64(sendAndReceiveData[0]) - netData.RxPackets = convert2Int64(sendAndReceiveData[1]) - netData.TxBytes = convert2Int64(sendAndReceiveData[2]) - netData.TxPackets = convert2Int64(sendAndReceiveData[3]) - netData.TimeStamp = time.Now().UnixNano() - _outNetWork <- netData - } - } - } - }) - go func() { - i.registerCallback("_Golang-iDevice_Over", func(_ libimobiledevice.DTXMessageResult) { - cancelFunc() - }) - select { - case <-ctx.Done(): - _, isOpen := <-_outNetWork - if isOpen { - close(_outNetWork) - } - } - return - }() - return _outNetWork, cancelFunc, err -} - -func (i *instruments) StopNetWorkingServer() (err error) { - var id uint32 - id, err = i.requestChannel(instrumentsServiceNetworking) - if err != nil { - return err - } - selector := "stopMonitoring" - args := libimobiledevice.NewAuxBuffer() - - if _, err = i.client.Invoke(selector, args, id, true); err != nil { - return err - } - return nil -} - -func (i *instruments) StartOpenglServer(ctxParent context.Context) (chanFPS chan FPSData, chanGPU chan GPUData, cancel context.CancelFunc, err error) { - var id uint32 - if ctxParent == nil { - return nil, nil, nil, fmt.Errorf("missing context") - } - ctx, cancelFunc := context.WithCancel(ctxParent) - _outFPS := make(chan FPSData) - _outGPU := make(chan GPUData) - if id, err = i.requestChannel(instrumentsServiceGraphicsOpengl); err != nil { - return nil, nil, cancelFunc, err - } - - selector := "availableStatistics" - args := libimobiledevice.NewAuxBuffer() - - if _, err = i.client.Invoke(selector, args, id, true); err != nil { - return nil, nil, cancelFunc, err - } - - selector = "setSamplingRate:" - if err = args.AppendObject(0.0); err != nil { - return nil, nil, cancelFunc, err - } - if _, err = i.client.Invoke(selector, args, id, true); err != nil { - return nil, nil, cancelFunc, err - } - - selector = "startSamplingAtTimeInterval:processIdentifier:" - args = libimobiledevice.NewAuxBuffer() - if err = args.AppendObject(0); err != nil { - return nil, nil, cancelFunc, err - } - if err = args.AppendObject(0); err != nil { - return nil, nil, cancelFunc, err - } - if _, err = i.client.Invoke(selector, args, id, true); err != nil { - return nil, nil, cancelFunc, err - } - - i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { - select { - case <-ctx.Done(): - return - default: - mess := m.Obj - var deviceUtilization = mess.(map[string]interface{})["Device Utilization %"] // Device Utilization - var tilerUtilization = mess.(map[string]interface{})["Tiler Utilization %"] // Tiler Utilization - var rendererUtilization = mess.(map[string]interface{})["Renderer Utilization %"] // Renderer Utilization - - var infoGPU GPUData - - infoGPU.DeviceUtilization = convert2Int64(deviceUtilization) - infoGPU.TilerUtilization = convert2Int64(tilerUtilization) - infoGPU.RendererUtilization = convert2Int64(rendererUtilization) - infoGPU.TimeStamp = time.Now().UnixNano() - _outGPU <- infoGPU - - var infoFPS FPSData - var fps = mess.(map[string]interface{})["CoreAnimationFramesPerSecond"] - infoFPS.FPS = int(convert2Int64(fps)) - infoFPS.TimeStamp = time.Now().UnixNano() - _outFPS <- infoFPS - } - }) - go func() { - i.registerCallback("_Golang-iDevice_Over", func(_ libimobiledevice.DTXMessageResult) { - cancelFunc() - }) - select { - case <-ctx.Done(): - var isOpen bool - if _outGPU != nil { - _, isOpen = <-_outGPU - if isOpen { - close(_outGPU) - } - } - if _outFPS != nil { - _, isOpen = <-_outFPS - if isOpen { - close(_outFPS) - } - } - } - return - }() - return _outFPS, _outGPU, cancelFunc, err -} - -func (i *instruments) StopOpenglServer() (err error) { - - id, err := i.requestChannel(instrumentsServiceGraphicsOpengl) - if err != nil { - return err - } - selector := "stop" - args := libimobiledevice.NewAuxBuffer() - - if _, err = i.client.Invoke(selector, args, id, true); err != nil { - return err - } - return nil -} - type Application struct { AppExtensionUUIDs []string `json:"AppExtensionUUIDs,omitempty"` BundlePath string `json:"BundlePath"` diff --git a/instruments_test.go b/instruments_test.go index 98bf203..8e208e6 100644 --- a/instruments_test.go +++ b/instruments_test.go @@ -22,7 +22,7 @@ func setupInstrumentsSrv(t *testing.T) { func Test_instruments_AppLaunch(t *testing.T) { setupInstrumentsSrv(t) - bundleID = "com.DataMesh.CheckList" + // bundleID = "com.leixipaopao.WebDriverAgentRunner.xctrunner" // pid, err := dev.AppLaunch(bundleID) pid, err := instrumentsSrv.AppLaunch(bundleID) From 63442df72179e8d18b71925238cfd5be32829f29 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Tue, 4 Oct 2022 12:30:08 +0800 Subject: [PATCH 07/20] feat: add invalid data to msg --- perfd.go | 188 ++++++++++++++++++++++++++++++-------------------- perfd_test.go | 4 ++ 2 files changed, 116 insertions(+), 76 deletions(-) diff --git a/perfd.go b/perfd.go index f4159b8..3c61737 100644 --- a/perfd.go +++ b/perfd.go @@ -143,27 +143,27 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { case <-c.stop: return case cpuBytes, ok := <-c.chanCPU: - if ok && c.option.cpu { + if ok { fmt.Println("cpu: ", string(cpuBytes)) outCh <- cpuBytes } case memBytes, ok := <-c.chanMem: - if ok && c.option.mem { + if ok { fmt.Println("mem: ", string(memBytes)) outCh <- memBytes } case gpuBytes, ok := <-c.chanGPU: - if ok && c.option.gpu { + if ok { fmt.Println("gpu: ", string(gpuBytes)) outCh <- gpuBytes } case fpsBytes, ok := <-c.chanFPS: - if ok && c.option.fps { + if ok { fmt.Println("fps: ", string(fpsBytes)) outCh <- fpsBytes } case networkBytes, ok := <-c.chanNetwork: - if ok && c.option.network { + if ok { fmt.Println("network: ", string(networkBytes)) outCh <- networkBytes } @@ -209,7 +209,7 @@ func (c *perfdClient) registerNetworking(ctx context.Context) ( func (c *perfdClient) parseNetworking(data interface{}) { // data example (3 types): // [ - // 2 + // 2 // type // [ // 36756 // RxBytes // 11180213 // RxPackets @@ -219,39 +219,48 @@ func (c *perfdClient) parseNetworking(data interface{}) { // ] // [2 [205 435704 0 0 0 0 0 0.005 0.005 95 166]] // [1 [[16 2 197 19 192 168 100 103 0 0 0 0 0 0 0 0] [16 2 1 187 117 174 183 75 0 0 0 0 0 0 0 0] 14 -2 262144 0 21 1]] + + netData := NetworkData{ + Type: "network", + TimeStamp: time.Now().Unix(), + } + + defer func() { + netBytes, _ := json.Marshal(netData) + c.chanNetwork <- netBytes + }() + raw, ok := data.([]interface{}) if !ok || len(raw) != 2 { + netData.Msg = fmt.Sprintf("invalid networking data: %v", data) return } if raw[0].(uint64) == 1 { // TODO + netData.Msg = fmt.Sprintf("unhandled networking data: %v", data) return } rtxData, ok := raw[1].([]interface{}) if !ok { + netData.Msg = fmt.Sprintf("unexpected networking data: %v", data) return } - netData := NetworkData{ - Type: "network", - TimeStamp: time.Now().Unix(), - RxBytes: convert2Int64(rtxData[0]), - RxPackets: convert2Int64(rtxData[1]), - TxBytes: convert2Int64(rtxData[2]), - TxPackets: convert2Int64(rtxData[3]), - } - netBytes, _ := json.Marshal(netData) - c.chanNetwork <- netBytes + netData.RxBytes = convert2Int64(rtxData[0]) + netData.RxPackets = convert2Int64(rtxData[1]) + netData.TxBytes = convert2Int64(rtxData[2]) + netData.TxPackets = convert2Int64(rtxData[3]) } type NetworkData struct { Type string `json:"type"` // network TimeStamp int64 `json:"timestamp"` - RxBytes int64 `json:"rxBytes"` - RxPackets int64 `json:"rxPackets"` - TxBytes int64 `json:"txBytes"` - TxPackets int64 `json:"txPackets"` + RxBytes int64 `json:"rx_bytes"` + RxPackets int64 `json:"rx_packets"` + TxBytes int64 `json:"tx_bytes"` + TxPackets int64 `json:"tx_packets"` + Msg string `json:"msg,omitempty"` // message for invalid data } func (c *perfdClient) registerGraphicsOpengl(ctx context.Context) ( @@ -305,54 +314,69 @@ func (c *perfdClient) parseGpuFps(data interface{}) { // map[ // Alloc system memory:50167808 // Allocated PB Size:1179648 - // CoreAnimationFramesPerSecond:0 - // Device Utilization %:0 + // CoreAnimationFramesPerSecond:0 // fps from GPU + // Device Utilization %:0 // device // IOGLBundleName:Built-In // In use system memory:10633216 - // Renderer Utilization %:0 + // Renderer Utilization %:0 // renderer // SplitSceneCount:0 // TiledSceneBytes:0 - // Tiler Utilization %:0 + // Tiler Utilization %:0 // tiler // XRVideoCardRunTimeStamp:1010679 // recoveryCount:0 // ] - var deviceUtilization = data.(map[string]interface{})["Device Utilization %"] // Device Utilization - var tilerUtilization = data.(map[string]interface{})["Tiler Utilization %"] // Tiler Utilization - var rendererUtilization = data.(map[string]interface{})["Renderer Utilization %"] // Renderer Utilization + timestamp := time.Now().Unix() gpuInfo := GPUData{ - Type: "gpu", - TimeStamp: time.Now().Unix(), - DeviceUtilization: convert2Int64(deviceUtilization), - TilerUtilization: convert2Int64(tilerUtilization), - RendererUtilization: convert2Int64(rendererUtilization), + Type: "gpu", + TimeStamp: timestamp, } - gpuBytes, _ := json.Marshal(gpuInfo) - c.chanGPU <- gpuBytes - - var fps = data.(map[string]interface{})["CoreAnimationFramesPerSecond"] fpsInfo := FPSData{ Type: "fps", - TimeStamp: time.Now().Unix(), - FPS: int(convert2Int64(fps)), + TimeStamp: timestamp, } - fpsBytes, _ := json.Marshal(fpsInfo) - c.chanFPS <- fpsBytes + + defer func() { + if c.option.gpu { + gpuBytes, _ := json.Marshal(gpuInfo) + c.chanGPU <- gpuBytes + } + if c.option.fps { + fpsBytes, _ := json.Marshal(fpsInfo) + c.chanFPS <- fpsBytes + } + }() + + raw, ok := data.(map[string]interface{}) + if !ok { + gpuInfo.Msg = fmt.Sprintf("invalid graphics.opengl data: %v", data) + fpsInfo.Msg = fmt.Sprintf("invalid graphics.opengl data: %v", data) + return + } + + // gpu + gpuInfo.DeviceUtilization = convert2Int64(raw["Device Utilization %"]) + gpuInfo.TilerUtilization = convert2Int64(raw["Tiler Utilization %"]) + gpuInfo.RendererUtilization = convert2Int64(raw["Renderer Utilization %"]) + + // fps + fpsInfo.FPS = int(convert2Int64(raw["CoreAnimationFramesPerSecond"])) } type GPUData struct { Type string `json:"type"` // gpu TimeStamp int64 `json:"timestamp"` - TilerUtilization int64 `json:"tilerUtilization"` // 处理顶点的GPU时间占比 - DeviceUtilization int64 `json:"deviceUtilization"` // 设备利用率 - RendererUtilization int64 `json:"rendererUtilization"` // 渲染器利用率 - Msg string `json:"msg,omitempty"` // 提示信息 + TilerUtilization int64 `json:"tiler_utilization"` // 处理顶点的 GPU 时间占比 + DeviceUtilization int64 `json:"device_utilization"` // 设备利用率 + RendererUtilization int64 `json:"renderer_utilization"` // 渲染器利用率 + Msg string `json:"msg,omitempty"` // message for invalid data } type FPSData struct { Type string `json:"type"` // fps TimeStamp int64 `json:"timestamp"` FPS int `json:"fps"` + Msg string `json:"msg,omitempty"` // message for invalid data } func (c *perfdClient) registerSysmontap(pid string, ctx context.Context) ( @@ -414,8 +438,33 @@ func (c *perfdClient) registerSysmontap(pid string, ctx context.Context) ( } func (c *perfdClient) parseCPUMem(data interface{}, pid string) { + timestamp := time.Now().Unix() + cpuInfo := CPUData{ + Type: "cpu", + TimeStamp: timestamp, + ProcPID: pid, + } + memInfo := MemData{ + Type: "mem", + TimeStamp: timestamp, + ProcPID: pid, + } + + defer func() { + if c.option.cpu { + cpuBytes, _ := json.Marshal(cpuInfo) + c.chanCPU <- cpuBytes + } + if c.option.mem { + memBytes, _ := json.Marshal(memInfo) + c.chanMem <- memBytes + } + }() + messArray, ok := data.([]interface{}) if !ok || len(messArray) != 2 { + cpuInfo.Msg = fmt.Sprintf("invalid sysmontap data: %v", data) + memInfo.Msg = fmt.Sprintf("invalid sysmontap data: %v", data) return } @@ -425,7 +474,7 @@ func (c *perfdClient) parseCPUMem(data interface{}, pid string) { systemInfo, processInfoList = processInfoList, systemInfo } if systemInfo["CPUCount"] == nil { - fmt.Printf("invalid system info: %v\n", systemInfo) + cpuInfo.Msg = fmt.Sprintf("invalid system info: %v", systemInfo) return } // systemInfo example: @@ -451,7 +500,8 @@ func (c *perfdClient) parseCPUMem(data interface{}, pid string) { var sysCpuUsage = systemInfo["SystemCPUUsage"].(map[string]interface{}) if processInfoList["Processes"] == nil { - fmt.Printf("invalid process info list: %v\n", processInfoList) + cpuInfo.Msg = fmt.Sprintf("invalid process info list: %v", processInfoList) + memInfo.Msg = fmt.Sprintf("invalid process info list: %v", processInfoList) return } // processInfoList example: @@ -465,21 +515,12 @@ func (c *perfdClient) parseCPUMem(data interface{}, pid string) { // EndMachAbsTime:2514112708690 // Type:5 // ] - processes := processInfoList["Processes"].(map[string]interface{}) - cpuInfo := CPUData{ - Type: "cpu", - TimeStamp: time.Now().Unix(), - CPUCount: int(cpuCount.(uint64)), - SysCPUUsageTotalLoad: sysCpuUsage["CPU_TotalLoad"].(float64), - ProcPID: pid, - } - memInfo := MemData{ - Type: "mem", - TimeStamp: time.Now().Unix(), - ProcPID: pid, - } + // cpu + cpuInfo.CPUCount = int(cpuCount.(uint64)) + cpuInfo.SysCPUUsageTotalLoad = sysCpuUsage["CPU_TotalLoad"].(float64) + processes := processInfoList["Processes"].(map[string]interface{}) procData, ok := processes[pid] processInfo := convertProcessData(procData) if ok && processInfo != nil { @@ -494,44 +535,39 @@ func (c *perfdClient) parseCPUMem(data interface{}, pid string) { memInfo.PhysMemory = convert2Int64(processInfo["physFootprint"]) } else { // cpu - cpuInfo.Msg = "invalid PID" + cpuInfo.Msg = fmt.Sprintf("pid %s not found", pid) // mem - memInfo.Msg = "invalid PID" + memInfo.Msg = fmt.Sprintf("pid %s not found", pid) memInfo.Vss = -1 memInfo.Rss = -1 memInfo.Anon = -1 memInfo.PhysMemory = -1 } - - cpuBytes, _ := json.Marshal(cpuInfo) - memBytes, _ := json.Marshal(memInfo) - c.chanCPU <- cpuBytes - c.chanMem <- memBytes } type CPUData struct { Type string `json:"type"` // cpu TimeStamp int64 `json:"timestamp"` - Msg string `json:"msg,omitempty"` // 提示信息 + Msg string `json:"msg,omitempty"` // message for invalid data // system - CPUCount int `json:"cpuCount"` // CPU总数 - SysCPUUsageTotalLoad float64 `json:"sysCpuUsageTotalLoad"` // 系统总体CPU占用 + CPUCount int `json:"cpu_count"` // CPU总数 + SysCPUUsageTotalLoad float64 `json:"sys_cpu_usage"` // 系统总体CPU占用 // process - ProcPID string `json:"procPID"` // 进程 PID - ProcCPUUsage float64 `json:"procCpuUsage,omitempty"` // 单个进程的CPU使用率 - ProcAttrCtxSwitch int64 `json:"procAttrCtxSwitch,omitempty"` // 上下文切换数 - ProcAttrIntWakeups int64 `json:"procAttrIntWakeups,omitempty"` // 唤醒数 + ProcPID string `json:"pid"` // 进程 PID + ProcCPUUsage float64 `json:"proc_cpu_usage,omitempty"` // 单个进程的CPU使用率 + ProcAttrCtxSwitch int64 `json:"proc_attr_ctx_switch,omitempty"` // 上下文切换数 + ProcAttrIntWakeups int64 `json:"proc_attr_int_wakeups,omitempty"` // 唤醒数 } type MemData struct { Type string `json:"type"` // mem TimeStamp int64 `json:"timestamp"` Anon int64 `json:"anon"` // 虚拟内存 - PhysMemory int64 `json:"physMemory"` // 物理内存 + PhysMemory int64 `json:"phys_memory"` // 物理内存 Rss int64 `json:"rss"` // 总内存 Vss int64 `json:"vss"` // 虚拟内存 - ProcPID string `json:"procPID"` // 进程 PID - Msg string `json:"msg,omitempty"` // 提示信息 + ProcPID string `json:"pid"` // 进程 PID + Msg string `json:"msg,omitempty"` // message for invalid data } func convertProcessData(procData interface{}) map[string]interface{} { diff --git a/perfd_test.go b/perfd_test.go index d966091..e1e2a0e 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -21,6 +21,7 @@ func TestPerfCPUMem(t *testing.T) { for { select { case <-timer.C: + dev.PerfStop() return case d := <-data: t.Log(string(d)) @@ -44,6 +45,7 @@ func TestPerfGPU(t *testing.T) { for { select { case <-timer.C: + dev.PerfStop() return case d := <-data: t.Log(string(d)) @@ -67,6 +69,7 @@ func TestPerfFPS(t *testing.T) { for { select { case <-timer.C: + dev.PerfStop() return case d := <-data: t.Log(string(d)) @@ -90,6 +93,7 @@ func TestPerfNetwork(t *testing.T) { for { select { case <-timer.C: + dev.PerfStop() return case d := <-data: t.Log(string(d)) From 19397e74317929644d33ea6dbce20ee13478e479 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 6 Oct 2022 22:08:30 +0800 Subject: [PATCH 08/20] fix: panics --- perfd.go | 8 ++++++-- perfd_test.go | 2 +- pkg/libimobiledevice/client_dtxmessage.go | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/perfd.go b/perfd.go index 3c61737..0343c9c 100644 --- a/perfd.go +++ b/perfd.go @@ -102,6 +102,7 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient return &perfdClient{ i: i.(*instruments), option: perfOption, + stop: make(chan struct{}), chanCPU: make(chan []byte, 10), chanMem: make(chan []byte, 10), chanGPU: make(chan []byte, 10), @@ -194,6 +195,8 @@ func (c *perfdClient) registerNetworking(ctx context.Context) ( if _, err = c.i.client.Invoke(selector, args, chanID, true); err != nil { return nil, err } + + ctx, cancel = context.WithCancel(ctx) c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { select { case <-ctx.Done(): @@ -203,7 +206,7 @@ func (c *perfdClient) registerNetworking(ctx context.Context) ( } }) - return nil, nil + return } func (c *perfdClient) parseNetworking(data interface{}) { @@ -297,6 +300,7 @@ func (c *perfdClient) registerGraphicsOpengl(ctx context.Context) ( return nil, err } + ctx, cancel = context.WithCancel(ctx) c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { select { case <-ctx.Done(): @@ -419,7 +423,7 @@ func (c *perfdClient) registerSysmontap(pid string, ctx context.Context) ( // start args = libimobiledevice.NewAuxBuffer() - if _, err = c.i.client.Invoke("start", args, chanID, true); err != nil { + if _, err = c.i.client.Invoke("start", args, chanID, false); err != nil { return nil, err } diff --git a/perfd_test.go b/perfd_test.go index e1e2a0e..626ed6f 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -11,7 +11,7 @@ func TestPerfCPUMem(t *testing.T) { data, err := dev.PerfStart( WithPerfCPU(true), WithPerfMem(true), - WithPerfBundleID("com.apple.Spotlight"), + WithPerfBundleID("com.apple.mobilesafari"), ) if err != nil { t.Fatal(err) diff --git a/pkg/libimobiledevice/client_dtxmessage.go b/pkg/libimobiledevice/client_dtxmessage.go index cf09ada..e7ead77 100644 --- a/pkg/libimobiledevice/client_dtxmessage.go +++ b/pkg/libimobiledevice/client_dtxmessage.go @@ -5,12 +5,13 @@ import ( "context" "encoding/hex" "fmt" - "github.com/electricbubble/gidevice/pkg/nskeyedarchiver" "io" "strings" "sync" "time" "unsafe" + + "github.com/electricbubble/gidevice/pkg/nskeyedarchiver" ) const ( @@ -242,7 +243,6 @@ func (c *dtxMessageClient) ReceiveDTXMessage() (result *DTXMessageResult, err er c.mu.Lock() c.resultMap[sk] = result c.mu.Unlock() - } return From d52723a2c682849f1d5637e8c50ea6d394746461 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 7 Oct 2022 09:32:50 +0800 Subject: [PATCH 09/20] refactor: get fps --- instruments.go | 18 ++++++++++++ perfd.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/instruments.go b/instruments.go index 769873c..b987a32 100644 --- a/instruments.go +++ b/instruments.go @@ -44,6 +44,24 @@ func (i *instruments) requestChannel(channel string) (id uint32, err error) { return i.client.RequestChannel(channel) } +func (i *instruments) call(channel, selector string, auxiliaries ...interface{}) ( + result *libimobiledevice.DTXMessageResult, err error) { + + chanID, err := i.requestChannel(channel) + if err != nil { + return nil, err + } + + args := libimobiledevice.NewAuxBuffer() + for _, aux := range auxiliaries { + if err = args.AppendObject(aux); err != nil { + return nil, err + } + } + + return i.client.Invoke(selector, args, chanID, true) +} + func (i *instruments) AppLaunch(bundleID string, opts ...AppLaunchOption) (pid int, err error) { opt := new(appLaunchOption) opt.appPath = "" diff --git a/perfd.go b/perfd.go index 0343c9c..e148774 100644 --- a/perfd.go +++ b/perfd.go @@ -122,7 +122,15 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { c.cancels = append(c.cancels, cancel) } - if c.option.gpu || c.option.fps { + if c.option.fps { + cancel, err := c.startGetFPS(context.Background()) + if err != nil { + return nil, err + } + c.cancels = append(c.cancels, cancel) + } + + if c.option.gpu { cancel, err := c.registerGraphicsOpengl(context.Background()) if err != nil { return nil, err @@ -266,6 +274,75 @@ type NetworkData struct { Msg string `json:"msg,omitempty"` // message for invalid data } +func (c *perfdClient) startGetFPS(ctx context.Context) ( + cancel context.CancelFunc, err error) { + + if _, err = c.i.call( + instrumentsServiceGraphicsOpengl, + "setSamplingRate:", + float64(1000)/100, // TODO: make it configurable + ); err != nil { + return nil, err + } + + if _, err = c.i.call( + instrumentsServiceGraphicsOpengl, + "startSamplingAtTimeInterval:", + 0, + ); err != nil { + return nil, err + } + + ctx, cancel = context.WithCancel(ctx) + c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { + select { + case <-ctx.Done(): + return + default: + c.parseFPS(m.Obj) + } + }) + + return +} + +func (c *perfdClient) parseFPS(data interface{}) { + // data example: + // map[ + // Alloc system memory:50167808 + // Allocated PB Size:1179648 + // CoreAnimationFramesPerSecond:0 // fps from GPU + // Device Utilization %:0 + // IOGLBundleName:Built-In + // In use system memory:10633216 + // Renderer Utilization %:0 + // SplitSceneCount:0 + // TiledSceneBytes:0 + // Tiler Utilization %:0 + // XRVideoCardRunTimeStamp:1010679 + // recoveryCount:0 + // ] + + fpsInfo := FPSData{ + Type: "fps", + TimeStamp: time.Now().Unix(), + } + + defer func() { + fpsBytes, _ := json.Marshal(fpsInfo) + c.chanFPS <- fpsBytes + }() + + raw, ok := data.(map[string]interface{}) + if !ok { + fpsInfo.Msg = fmt.Sprintf("invalid graphics.opengl data: %v", data) + return + } + + // fps + fpsInfo.FPS = int(convert2Int64(raw["CoreAnimationFramesPerSecond"])) +} + func (c *perfdClient) registerGraphicsOpengl(ctx context.Context) ( cancel context.CancelFunc, err error) { From 12ca03bff2ade3405181f2499493881678143af6 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 7 Oct 2022 13:04:09 +0800 Subject: [PATCH 10/20] refactor: get network data --- perfd.go | 261 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 166 insertions(+), 95 deletions(-) diff --git a/perfd.go b/perfd.go index e148774..8c71571 100644 --- a/perfd.go +++ b/perfd.go @@ -2,8 +2,10 @@ package giDevice import ( "context" + "encoding/binary" "encoding/json" "fmt" + "net" "strconv" "time" @@ -131,7 +133,7 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { } if c.option.gpu { - cancel, err := c.registerGraphicsOpengl(context.Background()) + cancel, err := c.startGetGPU(context.Background()) if err != nil { return nil, err } @@ -193,14 +195,17 @@ func (c *perfdClient) Stop() { func (c *perfdClient) registerNetworking(ctx context.Context) ( cancel context.CancelFunc, err error) { - chanID, err := c.i.requestChannel(instrumentsServiceNetworking) - if err != nil { + if _, err = c.i.call( + instrumentsServiceNetworking, + "replayLastRecordedSession", + ); err != nil { return nil, err } - selector := "startMonitoring" - args := libimobiledevice.NewAuxBuffer() - if _, err = c.i.client.Invoke(selector, args, chanID, true); err != nil { + if _, err = c.i.call( + instrumentsServiceNetworking, + "startMonitoring", + ); err != nil { return nil, err } @@ -218,60 +223,154 @@ func (c *perfdClient) registerNetworking(ctx context.Context) ( } func (c *perfdClient) parseNetworking(data interface{}) { - // data example (3 types): - // [ - // 2 // type - // [ - // 36756 // RxBytes - // 11180213 // RxPackets - // 32837 // TxBytes - // 8365982 // TxPackets - // map[$class:9] map[$class:9] map[$class:9] map[$class:9] map[$class:9] 144 -1] - // ] - // [2 [205 435704 0 0 0 0 0 0.005 0.005 95 166]] - // [1 [[16 2 197 19 192 168 100 103 0 0 0 0 0 0 0 0] [16 2 1 187 117 174 183 75 0 0 0 0 0 0 0 0] 14 -2 262144 0 21 1]] - - netData := NetworkData{ - Type: "network", - TimeStamp: time.Now().Unix(), - } - - defer func() { - netBytes, _ := json.Marshal(netData) - c.chanNetwork <- netBytes - }() - raw, ok := data.([]interface{}) if !ok || len(raw) != 2 { - netData.Msg = fmt.Sprintf("invalid networking data: %v", data) - return - } - if raw[0].(uint64) == 1 { - // TODO - netData.Msg = fmt.Sprintf("unhandled networking data: %v", data) + fmt.Printf("invalid networking data: %v\n", data) return } - rtxData, ok := raw[1].([]interface{}) - if !ok { - netData.Msg = fmt.Sprintf("unexpected networking data: %v", data) - return + var netBytes []byte + msgType := raw[0].(uint64) + msgValue := raw[1].([]interface{}) + if msgType == 0 { + // interface-detection + // ['InterfaceIndex', "Name"] + // e.g. [0, [14, 'en0']] + netData := NetworkDataInterfaceDetection{ + NetworkData: NetworkData{ + Type: "network-interface-detection", + TimeStamp: time.Now().Unix(), + }, + InterfaceIndex: convert2Int64(msgValue[0]), + Name: msgValue[1].(string), + } + netBytes, _ = json.Marshal(netData) + } else if msgType == 1 { + // connection-detected + // ['LocalAddress', 'RemoteAddress', 'InterfaceIndex', 'Pid', + // 'RecvBufferSize', 'RecvBufferUsed', 'SerialNumber', 'Kind'] + // e.g. [1 [[16 2 211 158 192 168 100 101 0 0 0 0 0 0 0 0] + // [16 2 0 53 183 221 253 100 0 0 0 0 0 0 0 0] + // 14 -2 786896 0 133 2]] + + localAddr, err := parseSocketAddr(msgValue[0].([]byte)) + if err != nil { + fmt.Printf("parse local socket address err: %v\n", err) + } + remoteAddr, err := parseSocketAddr(msgValue[1].([]byte)) + if err != nil { + fmt.Printf("parse remote socket address err: %v\n", err) + } + netData := NetworkDataConnectionDetected{ + NetworkData: NetworkData{ + Type: "network-connection-detected", + TimeStamp: time.Now().Unix(), + }, + LocalAddress: localAddr, + RemoteAddress: remoteAddr, + InterfaceIndex: convert2Int64(msgValue[2]), + Pid: convert2Int64(msgValue[3]), + RecvBufferSize: convert2Int64(msgValue[4]), + RecvBufferUsed: convert2Int64(msgValue[5]), + SerialNumber: convert2Int64(msgValue[6]), + Kind: convert2Int64(msgValue[7]), + } + netBytes, _ = json.Marshal(netData) + } else if msgType == 2 { + // connection-update + // ['RxPackets', 'RxBytes', 'TxPackets', 'TxBytes', + // 'RxDups', 'RxOOO', 'TxRetx', 'MinRTT', 'AvgRTT', 'ConnectionSerial'] + // e.g. [2, [21, 1708, 22, 14119, 309, 0, 5830, 0.076125, 0.076125, 54, -1]] + netData := NetworkDataConnectionUpdate{ + NetworkData: NetworkData{ + Type: "network-connection-update", + TimeStamp: time.Now().Unix(), + }, + RxBytes: convert2Int64(msgValue[0]), + RxPackets: convert2Int64(msgValue[1]), + TxBytes: convert2Int64(msgValue[2]), + TxPackets: convert2Int64(msgValue[3]), + } + if value, ok := msgValue[4].(uint64); ok { + netData.RxDups = int64(value) + } + if value, ok := msgValue[5].(uint64); ok { + netData.RxOOO = int64(value) + } + if value, ok := msgValue[6].(uint64); ok { + netData.TxRetx = int64(value) + } + if value, ok := msgValue[7].(uint64); ok { + netData.MinRTT = int64(value) + } + if value, ok := msgValue[8].(uint64); ok { + netData.AvgRTT = int64(value) + } + if value, ok := msgValue[9].(uint64); ok { + netData.ConnectionSerial = int64(value) + } + + netBytes, _ = json.Marshal(netData) } + c.chanNetwork <- netBytes +} - netData.RxBytes = convert2Int64(rtxData[0]) - netData.RxPackets = convert2Int64(rtxData[1]) - netData.TxBytes = convert2Int64(rtxData[2]) - netData.TxPackets = convert2Int64(rtxData[3]) +func parseSocketAddr(data []byte) (string, error) { + len := data[0] // length of address + _ = data[1] // family + port := binary.BigEndian.Uint16(data[2:4]) // port + + // network, data[4:4+len] + if len == 0x10 { + // IPv4, 4 bytes + ip := net.IP(data[4:8]) + return fmt.Sprintf("%s:%d", ip, port), nil + } else if len == 0x1c { + // IPv6, 16 bytes + ip := net.IP(data[4:20]) + return fmt.Sprintf("%s:%d", ip, port), nil + } + return "", fmt.Errorf("invalid socket address: %v", data) } type NetworkData struct { Type string `json:"type"` // network TimeStamp int64 `json:"timestamp"` - RxBytes int64 `json:"rx_bytes"` - RxPackets int64 `json:"rx_packets"` - TxBytes int64 `json:"tx_bytes"` - TxPackets int64 `json:"tx_packets"` - Msg string `json:"msg,omitempty"` // message for invalid data +} + +// network-interface-detection +type NetworkDataInterfaceDetection struct { + NetworkData + InterfaceIndex int64 `json:"interface_index"` // 0 + Name string `json:"name"` // 1 +} + +// network-connection-detected +type NetworkDataConnectionDetected struct { + NetworkData + LocalAddress string `json:"local_address"` // 0 + RemoteAddress string `json:"remote_address"` // 1 + InterfaceIndex int64 `json:"interface_index"` // 2 + Pid int64 `json:"pid"` // 3 + RecvBufferSize int64 `json:"recv_buffer_size"` // 4 + RecvBufferUsed int64 `json:"recv_buffer_used"` // 5 + SerialNumber int64 `json:"serial_number"` // 6 + Kind int64 `json:"kind"` // 7 +} + +// network-connection-update +type NetworkDataConnectionUpdate struct { + NetworkData + RxBytes int64 `json:"rx_bytes"` // 0 + RxPackets int64 `json:"rx_packets"` // 1 + TxBytes int64 `json:"tx_bytes"` // 2 + TxPackets int64 `json:"tx_packets"` // 3 + RxDups int64 `json:"rx_dups,omitempty"` // 4 + RxOOO int64 `json:"rx_000,omitempty"` // 5 + TxRetx int64 `json:"tx_retx,omitempty"` // 6 + MinRTT int64 `json:"min_rtt,omitempty"` // 7 + AvgRTT int64 `json:"avg_rtt,omitempty"` // 8 + ConnectionSerial int64 `json:"connection_serial"` // 9 } func (c *perfdClient) startGetFPS(ctx context.Context) ( @@ -343,37 +442,22 @@ func (c *perfdClient) parseFPS(data interface{}) { fpsInfo.FPS = int(convert2Int64(raw["CoreAnimationFramesPerSecond"])) } -func (c *perfdClient) registerGraphicsOpengl(ctx context.Context) ( +func (c *perfdClient) startGetGPU(ctx context.Context) ( cancel context.CancelFunc, err error) { - chanID, err := c.i.requestChannel(instrumentsServiceGraphicsOpengl) - if err != nil { - return nil, err - } - - selector := "availableStatistics" - args := libimobiledevice.NewAuxBuffer() - if _, err = c.i.client.Invoke(selector, args, chanID, true); err != nil { - return nil, err - } - - selector = "setSamplingRate:" - if err = args.AppendObject(0.0); err != nil { - return nil, err - } - if _, err = c.i.client.Invoke(selector, args, chanID, true); err != nil { + if _, err = c.i.call( + instrumentsServiceGraphicsOpengl, + "setSamplingRate:", + float64(1000)/100, // TODO: make it configurable + ); err != nil { return nil, err } - selector = "startSamplingAtTimeInterval:processIdentifier:" - args = libimobiledevice.NewAuxBuffer() - if err = args.AppendObject(0); err != nil { - return nil, err - } - if err = args.AppendObject(0); err != nil { - return nil, err - } - if _, err = c.i.client.Invoke(selector, args, chanID, true); err != nil { + if _, err = c.i.call( + instrumentsServiceGraphicsOpengl, + "startSamplingAtTimeInterval:", + 0, + ); err != nil { return nil, err } @@ -383,19 +467,19 @@ func (c *perfdClient) registerGraphicsOpengl(ctx context.Context) ( case <-ctx.Done(): return default: - c.parseGpuFps(m.Obj) + c.parseGPU(m.Obj) } }) return } -func (c *perfdClient) parseGpuFps(data interface{}) { +func (c *perfdClient) parseGPU(data interface{}) { // data example: // map[ // Alloc system memory:50167808 // Allocated PB Size:1179648 - // CoreAnimationFramesPerSecond:0 // fps from GPU + // CoreAnimationFramesPerSecond:0 // Device Utilization %:0 // device // IOGLBundleName:Built-In // In use system memory:10633216 @@ -407,31 +491,19 @@ func (c *perfdClient) parseGpuFps(data interface{}) { // recoveryCount:0 // ] - timestamp := time.Now().Unix() gpuInfo := GPUData{ Type: "gpu", - TimeStamp: timestamp, - } - fpsInfo := FPSData{ - Type: "fps", - TimeStamp: timestamp, + TimeStamp: time.Now().Unix(), } defer func() { - if c.option.gpu { - gpuBytes, _ := json.Marshal(gpuInfo) - c.chanGPU <- gpuBytes - } - if c.option.fps { - fpsBytes, _ := json.Marshal(fpsInfo) - c.chanFPS <- fpsBytes - } + gpuBytes, _ := json.Marshal(gpuInfo) + c.chanGPU <- gpuBytes }() raw, ok := data.(map[string]interface{}) if !ok { gpuInfo.Msg = fmt.Sprintf("invalid graphics.opengl data: %v", data) - fpsInfo.Msg = fmt.Sprintf("invalid graphics.opengl data: %v", data) return } @@ -439,9 +511,6 @@ func (c *perfdClient) parseGpuFps(data interface{}) { gpuInfo.DeviceUtilization = convert2Int64(raw["Device Utilization %"]) gpuInfo.TilerUtilization = convert2Int64(raw["Tiler Utilization %"]) gpuInfo.RendererUtilization = convert2Int64(raw["Renderer Utilization %"]) - - // fps - fpsInfo.FPS = int(convert2Int64(raw["CoreAnimationFramesPerSecond"])) } type GPUData struct { @@ -682,6 +751,8 @@ func convertProcessData(procData interface{}) map[string]interface{} { func convert2Int64(num interface{}) int64 { switch value := num.(type) { + case int64: + return value case uint64: return int64(value) case uint32: From cd1e806be6fd548a8756b98aa68fecf6671fad73 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 7 Oct 2022 14:22:59 +0800 Subject: [PATCH 11/20] change: add stop for callback --- perfd.go | 106 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/perfd.go b/perfd.go index 8c71571..fcd7ef0 100644 --- a/perfd.go +++ b/perfd.go @@ -213,6 +213,7 @@ func (c *perfdClient) registerNetworking(ctx context.Context) ( c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { select { case <-ctx.Done(): + c.i.call(instrumentsServiceNetworking, "stopMonitoring") return default: c.parseNetworking(m.Obj) @@ -237,7 +238,7 @@ func (c *perfdClient) parseNetworking(data interface{}) { // ['InterfaceIndex', "Name"] // e.g. [0, [14, 'en0']] netData := NetworkDataInterfaceDetection{ - NetworkData: NetworkData{ + PerfDataBase: PerfDataBase{ Type: "network-interface-detection", TimeStamp: time.Now().Unix(), }, @@ -262,7 +263,7 @@ func (c *perfdClient) parseNetworking(data interface{}) { fmt.Printf("parse remote socket address err: %v\n", err) } netData := NetworkDataConnectionDetected{ - NetworkData: NetworkData{ + PerfDataBase: PerfDataBase{ Type: "network-connection-detected", TimeStamp: time.Now().Unix(), }, @@ -282,7 +283,7 @@ func (c *perfdClient) parseNetworking(data interface{}) { // 'RxDups', 'RxOOO', 'TxRetx', 'MinRTT', 'AvgRTT', 'ConnectionSerial'] // e.g. [2, [21, 1708, 22, 14119, 309, 0, 5830, 0.076125, 0.076125, 54, -1]] netData := NetworkDataConnectionUpdate{ - NetworkData: NetworkData{ + PerfDataBase: PerfDataBase{ Type: "network-connection-update", TimeStamp: time.Now().Unix(), }, @@ -333,21 +334,22 @@ func parseSocketAddr(data []byte) (string, error) { return "", fmt.Errorf("invalid socket address: %v", data) } -type NetworkData struct { - Type string `json:"type"` // network +type PerfDataBase struct { + Type string `json:"type"` TimeStamp int64 `json:"timestamp"` + Msg string `json:"msg,omitempty"` // message for invalid data } // network-interface-detection type NetworkDataInterfaceDetection struct { - NetworkData + PerfDataBase InterfaceIndex int64 `json:"interface_index"` // 0 Name string `json:"name"` // 1 } // network-connection-detected type NetworkDataConnectionDetected struct { - NetworkData + PerfDataBase LocalAddress string `json:"local_address"` // 0 RemoteAddress string `json:"remote_address"` // 1 InterfaceIndex int64 `json:"interface_index"` // 2 @@ -360,7 +362,7 @@ type NetworkDataConnectionDetected struct { // network-connection-update type NetworkDataConnectionUpdate struct { - NetworkData + PerfDataBase RxBytes int64 `json:"rx_bytes"` // 0 RxPackets int64 `json:"rx_packets"` // 1 TxBytes int64 `json:"tx_bytes"` // 2 @@ -396,6 +398,7 @@ func (c *perfdClient) startGetFPS(ctx context.Context) ( c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { select { case <-ctx.Done(): + c.i.call(instrumentsServiceGraphicsOpengl, "stopSampling") return default: c.parseFPS(m.Obj) @@ -423,8 +426,10 @@ func (c *perfdClient) parseFPS(data interface{}) { // ] fpsInfo := FPSData{ - Type: "fps", - TimeStamp: time.Now().Unix(), + PerfDataBase: PerfDataBase{ + Type: "fps", + TimeStamp: time.Now().Unix(), + }, } defer func() { @@ -465,6 +470,7 @@ func (c *perfdClient) startGetGPU(ctx context.Context) ( c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { select { case <-ctx.Done(): + c.i.call(instrumentsServiceGraphicsOpengl, "stopSampling") return default: c.parseGPU(m.Obj) @@ -492,8 +498,10 @@ func (c *perfdClient) parseGPU(data interface{}) { // ] gpuInfo := GPUData{ - Type: "gpu", - TimeStamp: time.Now().Unix(), + PerfDataBase: PerfDataBase{ + Type: "gpu", + TimeStamp: time.Now().Unix(), + }, } defer func() { @@ -514,31 +522,21 @@ func (c *perfdClient) parseGPU(data interface{}) { } type GPUData struct { - Type string `json:"type"` // gpu - TimeStamp int64 `json:"timestamp"` - TilerUtilization int64 `json:"tiler_utilization"` // 处理顶点的 GPU 时间占比 - DeviceUtilization int64 `json:"device_utilization"` // 设备利用率 - RendererUtilization int64 `json:"renderer_utilization"` // 渲染器利用率 - Msg string `json:"msg,omitempty"` // message for invalid data + PerfDataBase // gpu + TilerUtilization int64 `json:"tiler_utilization"` // 处理顶点的 GPU 时间占比 + DeviceUtilization int64 `json:"device_utilization"` // 设备利用率 + RendererUtilization int64 `json:"renderer_utilization"` // 渲染器利用率 } type FPSData struct { - Type string `json:"type"` // fps - TimeStamp int64 `json:"timestamp"` - FPS int `json:"fps"` - Msg string `json:"msg,omitempty"` // message for invalid data + PerfDataBase // fps + FPS int `json:"fps"` } func (c *perfdClient) registerSysmontap(pid string, ctx context.Context) ( cancel context.CancelFunc, err error) { - chanID, err := c.i.requestChannel(instrumentsServiceSysmontap) - if err != nil { - return nil, err - } - // set config - args := libimobiledevice.NewAuxBuffer() config := map[string]interface{}{ "bm": 0, "cpuUsage": true, @@ -562,14 +560,19 @@ func (c *perfdClient) registerSysmontap(pid string, ctx context.Context) ( "physMemSize", }, } - args.AppendObject(config) - if _, err = c.i.client.Invoke("setConfig:", args, chanID, true); err != nil { + if _, err = c.i.call( + instrumentsServiceSysmontap, + "setConfig:", + config, + ); err != nil { return nil, err } // start - args = libimobiledevice.NewAuxBuffer() - if _, err = c.i.client.Invoke("start", args, chanID, false); err != nil { + if _, err = c.i.call( + instrumentsServiceSysmontap, + "start", + ); err != nil { return nil, err } @@ -578,26 +581,31 @@ func (c *perfdClient) registerSysmontap(pid string, ctx context.Context) ( c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { select { case <-ctx.Done(): + c.i.call(instrumentsServiceSysmontap, "stop") return default: - c.parseCPUMem(m.Obj, pid) + c.parseSysmontap(m.Obj, pid) } }) return cancel, err } -func (c *perfdClient) parseCPUMem(data interface{}, pid string) { +func (c *perfdClient) parseSysmontap(data interface{}, pid string) { timestamp := time.Now().Unix() cpuInfo := CPUData{ - Type: "cpu", - TimeStamp: timestamp, - ProcPID: pid, + PerfDataBase: PerfDataBase{ + Type: "cpu", + TimeStamp: timestamp, + }, + ProcPID: pid, } memInfo := MemData{ - Type: "mem", - TimeStamp: timestamp, - ProcPID: pid, + PerfDataBase: PerfDataBase{ + Type: "mem", + TimeStamp: timestamp, + }, + ProcPID: pid, } defer func() { @@ -696,9 +704,7 @@ func (c *perfdClient) parseCPUMem(data interface{}, pid string) { } type CPUData struct { - Type string `json:"type"` // cpu - TimeStamp int64 `json:"timestamp"` - Msg string `json:"msg,omitempty"` // message for invalid data + PerfDataBase // cpu // system CPUCount int `json:"cpu_count"` // CPU总数 SysCPUUsageTotalLoad float64 `json:"sys_cpu_usage"` // 系统总体CPU占用 @@ -710,14 +716,12 @@ type CPUData struct { } type MemData struct { - Type string `json:"type"` // mem - TimeStamp int64 `json:"timestamp"` - Anon int64 `json:"anon"` // 虚拟内存 - PhysMemory int64 `json:"phys_memory"` // 物理内存 - Rss int64 `json:"rss"` // 总内存 - Vss int64 `json:"vss"` // 虚拟内存 - ProcPID string `json:"pid"` // 进程 PID - Msg string `json:"msg,omitempty"` // message for invalid data + PerfDataBase // mem + Anon int64 `json:"anon"` // 虚拟内存 + PhysMemory int64 `json:"phys_memory"` // 物理内存 + Rss int64 `json:"rss"` // 总内存 + Vss int64 `json:"vss"` // 虚拟内存 + ProcPID string `json:"pid"` // 进程 PID } func convertProcessData(procData interface{}) map[string]interface{} { From c0647d7f641ddf1e75a2da36738eacc4a8c09f7f Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 7 Oct 2022 20:46:30 +0800 Subject: [PATCH 12/20] feat: system monitor for cpu/mem/disk/network --- perfd.go | 425 +++++++++++++++++++++++++++----------------------- perfd_test.go | 30 ++-- 2 files changed, 246 insertions(+), 209 deletions(-) diff --git a/perfd.go b/perfd.go index fcd7ef0..07dc177 100644 --- a/perfd.go +++ b/perfd.go @@ -13,54 +13,72 @@ import ( ) type perfOption struct { + // system + sysCPU bool + sysMem bool + sysDisk bool + sysNetwork bool + gpu bool + fps bool + network bool + // process bundleID string pid string - gpu bool - cpu bool - mem bool - fps bool - network bool } func defaulPerfOption() *perfOption { return &perfOption{ - gpu: false, - cpu: true, // default on - mem: true, // default on - fps: false, - network: false, + sysCPU: true, // default on + sysMem: true, // default on + sysDisk: false, + sysNetwork: false, + gpu: false, + fps: false, + network: false, } } type PerfOption func(*perfOption) -func WithPerfBundleID(bundleID string) PerfOption { +func WithPerfSystemCPU(b bool) PerfOption { return func(opt *perfOption) { - opt.bundleID = bundleID + opt.sysCPU = b } } -func WithPerfPID(pid string) PerfOption { +func WithPerfSystemMem(b bool) PerfOption { return func(opt *perfOption) { - opt.pid = pid + opt.sysMem = b } } -func WithPerfGPU(b bool) PerfOption { +func WithPerfSystemDisk(b bool) PerfOption { return func(opt *perfOption) { - opt.gpu = b + opt.sysDisk = b + } +} + +func WithPerfSystemNetwork(b bool) PerfOption { + return func(opt *perfOption) { + opt.sysNetwork = b + } +} + +func WithPerfBundleID(bundleID string) PerfOption { + return func(opt *perfOption) { + opt.bundleID = bundleID } } -func WithPerfCPU(b bool) PerfOption { +func WithPerfPID(pid string) PerfOption { return func(opt *perfOption) { - opt.cpu = b + opt.pid = pid } } -func WithPerfMem(b bool) PerfOption { +func WithPerfGPU(b bool) PerfOption { return func(opt *perfOption) { - opt.mem = b + opt.gpu = b } } @@ -77,15 +95,17 @@ func WithPerfNetwork(b bool) PerfOption { } type perfdClient struct { - option *perfOption - i *instruments - stop chan struct{} // used to stop perf client - cancels []context.CancelFunc // used to cancel all iterators - chanCPU chan []byte // cpu channel - chanMem chan []byte // mem channel - chanGPU chan []byte // gpu channel - chanFPS chan []byte // fps channel - chanNetwork chan []byte // network channel + option *perfOption + i *instruments + stop chan struct{} // used to stop perf client + cancels []context.CancelFunc // used to cancel all iterators + chanSysCPU chan []byte // system cpu channel + chanSysMem chan []byte // system mem channel + chanSysDisk chan []byte // system disk channel + chanSysNetwork chan []byte // system network channel + chanGPU chan []byte // gpu channel + chanFPS chan []byte // fps channel + chanNetwork chan []byte // network channel } func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient { @@ -102,22 +122,24 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient } return &perfdClient{ - i: i.(*instruments), - option: perfOption, - stop: make(chan struct{}), - chanCPU: make(chan []byte, 10), - chanMem: make(chan []byte, 10), - chanGPU: make(chan []byte, 10), - chanFPS: make(chan []byte, 10), - chanNetwork: make(chan []byte, 10), + i: i.(*instruments), + option: perfOption, + stop: make(chan struct{}), + chanSysCPU: make(chan []byte, 10), + chanSysMem: make(chan []byte, 10), + chanSysDisk: make(chan []byte, 10), + chanSysNetwork: make(chan []byte, 10), + chanGPU: make(chan []byte, 10), + chanFPS: make(chan []byte, 10), + chanNetwork: make(chan []byte, 10), } } func (c *perfdClient) Start() (data <-chan []byte, err error) { outCh := make(chan []byte, 100) - if c.option.cpu || c.option.mem { - cancel, err := c.registerSysmontap(c.option.pid, context.Background()) + if c.option.sysCPU || c.option.sysMem || c.option.sysDisk || c.option.sysNetwork { + cancel, err := c.registerSystemMonitor(context.Background()) if err != nil { return nil, err } @@ -153,29 +175,32 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { select { case <-c.stop: return - case cpuBytes, ok := <-c.chanCPU: + case cpuBytes, ok := <-c.chanSysCPU: if ok { - fmt.Println("cpu: ", string(cpuBytes)) outCh <- cpuBytes } - case memBytes, ok := <-c.chanMem: + case memBytes, ok := <-c.chanSysMem: if ok { - fmt.Println("mem: ", string(memBytes)) outCh <- memBytes } + case diskBytes, ok := <-c.chanSysDisk: + if ok { + outCh <- diskBytes + } + case networkBytes, ok := <-c.chanSysNetwork: + if ok { + outCh <- networkBytes + } case gpuBytes, ok := <-c.chanGPU: if ok { - fmt.Println("gpu: ", string(gpuBytes)) outCh <- gpuBytes } case fpsBytes, ok := <-c.chanFPS: if ok { - fmt.Println("fps: ", string(fpsBytes)) outCh <- fpsBytes } case networkBytes, ok := <-c.chanNetwork: if ok { - fmt.Println("network: ", string(networkBytes)) outCh <- networkBytes } } @@ -533,7 +558,30 @@ type FPSData struct { FPS int `json:"fps"` } -func (c *perfdClient) registerSysmontap(pid string, ctx context.Context) ( +var systemAttributes = []string{ + "threadCount", + // disk + "diskBytesRead", + "diskBytesWritten", + "diskReadOps", + "diskWriteOps", + // memory + "vmCompressorPageCount", + "vmExtPageCount", + "vmFreeCount", + "vmIntPageCount", + "vmPurgeableCount", + "vmWireCount", + "vmUsedCount", + "__vmSwapUsage", + // network + "netBytesIn", + "netBytesOut", + "netPacketsIn", + "netPacketsOut", +} + +func (c *perfdClient) registerSystemMonitor(ctx context.Context) ( cancel context.CancelFunc, err error) { // set config @@ -543,22 +591,10 @@ func (c *perfdClient) registerSysmontap(pid string, ctx context.Context) ( "sampleInterval": time.Second * 1, // 1s "ur": 1000, // 刷新频率 "procAttrs": []string{ - "memVirtualSize", // vss - "cpuUsage", - "ctxSwitch", // the number of context switches by process each second - "intWakeups", // the number of threads wakeups by process each second - "physFootprint", // real memory (物理内存) - "memResidentSize", // rss - "memAnon", // anonymous memory + "name", "pid", }, - "sysAttrs": []string{ // 系统信息字段 - "vmExtPageCount", - "vmFreeCount", - "vmPurgeableCount", - "vmSpeculativeCount", - "physMemSize", - }, + "sysAttrs": systemAttributes, // system performance } if _, err = c.i.call( instrumentsServiceSysmontap, @@ -584,173 +620,172 @@ func (c *perfdClient) registerSysmontap(pid string, ctx context.Context) ( c.i.call(instrumentsServiceSysmontap, "stop") return default: - c.parseSysmontap(m.Obj, pid) + c.parseSystemMonitor(m.Obj) } }) return cancel, err } -func (c *perfdClient) parseSysmontap(data interface{}, pid string) { - timestamp := time.Now().Unix() - cpuInfo := CPUData{ - PerfDataBase: PerfDataBase{ - Type: "cpu", - TimeStamp: timestamp, - }, - ProcPID: pid, - } - memInfo := MemData{ - PerfDataBase: PerfDataBase{ - Type: "mem", - TimeStamp: timestamp, - }, - ProcPID: pid, - } - - defer func() { - if c.option.cpu { - cpuBytes, _ := json.Marshal(cpuInfo) - c.chanCPU <- cpuBytes - } - if c.option.mem { - memBytes, _ := json.Marshal(memInfo) - c.chanMem <- memBytes - } - }() - - messArray, ok := data.([]interface{}) - if !ok || len(messArray) != 2 { - cpuInfo.Msg = fmt.Sprintf("invalid sysmontap data: %v", data) - memInfo.Msg = fmt.Sprintf("invalid sysmontap data: %v", data) +func (c *perfdClient) parseSystemMonitor(data interface{}) { + dataArray, ok := data.([]interface{}) + if !ok || len(dataArray) != 2 { return } - var systemInfo = messArray[0].(map[string]interface{}) - var processInfoList = messArray[1].(map[string]interface{}) - if systemInfo["CPUCount"] == nil { - systemInfo, processInfoList = processInfoList, systemInfo - } - if systemInfo["CPUCount"] == nil { - cpuInfo.Msg = fmt.Sprintf("invalid system info: %v", systemInfo) - return + timestamp := time.Now().Unix() + var systemInfo map[string]interface{} + data1 := dataArray[0].(map[string]interface{}) + data2 := dataArray[1].(map[string]interface{}) + if _, ok := data1["SystemCPUUsage"]; ok { + systemInfo = data1 + } else { + systemInfo = data2 } + // systemInfo example: // map[ // CPUCount:2 // EnabledCPUs:2 // PerCPUUsage:[ - // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:4.587155963302749 CPU_UserLoad:-1] - // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:0.9174311926605441 CPU_UserLoad:-1] + // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:3.9215686274509807 CPU_UserLoad:-1] + // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:11.650485436893206 CPU_UserLoad:-1]] // ] - // System:[70117 2850 465 1579 128643] + // System:[704211 35486281728 6303789056 3001119 1001 11033 52668 1740 40022 2114 17310 126903 1835008 160323 107909856 95067 95808179] // SystemCPUUsage:map[ // CPU_NiceLoad:0 // CPU_SystemLoad:-1 - // CPU_TotalLoad:5.504587155963293 + // CPU_TotalLoad:15.572054064344186 // CPU_UserLoad:-1 // ] - // StartMachAbsTime:2514085834016 - // EndMachAbsTime:2514111855034 + // StartMachAbsTime:5339240248449 + // EndMachAbsTime:5339264441260 // Type:41 // ] - var cpuCount = systemInfo["CPUCount"] - var sysCpuUsage = systemInfo["SystemCPUUsage"].(map[string]interface{}) - if processInfoList["Processes"] == nil { - cpuInfo.Msg = fmt.Sprintf("invalid process info list: %v", processInfoList) - memInfo.Msg = fmt.Sprintf("invalid process info list: %v", processInfoList) - return + if c.option.sysCPU { + sysCPUUsage := systemInfo["SystemCPUUsage"].(map[string]interface{}) + sysCPUInfo := SystemCPUData{ + PerfDataBase: PerfDataBase{ + Type: "sys_cpu", + TimeStamp: timestamp, + }, + NiceLoad: sysCPUUsage["CPU_NiceLoad"].(float64), + SystemLoad: sysCPUUsage["CPU_SystemLoad"].(float64), + TotalLoad: sysCPUUsage["CPU_TotalLoad"].(float64), + UserLoad: sysCPUUsage["CPU_UserLoad"].(float64), + } + cpuBytes, _ := json.Marshal(sysCPUInfo) + c.chanSysCPU <- cpuBytes } - // processInfoList example: - // map[ - // Processes:map[ - // 0:[108940918784 0.35006396059439243 11867680 6069179 147456 294600704 167346176 0] - // 100:[417741438976 0 65418 21019 1819088 7045120 1671168 100] - // 107:[417746960384 0.06996075980775063 71187 21226 3342800 9420800 3178496 107] - // ] - // StartMachAbsTime:2514086593642 - // EndMachAbsTime:2514112708690 - // Type:5 - // ] - // cpu - cpuInfo.CPUCount = int(cpuCount.(uint64)) - cpuInfo.SysCPUUsageTotalLoad = sysCpuUsage["CPU_TotalLoad"].(float64) - - processes := processInfoList["Processes"].(map[string]interface{}) - procData, ok := processes[pid] - processInfo := convertProcessData(procData) - if ok && processInfo != nil { - // cpu - cpuInfo.ProcCPUUsage = processInfo["cpuUsage"].(float64) - cpuInfo.ProcAttrCtxSwitch = convert2Int64(processInfo["ctxSwitch"]) - cpuInfo.ProcAttrIntWakeups = convert2Int64(processInfo["intWakeups"]) - // mem - memInfo.Vss = convert2Int64(processInfo["memVirtualSize"]) - memInfo.Rss = convert2Int64(processInfo["memResidentSize"]) - memInfo.Anon = convert2Int64(processInfo["memAnon"]) - memInfo.PhysMemory = convert2Int64(processInfo["physFootprint"]) - } else { - // cpu - cpuInfo.Msg = fmt.Sprintf("pid %s not found", pid) - // mem - memInfo.Msg = fmt.Sprintf("pid %s not found", pid) - memInfo.Vss = -1 - memInfo.Rss = -1 - memInfo.Anon = -1 - memInfo.PhysMemory = -1 + systemAttributesValue := systemInfo["System"].([]interface{}) + systemAttributesMap := make(map[string]int64) + for idx, value := range systemAttributes { + systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx]) + } + + if c.option.sysMem { + kernelPageSize := int64(1) // why 16384 ? + appMemory := (systemAttributesMap["vmIntPageCount"] - systemAttributesMap["vmPurgeableCount"]) * kernelPageSize + cachedFiles := (systemAttributesMap["vmExtPageCount"] - systemAttributesMap["vmPurgeableCount"]) * kernelPageSize + compressed := systemAttributesMap["vmCompressorPageCount"] * kernelPageSize + usedMemory := (systemAttributesMap["vmUsedCount"] - systemAttributesMap["vmExtPageCount"]) * kernelPageSize + wiredMemory := systemAttributesMap["vmWireCount"] * kernelPageSize + swapUsed := systemAttributesMap["__vmSwapUsage"] + freeMemory := systemAttributesMap["vmFreeCount"] * kernelPageSize + + sysMemInfo := SystemMemData{ + PerfDataBase: PerfDataBase{ + Type: "sys_mem", + TimeStamp: timestamp, + }, + AppMemory: appMemory, + UsedMemory: usedMemory, + WiredMemory: wiredMemory, + FreeMemory: freeMemory, + CachedFiles: cachedFiles, + Compressed: compressed, + SwapUsed: swapUsed, + } + memBytes, _ := json.Marshal(sysMemInfo) + c.chanSysMem <- memBytes + } + + if c.option.sysDisk { + diskBytesRead := systemAttributesMap["diskBytesRead"] + diskBytesWritten := systemAttributesMap["diskBytesWritten"] + diskReadOps := systemAttributesMap["diskReadOps"] + diskWriteOps := systemAttributesMap["diskWriteOps"] + + sysDiskInfo := SystemDiskData{ + PerfDataBase: PerfDataBase{ + Type: "sys_disk", + TimeStamp: timestamp, + }, + DataRead: diskBytesRead, + DataWritten: diskBytesWritten, + ReadOps: diskReadOps, + WriteOps: diskWriteOps, + } + diskBytes, _ := json.Marshal(sysDiskInfo) + c.chanSysDisk <- diskBytes + } + + if c.option.sysNetwork { + netBytesIn := systemAttributesMap["netBytesIn"] + netBytesOut := systemAttributesMap["netBytesOut"] + netPacketsIn := systemAttributesMap["netPacketsIn"] + netPacketsOut := systemAttributesMap["netPacketsOut"] + + sysNetworkInfo := SystemNetworkData{ + PerfDataBase: PerfDataBase{ + Type: "sys_network", + TimeStamp: timestamp, + }, + BytesIn: netBytesIn, + BytesOut: netBytesOut, + PacketsIn: netPacketsIn, + PacketsOut: netPacketsOut, + } + networkBytes, _ := json.Marshal(sysNetworkInfo) + c.chanSysNetwork <- networkBytes } } -type CPUData struct { - PerfDataBase // cpu - // system - CPUCount int `json:"cpu_count"` // CPU总数 - SysCPUUsageTotalLoad float64 `json:"sys_cpu_usage"` // 系统总体CPU占用 - // process - ProcPID string `json:"pid"` // 进程 PID - ProcCPUUsage float64 `json:"proc_cpu_usage,omitempty"` // 单个进程的CPU使用率 - ProcAttrCtxSwitch int64 `json:"proc_attr_ctx_switch,omitempty"` // 上下文切换数 - ProcAttrIntWakeups int64 `json:"proc_attr_int_wakeups,omitempty"` // 唤醒数 +type SystemCPUData struct { + PerfDataBase // system cpu + NiceLoad float64 `json:"nice_load"` + SystemLoad float64 `json:"system_load"` + TotalLoad float64 `json:"total_load"` + UserLoad float64 `json:"user_load"` } -type MemData struct { - PerfDataBase // mem - Anon int64 `json:"anon"` // 虚拟内存 - PhysMemory int64 `json:"phys_memory"` // 物理内存 - Rss int64 `json:"rss"` // 总内存 - Vss int64 `json:"vss"` // 虚拟内存 - ProcPID string `json:"pid"` // 进程 PID +type SystemMemData struct { + PerfDataBase // mem + AppMemory int64 `json:"app_memory"` + FreeMemory int64 `json:"free_memory"` + UsedMemory int64 `json:"used_memory"` + WiredMemory int64 `json:"wired_memory"` + CachedFiles int64 `json:"cached_files"` + Compressed int64 `json:"compressed"` + SwapUsed int64 `json:"swap_used"` } -func convertProcessData(procData interface{}) map[string]interface{} { - if procData == nil { - return nil - } - procDataArray, ok := procData.([]interface{}) - if !ok { - return nil - } - if len(procDataArray) != 8 { - return nil - } - - // procDataArray example: - // [417741438976 0 65418 21019 1819088 7045120 1671168 100] - // corresponds to procAttrs: - // ["memVirtualSize", "cpuUsage", "ctxSwitch", "intWakeups", - // "physFootprint", "memResidentSize", "memAnon", "pid"] - return map[string]interface{}{ - "memVirtualSize": procDataArray[0], - "cpuUsage": procDataArray[1], - "ctxSwitch": procDataArray[2], - "intWakeups": procDataArray[3], - "physFootprint": procDataArray[4], - "memResidentSize": procDataArray[5], - "memAnon": procDataArray[6], - "PID": procDataArray[7], - } +type SystemDiskData struct { + PerfDataBase // disk + DataRead int64 `json:"data_read"` + DataWritten int64 `json:"data_written"` + ReadOps int64 `json:"reads_in"` + WriteOps int64 `json:"writes_out"` +} + +type SystemNetworkData struct { + PerfDataBase // network + BytesIn int64 `json:"bytes_in"` + BytesOut int64 `json:"bytes_out"` + PacketsIn int64 `json:"packets_in"` + PacketsOut int64 `json:"packets_out"` } func convert2Int64(num interface{}) int64 { diff --git a/perfd_test.go b/perfd_test.go index 626ed6f..708b731 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -1,17 +1,19 @@ package giDevice import ( + "fmt" "testing" "time" ) -func TestPerfCPUMem(t *testing.T) { +func TestPerfSystemMonitor(t *testing.T) { setupLockdownSrv(t) data, err := dev.PerfStart( - WithPerfCPU(true), - WithPerfMem(true), - WithPerfBundleID("com.apple.mobilesafari"), + WithPerfSystemCPU(true), + WithPerfSystemMem(true), + WithPerfSystemDisk(true), + WithPerfSystemNetwork(true), ) if err != nil { t.Fatal(err) @@ -24,7 +26,7 @@ func TestPerfCPUMem(t *testing.T) { dev.PerfStop() return case d := <-data: - t.Log(string(d)) + fmt.Println(string(d)) } } } @@ -33,8 +35,8 @@ func TestPerfGPU(t *testing.T) { setupLockdownSrv(t) data, err := dev.PerfStart( - WithPerfCPU(false), - WithPerfMem(false), + WithPerfSystemCPU(false), + WithPerfSystemMem(false), WithPerfGPU(true), ) if err != nil { @@ -48,7 +50,7 @@ func TestPerfGPU(t *testing.T) { dev.PerfStop() return case d := <-data: - t.Log(string(d)) + fmt.Println(string(d)) } } } @@ -57,8 +59,8 @@ func TestPerfFPS(t *testing.T) { setupLockdownSrv(t) data, err := dev.PerfStart( - WithPerfCPU(false), - WithPerfMem(false), + WithPerfSystemCPU(false), + WithPerfSystemMem(false), WithPerfFPS(true), ) if err != nil { @@ -72,7 +74,7 @@ func TestPerfFPS(t *testing.T) { dev.PerfStop() return case d := <-data: - t.Log(string(d)) + fmt.Println(string(d)) } } } @@ -81,8 +83,8 @@ func TestPerfNetwork(t *testing.T) { setupLockdownSrv(t) data, err := dev.PerfStart( - WithPerfCPU(false), - WithPerfMem(false), + WithPerfSystemCPU(false), + WithPerfSystemMem(false), WithPerfNetwork(true), ) if err != nil { @@ -96,7 +98,7 @@ func TestPerfNetwork(t *testing.T) { dev.PerfStop() return case d := <-data: - t.Log(string(d)) + fmt.Println(string(d)) } } } From 999cc83c7d57736ca7f630992cd48fc4bbebdb80 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 7 Oct 2022 21:13:30 +0800 Subject: [PATCH 13/20] feat: config output interval time --- perfd.go | 31 ++++++++++++++++++++----------- perfd_test.go | 1 + 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/perfd.go b/perfd.go index 07dc177..e5c015e 100644 --- a/perfd.go +++ b/perfd.go @@ -24,17 +24,20 @@ type perfOption struct { // process bundleID string pid string + // config + outputInterval int // ms } func defaulPerfOption() *perfOption { return &perfOption{ - sysCPU: true, // default on - sysMem: true, // default on - sysDisk: false, - sysNetwork: false, - gpu: false, - fps: false, - network: false, + sysCPU: true, // default on + sysMem: true, // default on + sysDisk: false, + sysNetwork: false, + gpu: false, + fps: false, + network: false, + outputInterval: 1000, // default 1000ms } } @@ -94,6 +97,12 @@ func WithPerfNetwork(b bool) PerfOption { } } +func WithPerfOutputInterval(intervalMilliseconds int) PerfOption { + return func(opt *perfOption) { + opt.outputInterval = intervalMilliseconds + } +} + type perfdClient struct { option *perfOption i *instruments @@ -406,7 +415,7 @@ func (c *perfdClient) startGetFPS(ctx context.Context) ( if _, err = c.i.call( instrumentsServiceGraphicsOpengl, "setSamplingRate:", - float64(1000)/100, // TODO: make it configurable + float64(c.option.outputInterval)/100, ); err != nil { return nil, err } @@ -478,7 +487,7 @@ func (c *perfdClient) startGetGPU(ctx context.Context) ( if _, err = c.i.call( instrumentsServiceGraphicsOpengl, "setSamplingRate:", - float64(1000)/100, // TODO: make it configurable + float64(c.option.outputInterval)/100, ); err != nil { return nil, err } @@ -588,8 +597,8 @@ func (c *perfdClient) registerSystemMonitor(ctx context.Context) ( config := map[string]interface{}{ "bm": 0, "cpuUsage": true, - "sampleInterval": time.Second * 1, // 1s - "ur": 1000, // 刷新频率 + "sampleInterval": time.Second * 1, // 1s + "ur": c.option.outputInterval, // 输出频率 "procAttrs": []string{ "name", "pid", diff --git a/perfd_test.go b/perfd_test.go index 708b731..86d0efe 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -14,6 +14,7 @@ func TestPerfSystemMonitor(t *testing.T) { WithPerfSystemMem(true), WithPerfSystemDisk(true), WithPerfSystemNetwork(true), + WithPerfOutputInterval(1000), ) if err != nil { t.Fatal(err) From 7a8571721f7113755577b40d61639cf64fa12e12 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 7 Oct 2022 22:01:54 +0800 Subject: [PATCH 14/20] feat: config systemAttributes and processAttributes --- perfd.go | 172 ++++++++++++++++++++++++++++++++++++++------------ perfd_test.go | 24 +++++++ 2 files changed, 154 insertions(+), 42 deletions(-) diff --git a/perfd.go b/perfd.go index e5c015e..be70df9 100644 --- a/perfd.go +++ b/perfd.go @@ -22,10 +22,12 @@ type perfOption struct { fps bool network bool // process - bundleID string + bundleID string // TODO pid string // config - outputInterval int // ms + outputInterval int // ms + systemAttributes []string + processAttributes []string } func defaulPerfOption() *perfOption { @@ -38,6 +40,31 @@ func defaulPerfOption() *perfOption { fps: false, network: false, outputInterval: 1000, // default 1000ms + systemAttributes: []string{ + // disk + "diskBytesRead", + "diskBytesWritten", + "diskReadOps", + "diskWriteOps", + // memory + "vmCompressorPageCount", + "vmExtPageCount", + "vmFreeCount", + "vmIntPageCount", + "vmPurgeableCount", + "vmWireCount", + "vmUsedCount", + "__vmSwapUsage", + // network + "netBytesIn", + "netBytesOut", + "netPacketsIn", + "netPacketsOut", + }, + processAttributes: []string{ + "pid", + "cpuUsage", + }, } } @@ -73,9 +100,9 @@ func WithPerfBundleID(bundleID string) PerfOption { } } -func WithPerfPID(pid string) PerfOption { +func WithPerfPID(pid int) PerfOption { return func(opt *perfOption) { - opt.pid = pid + opt.pid = strconv.Itoa(pid) } } @@ -103,6 +130,18 @@ func WithPerfOutputInterval(intervalMilliseconds int) PerfOption { } } +func WithPerfProcessAttributes(attrs ...string) PerfOption { + return func(opt *perfOption) { + opt.processAttributes = attrs + } +} + +func WithPerfSystemAttributes(attrs ...string) PerfOption { + return func(opt *perfOption) { + opt.systemAttributes = attrs + } +} + type perfdClient struct { option *perfOption i *instruments @@ -115,6 +154,7 @@ type perfdClient struct { chanGPU chan []byte // gpu channel chanFPS chan []byte // fps channel chanNetwork chan []byte // network channel + chanProcess chan []byte // process channel } func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient { @@ -141,14 +181,15 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient chanGPU: make(chan []byte, 10), chanFPS: make(chan []byte, 10), chanNetwork: make(chan []byte, 10), + chanProcess: make(chan []byte, 10), } } func (c *perfdClient) Start() (data <-chan []byte, err error) { outCh := make(chan []byte, 100) - if c.option.sysCPU || c.option.sysMem || c.option.sysDisk || c.option.sysNetwork { - cancel, err := c.registerSystemMonitor(context.Background()) + if c.option.sysCPU || c.option.sysMem || c.option.sysDisk || c.option.sysNetwork || c.option.pid != "" { + cancel, err := c.registerSysmontap(context.Background()) if err != nil { return nil, err } @@ -212,6 +253,10 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { if ok { outCh <- networkBytes } + case processBytes, ok := <-c.chanProcess: + if ok { + outCh <- processBytes + } } } }() @@ -567,43 +612,17 @@ type FPSData struct { FPS int `json:"fps"` } -var systemAttributes = []string{ - "threadCount", - // disk - "diskBytesRead", - "diskBytesWritten", - "diskReadOps", - "diskWriteOps", - // memory - "vmCompressorPageCount", - "vmExtPageCount", - "vmFreeCount", - "vmIntPageCount", - "vmPurgeableCount", - "vmWireCount", - "vmUsedCount", - "__vmSwapUsage", - // network - "netBytesIn", - "netBytesOut", - "netPacketsIn", - "netPacketsOut", -} - -func (c *perfdClient) registerSystemMonitor(ctx context.Context) ( +func (c *perfdClient) registerSysmontap(ctx context.Context) ( cancel context.CancelFunc, err error) { // set config config := map[string]interface{}{ "bm": 0, "cpuUsage": true, - "sampleInterval": time.Second * 1, // 1s - "ur": c.option.outputInterval, // 输出频率 - "procAttrs": []string{ - "name", - "pid", - }, - "sysAttrs": systemAttributes, // system performance + "sampleInterval": time.Second * 1, // 1s + "ur": c.option.outputInterval, // 输出频率 + "procAttrs": c.option.processAttributes, // process performance + "sysAttrs": c.option.systemAttributes, // system performance } if _, err = c.i.call( instrumentsServiceSysmontap, @@ -629,19 +648,88 @@ func (c *perfdClient) registerSystemMonitor(ctx context.Context) ( c.i.call(instrumentsServiceSysmontap, "stop") return default: - c.parseSystemMonitor(m.Obj) + dataArray, ok := m.Obj.([]interface{}) + if !ok || len(dataArray) != 2 { + return + } + if c.option.pid != "" { + c.parseProcessData(dataArray) + } else { + c.parseSystemData(dataArray) + } } }) return cancel, err } -func (c *perfdClient) parseSystemMonitor(data interface{}) { - dataArray, ok := data.([]interface{}) - if !ok || len(dataArray) != 2 { +func (c *perfdClient) parseProcessData(dataArray []interface{}) { + // dataArray example: + // [ + // map[ + // CPUCount:2 + // EnabledCPUs:2 + // PerCPUUsage:[ + // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:3.6363636363636402 CPU_UserLoad:-1] + // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:2.7272727272727195 CPU_UserLoad:-1] + // ] + // System:[36408520704 6897049600 3031160 773697 15596 61940 1297 26942 588 17020 127346 1835008 119718056 107009899 174046 103548] + // SystemCPUUsage:map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:6.36363636363636 CPU_UserLoad:-1] + // StartMachAbsTime:5896602132889 + // EndMachAbsTime:5896628486761 + // Type:41 + // ] + // map[ + // Processes:map[ + // 0:[1.3582834340402803 0] + // 124:[0.011456702068519481 124] + // 136:[0.05468332721703649 136] + // ] + // StartMachAbsTime:5896602295095 + // EndMachAbsTime:5896628780514 + // Type:5 + // ] + // ] + + processData := make(map[string]interface{}) + processData["type"] = "process" + processData["timestamp"] = time.Now().Unix() + processData["pid"] = c.option.pid + + defer func() { + processBytes, _ := json.Marshal(processData) + c.chanProcess <- processBytes + }() + + systemInfo := dataArray[0].(map[string]interface{}) + processInfo := dataArray[1].(map[string]interface{}) + if _, ok := systemInfo["System"]; !ok { + systemInfo, processInfo = processInfo, systemInfo + } + + var targetProcessValue []interface{} + processList := processInfo["Processes"].(map[string]interface{}) + for pid, v := range processList { + if pid != c.option.pid { + continue + } + targetProcessValue = v.([]interface{}) + } + + if targetProcessValue == nil { + processData["msg"] = fmt.Sprintf("process %s not found", c.option.pid) return } + processAttributesMap := make(map[string]interface{}) + for idx, value := range c.option.processAttributes { + processAttributesMap[value] = convert2Int64(targetProcessValue[idx]) + } + + processData["attributes"] = processAttributesMap +} + +func (c *perfdClient) parseSystemData(dataArray []interface{}) { timestamp := time.Now().Unix() var systemInfo map[string]interface{} data1 := dataArray[0].(map[string]interface{}) @@ -690,7 +778,7 @@ func (c *perfdClient) parseSystemMonitor(data interface{}) { systemAttributesValue := systemInfo["System"].([]interface{}) systemAttributesMap := make(map[string]int64) - for idx, value := range systemAttributes { + for idx, value := range c.option.systemAttributes { systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx]) } diff --git a/perfd_test.go b/perfd_test.go index 86d0efe..82b738c 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -32,6 +32,30 @@ func TestPerfSystemMonitor(t *testing.T) { } } +func TestPerfProcessMonitor(t *testing.T) { + setupLockdownSrv(t) + + data, err := dev.PerfStart( + WithPerfProcessAttributes("pid", "cpuUsage", "memAnon"), + WithPerfOutputInterval(1000), + WithPerfPID(100), + ) + if err != nil { + t.Fatal(err) + } + + timer := time.NewTimer(time.Duration(time.Second * 10)) + for { + select { + case <-timer.C: + dev.PerfStop() + return + case d := <-data: + fmt.Println(string(d)) + } + } +} + func TestPerfGPU(t *testing.T) { setupLockdownSrv(t) From 4d7de653a797b920aaa7f08057505433e357f11b Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 7 Oct 2022 22:58:39 +0800 Subject: [PATCH 15/20] feat: get process performance --- perfd.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/perfd.go b/perfd.go index be70df9..44dacfa 100644 --- a/perfd.go +++ b/perfd.go @@ -725,8 +725,14 @@ func (c *perfdClient) parseProcessData(dataArray []interface{}) { for idx, value := range c.option.processAttributes { processAttributesMap[value] = convert2Int64(targetProcessValue[idx]) } + processData["proc_perf"] = processAttributesMap - processData["attributes"] = processAttributesMap + systemAttributesValue := systemInfo["System"].([]interface{}) + systemAttributesMap := make(map[string]int64) + for idx, value := range c.option.systemAttributes { + systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx]) + } + processData["sys_perf"] = systemAttributesMap } func (c *perfdClient) parseSystemData(dataArray []interface{}) { From 4f681249b430d208a6f5cee5b5aa2bc0aa26374f Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 8 Oct 2022 10:30:25 +0800 Subject: [PATCH 16/20] fix: ensure processAttributes contains pid --- perfd.go | 14 ++++++++++++++ perfd_test.go | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/perfd.go b/perfd.go index 44dacfa..a645262 100644 --- a/perfd.go +++ b/perfd.go @@ -163,6 +163,11 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient fn(perfOption) } + // processAttributes must contain pid, or it can't get process info, reason unknown + if !containString(perfOption.processAttributes, "pid") { + perfOption.processAttributes = append(perfOption.processAttributes, "pid") + } + if perfOption.bundleID != "" { pid, err := d.getPidByBundleID(perfOption.bundleID) if err == nil { @@ -909,3 +914,12 @@ func convert2Int64(num interface{}) int64 { fmt.Printf("convert2Int64 failed: %v, %T\n", num, num) return -1 } + +func containString(ss []string, s string) bool { + for _, v := range ss { + if s == v { + return true + } + } + return false +} diff --git a/perfd_test.go b/perfd_test.go index 82b738c..4d030f9 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -36,7 +36,7 @@ func TestPerfProcessMonitor(t *testing.T) { setupLockdownSrv(t) data, err := dev.PerfStart( - WithPerfProcessAttributes("pid", "cpuUsage", "memAnon"), + WithPerfProcessAttributes("cpuUsage", "memAnon"), WithPerfOutputInterval(1000), WithPerfPID(100), ) From d79086a752a7dead02defb6303a23b908f7ca9cc Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 8 Oct 2022 11:54:33 +0800 Subject: [PATCH 17/20] feat: getPidByBundleID --- device.go | 28 ---------------------------- idevice.go | 1 + instruments.go | 29 +++++++++++++++++++++++++++++ perfd.go | 20 +++++++++++--------- perfd_test.go | 1 + 5 files changed, 42 insertions(+), 37 deletions(-) diff --git a/device.go b/device.go index 3a8c1c7..e7df5f1 100644 --- a/device.go +++ b/device.go @@ -572,34 +572,6 @@ func (d *device) PcapStop() { d.pcapd.Stop() } -func (d *device) getPidByBundleID(bundleID string) (pid int, err error) { - mapper := make(map[string]interface{}) - apps, err := d.AppList() - if err != nil { - fmt.Printf("get app list error: %v\n", err) - return -1, err - } - for _, app := range apps { - mapper[app.ExecutableName] = app.CFBundleIdentifier - } - - processes, err := d.AppRunningProcesses() - if err != nil { - fmt.Printf("get running app processes error: %v\n", err) - return -1, err - } - for _, proc := range processes { - b, ok := mapper[proc.Name] - if ok && bundleID == b { - fmt.Printf("get pid %d by bundleId %s\n", proc.Pid, bundleID) - return proc.Pid, nil - } - } - - fmt.Printf("can't find pid by bundleID: %s\n", bundleID) - return -1, fmt.Errorf("can't find pid by bundleID: %s", bundleID) -} - func (d *device) PerfStart(opts ...PerfOption) (data <-chan []byte, err error) { if _, err = d.instrumentsService(); err != nil { return nil, err diff --git a/idevice.go b/idevice.go index a62a655..e773a6c 100644 --- a/idevice.go +++ b/idevice.go @@ -145,6 +145,7 @@ type Instruments interface { AppList(opts ...AppListOption) (apps []Application, err error) DeviceInfo() (devInfo *DeviceInfo, err error) + getPidByBundleID(bundleID string) (pid int, err error) appProcess(bundleID string) (err error) startObserving(pid int) (err error) diff --git a/instruments.go b/instruments.go index b987a32..b99e3e0 100644 --- a/instruments.go +++ b/instruments.go @@ -62,6 +62,35 @@ func (i *instruments) call(channel, selector string, auxiliaries ...interface{}) return i.client.Invoke(selector, args, chanID, true) } +func (i *instruments) getPidByBundleID(bundleID string) (pid int, err error) { + apps, err := i.AppList() + if err != nil { + fmt.Printf("get app list error: %v\n", err) + return 0, err + } + + mapper := make(map[string]interface{}) + for _, app := range apps { + mapper[app.ExecutableName] = app.CFBundleIdentifier + } + + processes, err := i.AppRunningProcesses() + if err != nil { + fmt.Printf("get running app processes error: %v\n", err) + return 0, err + } + for _, proc := range processes { + b, ok := mapper[proc.Name] + if ok && bundleID == b { + fmt.Printf("get pid %d by bundleId %s\n", proc.Pid, bundleID) + return proc.Pid, nil + } + } + + fmt.Printf("can't find pid by bundleID: %s\n", bundleID) + return 0, fmt.Errorf("can't find pid by bundleID: %s", bundleID) +} + func (i *instruments) AppLaunch(bundleID string, opts ...AppLaunchOption) (pid int, err error) { opt := new(appLaunchOption) opt.appPath = "" diff --git a/perfd.go b/perfd.go index a645262..f07c273 100644 --- a/perfd.go +++ b/perfd.go @@ -168,13 +168,6 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient perfOption.processAttributes = append(perfOption.processAttributes, "pid") } - if perfOption.bundleID != "" { - pid, err := d.getPidByBundleID(perfOption.bundleID) - if err == nil { - perfOption.pid = strconv.Itoa(pid) - } - } - return &perfdClient{ i: i.(*instruments), option: perfOption, @@ -193,7 +186,8 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient func (c *perfdClient) Start() (data <-chan []byte, err error) { outCh := make(chan []byte, 100) - if c.option.sysCPU || c.option.sysMem || c.option.sysDisk || c.option.sysNetwork || c.option.pid != "" { + if c.option.sysCPU || c.option.sysMem || c.option.sysDisk || + c.option.sysNetwork || c.option.pid != "" || c.option.bundleID != "" { cancel, err := c.registerSysmontap(context.Background()) if err != nil { return nil, err @@ -645,6 +639,14 @@ func (c *perfdClient) registerSysmontap(ctx context.Context) ( return nil, err } + if c.option.bundleID != "" { + pid, err := c.i.getPidByBundleID(c.option.bundleID) + if err != nil { + return nil, fmt.Errorf("get pid by bundle id error: %v", err) + } + c.option.pid = strconv.Itoa(pid) + } + // register listener ctx, cancel = context.WithCancel(ctx) c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { @@ -728,7 +730,7 @@ func (c *perfdClient) parseProcessData(dataArray []interface{}) { processAttributesMap := make(map[string]interface{}) for idx, value := range c.option.processAttributes { - processAttributesMap[value] = convert2Int64(targetProcessValue[idx]) + processAttributesMap[value] = targetProcessValue[idx] } processData["proc_perf"] = processAttributesMap diff --git a/perfd_test.go b/perfd_test.go index 4d030f9..33abcaf 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -39,6 +39,7 @@ func TestPerfProcessMonitor(t *testing.T) { WithPerfProcessAttributes("cpuUsage", "memAnon"), WithPerfOutputInterval(1000), WithPerfPID(100), + WithPerfBundleID("com.apple.mobilesafari"), // higher priority than pid ) if err != nil { t.Fatal(err) From b36169c1673956f5d0aabfac2384b4644c39a3a8 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 9 Oct 2022 19:23:29 +0800 Subject: [PATCH 18/20] change: make perfd public --- instruments.go | 6 +- perfd.go | 158 +++++++++++----------- pkg/libimobiledevice/auxbuffer.go | 1 + pkg/libimobiledevice/client_dtxmessage.go | 2 +- pkg/libimobiledevice/lib.go | 3 +- 5 files changed, 86 insertions(+), 84 deletions(-) diff --git a/instruments.go b/instruments.go index b99e3e0..b18147b 100644 --- a/instruments.go +++ b/instruments.go @@ -12,9 +12,9 @@ const ( instrumentsServiceDeviceInfo = "com.apple.instruments.server.services.deviceinfo" instrumentsServiceProcessControl = "com.apple.instruments.server.services.processcontrol" instrumentsServiceDeviceApplictionListing = "com.apple.instruments.server.services.device.applictionListing" - instrumentsServiceGraphicsOpengl = "com.apple.instruments.server.services.graphics.opengl" // 获取FPS - instrumentsServiceSysmontap = "com.apple.instruments.server.services.sysmontap" // 获取 CPU/Mem 性能数据 - instrumentsServiceNetworking = "com.apple.instruments.server.services.networking" // 获取全局网络数据 + instrumentsServiceGraphicsOpengl = "com.apple.instruments.server.services.graphics.opengl" // 获取 GPU/FPS + instrumentsServiceSysmontap = "com.apple.instruments.server.services.sysmontap" // 获取 CPU/Mem/Disk/Network 性能数据 + instrumentsServiceNetworking = "com.apple.instruments.server.services.networking" // 获取所有网络详情数据 instrumentsServiceMobileNotifications = "com.apple.instruments.server.services.mobilenotifications" // 监控应用状态 ) diff --git a/perfd.go b/perfd.go index f07c273..c0c8007 100644 --- a/perfd.go +++ b/perfd.go @@ -12,35 +12,35 @@ import ( "github.com/electricbubble/gidevice/pkg/libimobiledevice" ) -type perfOption struct { +type PerfOptions struct { // system - sysCPU bool - sysMem bool - sysDisk bool - sysNetwork bool + SysCPU bool `json:"sys_cpu,omitempty" yaml:"sys_cpu,omitempty"` + SysMem bool `json:"sys_mem,omitempty" yaml:"sys_mem,omitempty"` + SysDisk bool `json:"sys_disk,omitempty" yaml:"sys_disk,omitempty"` + SysNetwork bool `json:"sys_network,omitempty" yaml:"sys_network,omitempty"` gpu bool - fps bool - network bool + FPS bool `json:"fps,omitempty" yaml:"fps,omitempty"` + Network bool `json:"network,omitempty" yaml:"network,omitempty"` // process - bundleID string // TODO - pid string + BundleID string `json:"bundle_id,omitempty" yaml:"bundle_id,omitempty"` + Pid int `json:"pid,omitempty" yaml:"pid,omitempty"` // config - outputInterval int // ms - systemAttributes []string - processAttributes []string + OutputInterval int `json:"output_interval,omitempty" yaml:"output_interval,omitempty"` // ms + SystemAttributes []string `json:"system_attributes,omitempty" yaml:"system_attributes,omitempty"` + ProcessAttributes []string `json:"process_attributes,omitempty" yaml:"process_attributes,omitempty"` } -func defaulPerfOption() *perfOption { - return &perfOption{ - sysCPU: true, // default on - sysMem: true, // default on - sysDisk: false, - sysNetwork: false, +func defaulPerfOption() *PerfOptions { + return &PerfOptions{ + SysCPU: true, // default on + SysMem: true, // default on + SysDisk: false, + SysNetwork: false, gpu: false, - fps: false, - network: false, - outputInterval: 1000, // default 1000ms - systemAttributes: []string{ + FPS: false, + Network: false, + OutputInterval: 1000, // default 1000ms + SystemAttributes: []string{ // disk "diskBytesRead", "diskBytesWritten", @@ -61,89 +61,89 @@ func defaulPerfOption() *perfOption { "netPacketsIn", "netPacketsOut", }, - processAttributes: []string{ + ProcessAttributes: []string{ "pid", "cpuUsage", }, } } -type PerfOption func(*perfOption) +type PerfOption func(*PerfOptions) func WithPerfSystemCPU(b bool) PerfOption { - return func(opt *perfOption) { - opt.sysCPU = b + return func(opt *PerfOptions) { + opt.SysCPU = b } } func WithPerfSystemMem(b bool) PerfOption { - return func(opt *perfOption) { - opt.sysMem = b + return func(opt *PerfOptions) { + opt.SysMem = b } } func WithPerfSystemDisk(b bool) PerfOption { - return func(opt *perfOption) { - opt.sysDisk = b + return func(opt *PerfOptions) { + opt.SysDisk = b } } func WithPerfSystemNetwork(b bool) PerfOption { - return func(opt *perfOption) { - opt.sysNetwork = b + return func(opt *PerfOptions) { + opt.SysNetwork = b } } func WithPerfBundleID(bundleID string) PerfOption { - return func(opt *perfOption) { - opt.bundleID = bundleID + return func(opt *PerfOptions) { + opt.BundleID = bundleID } } func WithPerfPID(pid int) PerfOption { - return func(opt *perfOption) { - opt.pid = strconv.Itoa(pid) + return func(opt *PerfOptions) { + opt.Pid = pid } } func WithPerfGPU(b bool) PerfOption { - return func(opt *perfOption) { + return func(opt *PerfOptions) { opt.gpu = b } } func WithPerfFPS(b bool) PerfOption { - return func(opt *perfOption) { - opt.fps = b + return func(opt *PerfOptions) { + opt.FPS = b } } func WithPerfNetwork(b bool) PerfOption { - return func(opt *perfOption) { - opt.network = b + return func(opt *PerfOptions) { + opt.Network = b } } func WithPerfOutputInterval(intervalMilliseconds int) PerfOption { - return func(opt *perfOption) { - opt.outputInterval = intervalMilliseconds + return func(opt *PerfOptions) { + opt.OutputInterval = intervalMilliseconds } } func WithPerfProcessAttributes(attrs ...string) PerfOption { - return func(opt *perfOption) { - opt.processAttributes = attrs + return func(opt *PerfOptions) { + opt.ProcessAttributes = attrs } } func WithPerfSystemAttributes(attrs ...string) PerfOption { - return func(opt *perfOption) { - opt.systemAttributes = attrs + return func(opt *PerfOptions) { + opt.SystemAttributes = attrs } } type perfdClient struct { - option *perfOption + option *PerfOptions i *instruments stop chan struct{} // used to stop perf client cancels []context.CancelFunc // used to cancel all iterators @@ -164,8 +164,8 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient } // processAttributes must contain pid, or it can't get process info, reason unknown - if !containString(perfOption.processAttributes, "pid") { - perfOption.processAttributes = append(perfOption.processAttributes, "pid") + if !containString(perfOption.ProcessAttributes, "pid") { + perfOption.ProcessAttributes = append(perfOption.ProcessAttributes, "pid") } return &perfdClient{ @@ -186,8 +186,8 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient func (c *perfdClient) Start() (data <-chan []byte, err error) { outCh := make(chan []byte, 100) - if c.option.sysCPU || c.option.sysMem || c.option.sysDisk || - c.option.sysNetwork || c.option.pid != "" || c.option.bundleID != "" { + if c.option.SysCPU || c.option.SysMem || c.option.SysDisk || + c.option.SysNetwork { cancel, err := c.registerSysmontap(context.Background()) if err != nil { return nil, err @@ -195,7 +195,7 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { c.cancels = append(c.cancels, cancel) } - if c.option.fps { + if c.option.FPS { cancel, err := c.startGetFPS(context.Background()) if err != nil { return nil, err @@ -211,7 +211,7 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { c.cancels = append(c.cancels, cancel) } - if c.option.network { + if c.option.Network { cancel, err := c.registerNetworking(context.Background()) if err != nil { return nil, err @@ -459,7 +459,7 @@ func (c *perfdClient) startGetFPS(ctx context.Context) ( if _, err = c.i.call( instrumentsServiceGraphicsOpengl, "setSamplingRate:", - float64(c.option.outputInterval)/100, + float64(c.option.OutputInterval)/100, ); err != nil { return nil, err } @@ -531,7 +531,7 @@ func (c *perfdClient) startGetGPU(ctx context.Context) ( if _, err = c.i.call( instrumentsServiceGraphicsOpengl, "setSamplingRate:", - float64(c.option.outputInterval)/100, + float64(c.option.OutputInterval)/100, ); err != nil { return nil, err } @@ -619,9 +619,9 @@ func (c *perfdClient) registerSysmontap(ctx context.Context) ( "bm": 0, "cpuUsage": true, "sampleInterval": time.Second * 1, // 1s - "ur": c.option.outputInterval, // 输出频率 - "procAttrs": c.option.processAttributes, // process performance - "sysAttrs": c.option.systemAttributes, // system performance + "ur": c.option.OutputInterval, // 输出频率 + "procAttrs": c.option.ProcessAttributes, // process performance + "sysAttrs": c.option.SystemAttributes, // system performance } if _, err = c.i.call( instrumentsServiceSysmontap, @@ -639,14 +639,6 @@ func (c *perfdClient) registerSysmontap(ctx context.Context) ( return nil, err } - if c.option.bundleID != "" { - pid, err := c.i.getPidByBundleID(c.option.bundleID) - if err != nil { - return nil, fmt.Errorf("get pid by bundle id error: %v", err) - } - c.option.pid = strconv.Itoa(pid) - } - // register listener ctx, cancel = context.WithCancel(ctx) c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { @@ -659,7 +651,17 @@ func (c *perfdClient) registerSysmontap(ctx context.Context) ( if !ok || len(dataArray) != 2 { return } - if c.option.pid != "" { + + if c.option.BundleID != "" { + pid, err := c.i.getPidByBundleID(c.option.BundleID) + if err != nil { + fmt.Printf("get pid by bundle id failed: %v", err) + return + } + c.option.Pid = pid + } + + if c.option.Pid != 0 { c.parseProcessData(dataArray) } else { c.parseSystemData(dataArray) @@ -701,7 +703,7 @@ func (c *perfdClient) parseProcessData(dataArray []interface{}) { processData := make(map[string]interface{}) processData["type"] = "process" processData["timestamp"] = time.Now().Unix() - processData["pid"] = c.option.pid + processData["pid"] = c.option.Pid defer func() { processBytes, _ := json.Marshal(processData) @@ -717,26 +719,26 @@ func (c *perfdClient) parseProcessData(dataArray []interface{}) { var targetProcessValue []interface{} processList := processInfo["Processes"].(map[string]interface{}) for pid, v := range processList { - if pid != c.option.pid { + if pid != strconv.Itoa(c.option.Pid) { continue } targetProcessValue = v.([]interface{}) } if targetProcessValue == nil { - processData["msg"] = fmt.Sprintf("process %s not found", c.option.pid) + processData["msg"] = fmt.Sprintf("process %d not found", c.option.Pid) return } processAttributesMap := make(map[string]interface{}) - for idx, value := range c.option.processAttributes { + for idx, value := range c.option.ProcessAttributes { processAttributesMap[value] = targetProcessValue[idx] } processData["proc_perf"] = processAttributesMap systemAttributesValue := systemInfo["System"].([]interface{}) systemAttributesMap := make(map[string]int64) - for idx, value := range c.option.systemAttributes { + for idx, value := range c.option.SystemAttributes { systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx]) } processData["sys_perf"] = systemAttributesMap @@ -773,7 +775,7 @@ func (c *perfdClient) parseSystemData(dataArray []interface{}) { // Type:41 // ] - if c.option.sysCPU { + if c.option.SysCPU { sysCPUUsage := systemInfo["SystemCPUUsage"].(map[string]interface{}) sysCPUInfo := SystemCPUData{ PerfDataBase: PerfDataBase{ @@ -791,11 +793,11 @@ func (c *perfdClient) parseSystemData(dataArray []interface{}) { systemAttributesValue := systemInfo["System"].([]interface{}) systemAttributesMap := make(map[string]int64) - for idx, value := range c.option.systemAttributes { + for idx, value := range c.option.SystemAttributes { systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx]) } - if c.option.sysMem { + if c.option.SysMem { kernelPageSize := int64(1) // why 16384 ? appMemory := (systemAttributesMap["vmIntPageCount"] - systemAttributesMap["vmPurgeableCount"]) * kernelPageSize cachedFiles := (systemAttributesMap["vmExtPageCount"] - systemAttributesMap["vmPurgeableCount"]) * kernelPageSize @@ -822,7 +824,7 @@ func (c *perfdClient) parseSystemData(dataArray []interface{}) { c.chanSysMem <- memBytes } - if c.option.sysDisk { + if c.option.SysDisk { diskBytesRead := systemAttributesMap["diskBytesRead"] diskBytesWritten := systemAttributesMap["diskBytesWritten"] diskReadOps := systemAttributesMap["diskReadOps"] @@ -842,7 +844,7 @@ func (c *perfdClient) parseSystemData(dataArray []interface{}) { c.chanSysDisk <- diskBytes } - if c.option.sysNetwork { + if c.option.SysNetwork { netBytesIn := systemAttributesMap["netBytesIn"] netBytesOut := systemAttributesMap["netBytesOut"] netPacketsIn := systemAttributesMap["netPacketsIn"] diff --git a/pkg/libimobiledevice/auxbuffer.go b/pkg/libimobiledevice/auxbuffer.go index 966d45f..a48e77c 100644 --- a/pkg/libimobiledevice/auxbuffer.go +++ b/pkg/libimobiledevice/auxbuffer.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "errors" + "github.com/electricbubble/gidevice/pkg/nskeyedarchiver" ) diff --git a/pkg/libimobiledevice/client_dtxmessage.go b/pkg/libimobiledevice/client_dtxmessage.go index e7ead77..067446a 100644 --- a/pkg/libimobiledevice/client_dtxmessage.go +++ b/pkg/libimobiledevice/client_dtxmessage.go @@ -113,7 +113,7 @@ func (c *dtxMessageClient) SendDTXMessage(selector string, aux []byte, channelCo func (c *dtxMessageClient) ReceiveDTXMessage() (result *DTXMessageResult, err error) { bufPayload := new(bytes.Buffer) - header := new(dtxMessageHeaderPacket) + var header *dtxMessageHeaderPacket = nil var needToReply *dtxMessageHeaderPacket = nil for { diff --git a/pkg/libimobiledevice/lib.go b/pkg/libimobiledevice/lib.go index 950981e..39e8c93 100644 --- a/pkg/libimobiledevice/lib.go +++ b/pkg/libimobiledevice/lib.go @@ -2,7 +2,6 @@ package libimobiledevice import ( "bytes" - "fmt" "log" ) @@ -25,5 +24,5 @@ func debugLog(msg string) { if !debugFlag { return } - log.Println(fmt.Sprintf("[%s-debug] %s", ProgramName, msg)) + log.Printf("[%s-debug] %s\n", ProgramName, msg) } From 8a73d55ade106f965d6e6ec6a83350f8eac9e3db Mon Sep 17 00:00:00 2001 From: debugtalk Date: Tue, 11 Oct 2022 22:13:20 +0800 Subject: [PATCH 19/20] fix: each instruments service should have individual connection, otherwise it will be blocked --- device.go | 1 - idevice.go | 1 + perfd.go | 44 +++++++++++++++++++++++++++++++++----------- testmanagerd.go | 3 ++- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/device.go b/device.go index e7df5f1..15a7343 100644 --- a/device.go +++ b/device.go @@ -801,7 +801,6 @@ func (d *device) XCTest(bundleID string, opts ...XCTestOption) (out <-chan strin } // time.Sleep(time.Second) close(_out) - return }() return _out, cancelFunc, err diff --git a/idevice.go b/idevice.go index e773a6c..2ff71b4 100644 --- a/idevice.go +++ b/idevice.go @@ -151,6 +151,7 @@ type Instruments interface { notifyOfPublishedCapabilities() (err error) requestChannel(channel string) (id uint32, err error) + call(channel, selector string, auxiliaries ...interface{}) (result *libimobiledevice.DTXMessageResult, err error) // sysMonSetConfig(cfg ...interface{}) (err error) // SysMonStart(cfg ...interface{}) (_ interface{}, err error) diff --git a/perfd.go b/perfd.go index c0c8007..4c964d1 100644 --- a/perfd.go +++ b/perfd.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net" + "os" "strconv" "time" @@ -144,7 +145,7 @@ func WithPerfSystemAttributes(attrs ...string) PerfOption { type perfdClient struct { option *PerfOptions - i *instruments + i Instruments stop chan struct{} // used to stop perf client cancels []context.CancelFunc // used to cancel all iterators chanSysCPU chan []byte // system cpu channel @@ -163,13 +164,23 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient fn(perfOption) } + // wait until get pid for bundle id + if perfOption.BundleID != "" { + var err error + perfOption.Pid, err = d.waitForBundleUp(perfOption.BundleID) + if err != nil { + fmt.Printf("get pid by bundle id failed: %v\n", err) + os.Exit(1) + } + } + // processAttributes must contain pid, or it can't get process info, reason unknown if !containString(perfOption.ProcessAttributes, "pid") { perfOption.ProcessAttributes = append(perfOption.ProcessAttributes, "pid") } return &perfdClient{ - i: i.(*instruments), + i: i, option: perfOption, stop: make(chan struct{}), chanSysCPU: make(chan []byte, 10), @@ -183,6 +194,26 @@ func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient } } +func (d *device) waitForBundleUp(bundleID string) (pid int, err error) { + // NOTICE: each instruments service should have individual connection, otherwise it will be blocked + var instruments Instruments + if _, err = d.lockdownService(); err != nil { + return + } + if instruments, err = d.lockdown.InstrumentsService(); err != nil { + return + } + + for { + pid, err := instruments.getPidByBundleID(bundleID) + if err != nil { + time.Sleep(1 * time.Second) + continue + } + return pid, nil + } +} + func (c *perfdClient) Start() (data <-chan []byte, err error) { outCh := make(chan []byte, 100) @@ -652,15 +683,6 @@ func (c *perfdClient) registerSysmontap(ctx context.Context) ( return } - if c.option.BundleID != "" { - pid, err := c.i.getPidByBundleID(c.option.BundleID) - if err != nil { - fmt.Printf("get pid by bundle id failed: %v", err) - return - } - c.option.Pid = pid - } - if c.option.Pid != 0 { c.parseProcessData(dataArray) } else { diff --git a/testmanagerd.go b/testmanagerd.go index d4a34c9..5ba78c9 100644 --- a/testmanagerd.go +++ b/testmanagerd.go @@ -36,7 +36,8 @@ func (t *testmanagerd) newXCTestManagerDaemon() (xcTestManager XCTestManagerDaem return } -func (t *testmanagerd) invoke(selector string, args *libimobiledevice.AuxBuffer, channelCode uint32, expectsReply bool) (result *libimobiledevice.DTXMessageResult, err error) { +func (t *testmanagerd) invoke(selector string, args *libimobiledevice.AuxBuffer, channelCode uint32, expectsReply bool) ( + result *libimobiledevice.DTXMessageResult, err error) { return t.client.Invoke(selector, args, channelCode, expectsReply) } From 9b59e12ecc77376c056c023d581c1188ee1b6027 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 12 Oct 2022 15:08:47 +0800 Subject: [PATCH 20/20] refactor: support monitor multiple instruments services concurrently --- device.go | 105 ++++- perfd.go | 1039 +++++++++++++++++++++++-------------------------- perfd_test.go | 29 ++ 3 files changed, 614 insertions(+), 559 deletions(-) diff --git a/device.go b/device.go index 15a7343..b59de94 100644 --- a/device.go +++ b/device.go @@ -48,7 +48,7 @@ type device struct { springBoard SpringBoard crashReportMover CrashReportMover pcapd Pcapd - perfd Perfd + perfd []Perfd } func (d *device) Properties() DeviceProperties { @@ -317,14 +317,19 @@ func (d *device) InstallationProxyLookup(opts ...InstallationProxyOption) (looku return d.installationProxy.Lookup(opts...) } +func (d *device) newInstrumentsService() (instruments Instruments, err error) { + // NOTICE: each instruments service should have individual connection, otherwise it will be blocked + if _, err = d.lockdownService(); err != nil { + return + } + return d.lockdown.InstrumentsService() +} + func (d *device) instrumentsService() (instruments Instruments, err error) { if d.instruments != nil { return d.instruments, nil } - if _, err = d.lockdownService(); err != nil { - return nil, err - } - if d.instruments, err = d.lockdown.InstrumentsService(); err != nil { + if d.instruments, err = d.newInstrumentsService(); err != nil { return nil, err } instruments = d.instruments @@ -573,19 +578,99 @@ func (d *device) PcapStop() { } func (d *device) PerfStart(opts ...PerfOption) (data <-chan []byte, err error) { - if _, err = d.instrumentsService(); err != nil { - return nil, err + perfOptions := defaulPerfOption() + for _, fn := range opts { + fn(perfOptions) } - d.perfd = d.newPerfdClient(d.instruments, opts...) - return d.perfd.Start() + // wait until get pid for bundle id + if perfOptions.BundleID != "" { + instruments, err := d.newInstrumentsService() + if err != nil { + fmt.Printf("get pid by bundle id failed: %v\n", err) + os.Exit(1) + } + + for { + pid, err := instruments.getPidByBundleID(perfOptions.BundleID) + if err != nil { + time.Sleep(1 * time.Second) + continue + } + perfOptions.Pid = pid + break + } + } + + // processAttributes must contain pid, or it can't get process info, reason unknown + if !containString(perfOptions.ProcessAttributes, "pid") { + perfOptions.ProcessAttributes = append(perfOptions.ProcessAttributes, "pid") + } + + outCh := make(chan []byte, 100) + + if perfOptions.SysCPU || perfOptions.SysMem || perfOptions.SysDisk || + perfOptions.SysNetwork { + perfd, err := d.newPerfdSysmontap(perfOptions) + if err != nil { + return nil, err + } + data, err := perfd.Start() + if err != nil { + return nil, err + } + go func() { + for { + outCh <- (<-data) + } + }() + d.perfd = append(d.perfd, perfd) + } + + if perfOptions.Network { + perfd, err := d.newPerfdNetworking(perfOptions) + if err != nil { + return nil, err + } + data, err := perfd.Start() + if err != nil { + return nil, err + } + go func() { + for { + outCh <- (<-data) + } + }() + d.perfd = append(d.perfd, perfd) + } + + if perfOptions.FPS || perfOptions.gpu { + perfd, err := d.newPerfdGraphicsOpengl(perfOptions) + if err != nil { + return nil, err + } + data, err := perfd.Start() + if err != nil { + return nil, err + } + go func() { + for { + outCh <- (<-data) + } + }() + d.perfd = append(d.perfd, perfd) + } + + return outCh, nil } func (d *device) PerfStop() { if d.perfd == nil { return } - d.perfd.Stop() + for _, p := range d.perfd { + p.Stop() + } } func (d *device) crashReportMoverService() (crashReportMover CrashReportMover, err error) { diff --git a/perfd.go b/perfd.go index 4c964d1..995e40d 100644 --- a/perfd.go +++ b/perfd.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "net" - "os" "strconv" "time" @@ -144,116 +143,96 @@ func WithPerfSystemAttributes(attrs ...string) PerfOption { } type perfdClient struct { - option *PerfOptions - i Instruments - stop chan struct{} // used to stop perf client - cancels []context.CancelFunc // used to cancel all iterators - chanSysCPU chan []byte // system cpu channel - chanSysMem chan []byte // system mem channel - chanSysDisk chan []byte // system disk channel - chanSysNetwork chan []byte // system network channel - chanGPU chan []byte // gpu channel - chanFPS chan []byte // fps channel - chanNetwork chan []byte // network channel - chanProcess chan []byte // process channel -} - -func (d *device) newPerfdClient(i Instruments, opts ...PerfOption) *perfdClient { - perfOption := defaulPerfOption() - for _, fn := range opts { - fn(perfOption) - } - - // wait until get pid for bundle id - if perfOption.BundleID != "" { - var err error - perfOption.Pid, err = d.waitForBundleUp(perfOption.BundleID) - if err != nil { - fmt.Printf("get pid by bundle id failed: %v\n", err) - os.Exit(1) - } - } + options *PerfOptions + i Instruments + stop chan struct{} // used to stop perf client + cancel context.CancelFunc // used to cancel all iterators +} - // processAttributes must contain pid, or it can't get process info, reason unknown - if !containString(perfOption.ProcessAttributes, "pid") { - perfOption.ProcessAttributes = append(perfOption.ProcessAttributes, "pid") +func (d *device) newPerfdSysmontap(options *PerfOptions) (*perfdSysmontap, error) { + instruments, err := d.newInstrumentsService() + if err != nil { + return nil, err } - - return &perfdClient{ - i: i, - option: perfOption, - stop: make(chan struct{}), + return &perfdSysmontap{ + perfdClient: perfdClient{ + i: instruments, + options: options, + stop: make(chan struct{}), + }, chanSysCPU: make(chan []byte, 10), chanSysMem: make(chan []byte, 10), chanSysDisk: make(chan []byte, 10), chanSysNetwork: make(chan []byte, 10), - chanGPU: make(chan []byte, 10), - chanFPS: make(chan []byte, 10), - chanNetwork: make(chan []byte, 10), chanProcess: make(chan []byte, 10), - } + }, nil } -func (d *device) waitForBundleUp(bundleID string) (pid int, err error) { - // NOTICE: each instruments service should have individual connection, otherwise it will be blocked - var instruments Instruments - if _, err = d.lockdownService(); err != nil { - return - } - if instruments, err = d.lockdown.InstrumentsService(); err != nil { - return - } - - for { - pid, err := instruments.getPidByBundleID(bundleID) - if err != nil { - time.Sleep(1 * time.Second) - continue - } - return pid, nil - } +type perfdSysmontap struct { + perfdClient + chanSysCPU chan []byte // system cpu channel + chanSysMem chan []byte // system mem channel + chanSysDisk chan []byte // system disk channel + chanSysNetwork chan []byte // system network channel + chanProcess chan []byte // process channel } -func (c *perfdClient) Start() (data <-chan []byte, err error) { - outCh := make(chan []byte, 100) +func (c *perfdSysmontap) Start() (data <-chan []byte, err error) { - if c.option.SysCPU || c.option.SysMem || c.option.SysDisk || - c.option.SysNetwork { - cancel, err := c.registerSysmontap(context.Background()) - if err != nil { - return nil, err - } - c.cancels = append(c.cancels, cancel) + // set config + config := map[string]interface{}{ + "bm": 0, + "cpuUsage": true, + "sampleInterval": time.Second * 1, // 1s + "ur": c.options.OutputInterval, // 输出频率 + "procAttrs": c.options.ProcessAttributes, // process performance + "sysAttrs": c.options.SystemAttributes, // system performance } - - if c.option.FPS { - cancel, err := c.startGetFPS(context.Background()) - if err != nil { - return nil, err - } - c.cancels = append(c.cancels, cancel) + if _, err = c.i.call( + instrumentsServiceSysmontap, + "setConfig:", + config, + ); err != nil { + return nil, err } - if c.option.gpu { - cancel, err := c.startGetGPU(context.Background()) - if err != nil { - return nil, err - } - c.cancels = append(c.cancels, cancel) + // start + if _, err = c.i.call( + instrumentsServiceSysmontap, + "start", + ); err != nil { + return nil, err } - if c.option.Network { - cancel, err := c.registerNetworking(context.Background()) - if err != nil { - return nil, err + // register listener + ctx, cancel := context.WithCancel(context.TODO()) + c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { + select { + case <-ctx.Done(): + c.i.call(instrumentsServiceSysmontap, "stop") + return + default: + dataArray, ok := m.Obj.([]interface{}) + if !ok || len(dataArray) != 2 { + return + } + + if c.options.Pid != 0 { + c.parseProcessData(dataArray) + } else { + c.parseSystemData(dataArray) + } } - c.cancels = append(c.cancels, cancel) - } + }) + c.cancel = cancel + + outCh := make(chan []byte, 100) go func() { for { select { case <-c.stop: + c.cancel() return case cpuBytes, ok := <-c.chanSysCPU: if ok { @@ -271,18 +250,6 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { if ok { outCh <- networkBytes } - case gpuBytes, ok := <-c.chanGPU: - if ok { - outCh <- gpuBytes - } - case fpsBytes, ok := <-c.chanFPS: - if ok { - outCh <- fpsBytes - } - case networkBytes, ok := <-c.chanNetwork: - if ok { - outCh <- networkBytes - } case processBytes, ok := <-c.chanProcess: if ok { outCh <- processBytes @@ -294,101 +261,366 @@ func (c *perfdClient) Start() (data <-chan []byte, err error) { return outCh, nil } -func (c *perfdClient) Stop() { +func (c *perfdSysmontap) Stop() { close(c.stop) - for _, cancel := range c.cancels { - cancel() - } } -func (c *perfdClient) registerNetworking(ctx context.Context) ( - cancel context.CancelFunc, err error) { +func (c *perfdSysmontap) parseProcessData(dataArray []interface{}) { + // dataArray example: + // [ + // map[ + // CPUCount:2 + // EnabledCPUs:2 + // PerCPUUsage:[ + // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:3.6363636363636402 CPU_UserLoad:-1] + // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:2.7272727272727195 CPU_UserLoad:-1] + // ] + // System:[36408520704 6897049600 3031160 773697 15596 61940 1297 26942 588 17020 127346 1835008 119718056 107009899 174046 103548] + // SystemCPUUsage:map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:6.36363636363636 CPU_UserLoad:-1] + // StartMachAbsTime:5896602132889 + // EndMachAbsTime:5896628486761 + // Type:41 + // ] + // map[ + // Processes:map[ + // 0:[1.3582834340402803 0] + // 124:[0.011456702068519481 124] + // 136:[0.05468332721703649 136] + // ] + // StartMachAbsTime:5896602295095 + // EndMachAbsTime:5896628780514 + // Type:5 + // ] + // ] + + processData := make(map[string]interface{}) + processData["type"] = "process" + processData["timestamp"] = time.Now().Unix() + processData["pid"] = c.options.Pid - if _, err = c.i.call( - instrumentsServiceNetworking, - "replayLastRecordedSession", - ); err != nil { - return nil, err - } + defer func() { + processBytes, _ := json.Marshal(processData) + c.chanProcess <- processBytes + }() - if _, err = c.i.call( - instrumentsServiceNetworking, - "startMonitoring", - ); err != nil { - return nil, err + systemInfo := dataArray[0].(map[string]interface{}) + processInfo := dataArray[1].(map[string]interface{}) + if _, ok := systemInfo["System"]; !ok { + systemInfo, processInfo = processInfo, systemInfo } - ctx, cancel = context.WithCancel(ctx) - c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { - select { - case <-ctx.Done(): - c.i.call(instrumentsServiceNetworking, "stopMonitoring") - return - default: - c.parseNetworking(m.Obj) + var targetProcessValue []interface{} + processList := processInfo["Processes"].(map[string]interface{}) + for pid, v := range processList { + if pid != strconv.Itoa(c.options.Pid) { + continue } - }) + targetProcessValue = v.([]interface{}) + } + + if targetProcessValue == nil { + processData["msg"] = fmt.Sprintf("process %d not found", c.options.Pid) + return + } + + processAttributesMap := make(map[string]interface{}) + for idx, value := range c.options.ProcessAttributes { + processAttributesMap[value] = targetProcessValue[idx] + } + processData["proc_perf"] = processAttributesMap - return + systemAttributesValue := systemInfo["System"].([]interface{}) + systemAttributesMap := make(map[string]int64) + for idx, value := range c.options.SystemAttributes { + systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx]) + } + processData["sys_perf"] = systemAttributesMap } -func (c *perfdClient) parseNetworking(data interface{}) { - raw, ok := data.([]interface{}) - if !ok || len(raw) != 2 { - fmt.Printf("invalid networking data: %v\n", data) - return +func (c *perfdSysmontap) parseSystemData(dataArray []interface{}) { + timestamp := time.Now().Unix() + var systemInfo map[string]interface{} + data1 := dataArray[0].(map[string]interface{}) + data2 := dataArray[1].(map[string]interface{}) + if _, ok := data1["SystemCPUUsage"]; ok { + systemInfo = data1 + } else { + systemInfo = data2 } - var netBytes []byte - msgType := raw[0].(uint64) - msgValue := raw[1].([]interface{}) - if msgType == 0 { - // interface-detection - // ['InterfaceIndex', "Name"] - // e.g. [0, [14, 'en0']] - netData := NetworkDataInterfaceDetection{ + // systemInfo example: + // map[ + // CPUCount:2 + // EnabledCPUs:2 + // PerCPUUsage:[ + // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:3.9215686274509807 CPU_UserLoad:-1] + // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:11.650485436893206 CPU_UserLoad:-1]] + // ] + // System:[704211 35486281728 6303789056 3001119 1001 11033 52668 1740 40022 2114 17310 126903 1835008 160323 107909856 95067 95808179] + // SystemCPUUsage:map[ + // CPU_NiceLoad:0 + // CPU_SystemLoad:-1 + // CPU_TotalLoad:15.572054064344186 + // CPU_UserLoad:-1 + // ] + // StartMachAbsTime:5339240248449 + // EndMachAbsTime:5339264441260 + // Type:41 + // ] + + if c.options.SysCPU { + sysCPUUsage := systemInfo["SystemCPUUsage"].(map[string]interface{}) + sysCPUInfo := SystemCPUData{ PerfDataBase: PerfDataBase{ - Type: "network-interface-detection", - TimeStamp: time.Now().Unix(), + Type: "sys_cpu", + TimeStamp: timestamp, }, - InterfaceIndex: convert2Int64(msgValue[0]), - Name: msgValue[1].(string), + NiceLoad: sysCPUUsage["CPU_NiceLoad"].(float64), + SystemLoad: sysCPUUsage["CPU_SystemLoad"].(float64), + TotalLoad: sysCPUUsage["CPU_TotalLoad"].(float64), + UserLoad: sysCPUUsage["CPU_UserLoad"].(float64), } - netBytes, _ = json.Marshal(netData) - } else if msgType == 1 { - // connection-detected - // ['LocalAddress', 'RemoteAddress', 'InterfaceIndex', 'Pid', - // 'RecvBufferSize', 'RecvBufferUsed', 'SerialNumber', 'Kind'] - // e.g. [1 [[16 2 211 158 192 168 100 101 0 0 0 0 0 0 0 0] - // [16 2 0 53 183 221 253 100 0 0 0 0 0 0 0 0] - // 14 -2 786896 0 133 2]] + cpuBytes, _ := json.Marshal(sysCPUInfo) + c.chanSysCPU <- cpuBytes + } - localAddr, err := parseSocketAddr(msgValue[0].([]byte)) - if err != nil { - fmt.Printf("parse local socket address err: %v\n", err) - } - remoteAddr, err := parseSocketAddr(msgValue[1].([]byte)) - if err != nil { - fmt.Printf("parse remote socket address err: %v\n", err) - } - netData := NetworkDataConnectionDetected{ + systemAttributesValue := systemInfo["System"].([]interface{}) + systemAttributesMap := make(map[string]int64) + for idx, value := range c.options.SystemAttributes { + systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx]) + } + + if c.options.SysMem { + kernelPageSize := int64(1) // why 16384 ? + appMemory := (systemAttributesMap["vmIntPageCount"] - systemAttributesMap["vmPurgeableCount"]) * kernelPageSize + cachedFiles := (systemAttributesMap["vmExtPageCount"] - systemAttributesMap["vmPurgeableCount"]) * kernelPageSize + compressed := systemAttributesMap["vmCompressorPageCount"] * kernelPageSize + usedMemory := (systemAttributesMap["vmUsedCount"] - systemAttributesMap["vmExtPageCount"]) * kernelPageSize + wiredMemory := systemAttributesMap["vmWireCount"] * kernelPageSize + swapUsed := systemAttributesMap["__vmSwapUsage"] + freeMemory := systemAttributesMap["vmFreeCount"] * kernelPageSize + + sysMemInfo := SystemMemData{ PerfDataBase: PerfDataBase{ - Type: "network-connection-detected", - TimeStamp: time.Now().Unix(), + Type: "sys_mem", + TimeStamp: timestamp, }, - LocalAddress: localAddr, - RemoteAddress: remoteAddr, - InterfaceIndex: convert2Int64(msgValue[2]), - Pid: convert2Int64(msgValue[3]), - RecvBufferSize: convert2Int64(msgValue[4]), - RecvBufferUsed: convert2Int64(msgValue[5]), - SerialNumber: convert2Int64(msgValue[6]), - Kind: convert2Int64(msgValue[7]), - } - netBytes, _ = json.Marshal(netData) - } else if msgType == 2 { - // connection-update - // ['RxPackets', 'RxBytes', 'TxPackets', 'TxBytes', + AppMemory: appMemory, + UsedMemory: usedMemory, + WiredMemory: wiredMemory, + FreeMemory: freeMemory, + CachedFiles: cachedFiles, + Compressed: compressed, + SwapUsed: swapUsed, + } + memBytes, _ := json.Marshal(sysMemInfo) + c.chanSysMem <- memBytes + } + + if c.options.SysDisk { + diskBytesRead := systemAttributesMap["diskBytesRead"] + diskBytesWritten := systemAttributesMap["diskBytesWritten"] + diskReadOps := systemAttributesMap["diskReadOps"] + diskWriteOps := systemAttributesMap["diskWriteOps"] + + sysDiskInfo := SystemDiskData{ + PerfDataBase: PerfDataBase{ + Type: "sys_disk", + TimeStamp: timestamp, + }, + DataRead: diskBytesRead, + DataWritten: diskBytesWritten, + ReadOps: diskReadOps, + WriteOps: diskWriteOps, + } + diskBytes, _ := json.Marshal(sysDiskInfo) + c.chanSysDisk <- diskBytes + } + + if c.options.SysNetwork { + netBytesIn := systemAttributesMap["netBytesIn"] + netBytesOut := systemAttributesMap["netBytesOut"] + netPacketsIn := systemAttributesMap["netPacketsIn"] + netPacketsOut := systemAttributesMap["netPacketsOut"] + + sysNetworkInfo := SystemNetworkData{ + PerfDataBase: PerfDataBase{ + Type: "sys_network", + TimeStamp: timestamp, + }, + BytesIn: netBytesIn, + BytesOut: netBytesOut, + PacketsIn: netPacketsIn, + PacketsOut: netPacketsOut, + } + networkBytes, _ := json.Marshal(sysNetworkInfo) + c.chanSysNetwork <- networkBytes + } +} + +type SystemCPUData struct { + PerfDataBase // system cpu + NiceLoad float64 `json:"nice_load"` + SystemLoad float64 `json:"system_load"` + TotalLoad float64 `json:"total_load"` + UserLoad float64 `json:"user_load"` +} + +type SystemMemData struct { + PerfDataBase // mem + AppMemory int64 `json:"app_memory"` + FreeMemory int64 `json:"free_memory"` + UsedMemory int64 `json:"used_memory"` + WiredMemory int64 `json:"wired_memory"` + CachedFiles int64 `json:"cached_files"` + Compressed int64 `json:"compressed"` + SwapUsed int64 `json:"swap_used"` +} + +type SystemDiskData struct { + PerfDataBase // disk + DataRead int64 `json:"data_read"` + DataWritten int64 `json:"data_written"` + ReadOps int64 `json:"reads_in"` + WriteOps int64 `json:"writes_out"` +} + +type SystemNetworkData struct { + PerfDataBase // network + BytesIn int64 `json:"bytes_in"` + BytesOut int64 `json:"bytes_out"` + PacketsIn int64 `json:"packets_in"` + PacketsOut int64 `json:"packets_out"` +} + +func (d *device) newPerfdNetworking(options *PerfOptions) (*perfdNetworking, error) { + instruments, err := d.newInstrumentsService() + if err != nil { + return nil, err + } + return &perfdNetworking{ + perfdClient: perfdClient{ + i: instruments, + options: options, + stop: make(chan struct{}), + }, + chanNetwork: make(chan []byte, 10), + }, nil +} + +type perfdNetworking struct { + perfdClient + chanNetwork chan []byte // network channel +} + +func (c *perfdNetworking) Start() (data <-chan []byte, err error) { + + if _, err = c.i.call( + instrumentsServiceNetworking, + "replayLastRecordedSession", + ); err != nil { + return nil, err + } + + if _, err = c.i.call( + instrumentsServiceNetworking, + "startMonitoring", + ); err != nil { + return nil, err + } + + ctx, cancel := context.WithCancel(context.TODO()) + c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { + select { + case <-ctx.Done(): + c.i.call(instrumentsServiceNetworking, "stopMonitoring") + return + default: + c.parseNetworking(m.Obj) + } + }) + c.cancel = cancel + + outCh := make(chan []byte, 100) + + go func() { + for { + select { + case <-c.stop: + c.cancel() + return + case networkBytes, ok := <-c.chanNetwork: + if ok { + outCh <- networkBytes + } + } + } + }() + + return outCh, nil +} + +func (c *perfdNetworking) Stop() { + close(c.stop) +} + +func (c *perfdNetworking) parseNetworking(data interface{}) { + raw, ok := data.([]interface{}) + if !ok || len(raw) != 2 { + fmt.Printf("invalid networking data: %v\n", data) + return + } + + var netBytes []byte + msgType := raw[0].(uint64) + msgValue := raw[1].([]interface{}) + if msgType == 0 { + // interface-detection + // ['InterfaceIndex', "Name"] + // e.g. [0, [14, 'en0']] + netData := NetworkDataInterfaceDetection{ + PerfDataBase: PerfDataBase{ + Type: "network-interface-detection", + TimeStamp: time.Now().Unix(), + }, + InterfaceIndex: convert2Int64(msgValue[0]), + Name: msgValue[1].(string), + } + netBytes, _ = json.Marshal(netData) + } else if msgType == 1 { + // connection-detected + // ['LocalAddress', 'RemoteAddress', 'InterfaceIndex', 'Pid', + // 'RecvBufferSize', 'RecvBufferUsed', 'SerialNumber', 'Kind'] + // e.g. [1 [[16 2 211 158 192 168 100 101 0 0 0 0 0 0 0 0] + // [16 2 0 53 183 221 253 100 0 0 0 0 0 0 0 0] + // 14 -2 786896 0 133 2]] + + localAddr, err := parseSocketAddr(msgValue[0].([]byte)) + if err != nil { + fmt.Printf("parse local socket address err: %v\n", err) + } + remoteAddr, err := parseSocketAddr(msgValue[1].([]byte)) + if err != nil { + fmt.Printf("parse remote socket address err: %v\n", err) + } + netData := NetworkDataConnectionDetected{ + PerfDataBase: PerfDataBase{ + Type: "network-connection-detected", + TimeStamp: time.Now().Unix(), + }, + LocalAddress: localAddr, + RemoteAddress: remoteAddr, + InterfaceIndex: convert2Int64(msgValue[2]), + Pid: convert2Int64(msgValue[3]), + RecvBufferSize: convert2Int64(msgValue[4]), + RecvBufferUsed: convert2Int64(msgValue[5]), + SerialNumber: convert2Int64(msgValue[6]), + Kind: convert2Int64(msgValue[7]), + } + netBytes, _ = json.Marshal(netData) + } else if msgType == 2 { + // connection-update + // ['RxPackets', 'RxBytes', 'TxPackets', 'TxBytes', // 'RxDups', 'RxOOO', 'TxRetx', 'MinRTT', 'AvgRTT', 'ConnectionSerial'] // e.g. [2, [21, 1708, 22, 14119, 309, 0, 5830, 0.076125, 0.076125, 54, -1]] netData := NetworkDataConnectionUpdate{ @@ -484,13 +716,34 @@ type NetworkDataConnectionUpdate struct { ConnectionSerial int64 `json:"connection_serial"` // 9 } -func (c *perfdClient) startGetFPS(ctx context.Context) ( - cancel context.CancelFunc, err error) { +func (d *device) newPerfdGraphicsOpengl(options *PerfOptions) (*perfdGraphicsOpengl, error) { + instruments, err := d.newInstrumentsService() + if err != nil { + return nil, err + } + return &perfdGraphicsOpengl{ + perfdClient: perfdClient{ + i: instruments, + options: options, + stop: make(chan struct{}), + }, + chanGPU: make(chan []byte, 10), + chanFPS: make(chan []byte, 10), + }, nil +} + +type perfdGraphicsOpengl struct { + perfdClient + chanGPU chan []byte // gpu channel + chanFPS chan []byte // fps channel +} + +func (c *perfdGraphicsOpengl) Start() (data <-chan []byte, err error) { if _, err = c.i.call( instrumentsServiceGraphicsOpengl, "setSamplingRate:", - float64(c.option.OutputInterval)/100, + float64(c.options.OutputInterval)/100, ); err != nil { return nil, err } @@ -503,37 +756,68 @@ func (c *perfdClient) startGetFPS(ctx context.Context) ( return nil, err } - ctx, cancel = context.WithCancel(ctx) + ctx, cancel := context.WithCancel(context.TODO()) c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { select { case <-ctx.Done(): c.i.call(instrumentsServiceGraphicsOpengl, "stopSampling") return default: - c.parseFPS(m.Obj) + c.parseData(m.Obj) } }) + c.cancel = cancel + + outCh := make(chan []byte, 100) + + go func() { + for { + select { + case <-c.stop: + c.cancel() + return + case gpuBytes, ok := <-c.chanGPU: + if ok { + outCh <- gpuBytes + } + case fpsBytes, ok := <-c.chanFPS: + if ok { + outCh <- fpsBytes + } + } + } + }() + + return outCh, nil +} - return +func (c *perfdGraphicsOpengl) Stop() { + close(c.stop) } -func (c *perfdClient) parseFPS(data interface{}) { +func (c *perfdGraphicsOpengl) parseData(data interface{}) { // data example: // map[ // Alloc system memory:50167808 // Allocated PB Size:1179648 // CoreAnimationFramesPerSecond:0 // fps from GPU - // Device Utilization %:0 + // Device Utilization %:0 // device // IOGLBundleName:Built-In // In use system memory:10633216 - // Renderer Utilization %:0 + // Renderer Utilization %:0 // renderer // SplitSceneCount:0 // TiledSceneBytes:0 - // Tiler Utilization %:0 + // Tiler Utilization %:0 // tiler // XRVideoCardRunTimeStamp:1010679 // recoveryCount:0 // ] + gpuInfo := GPUData{ + PerfDataBase: PerfDataBase{ + Type: "gpu", + TimeStamp: time.Now().Unix(), + }, + } fpsInfo := FPSData{ PerfDataBase: PerfDataBase{ Type: "fps", @@ -542,386 +826,43 @@ func (c *perfdClient) parseFPS(data interface{}) { } defer func() { - fpsBytes, _ := json.Marshal(fpsInfo) - c.chanFPS <- fpsBytes + if c.options.gpu { + gpuBytes, _ := json.Marshal(gpuInfo) + c.chanGPU <- gpuBytes + } + if c.options.FPS { + fpsBytes, _ := json.Marshal(fpsInfo) + c.chanFPS <- fpsBytes + } }() raw, ok := data.(map[string]interface{}) if !ok { - fpsInfo.Msg = fmt.Sprintf("invalid graphics.opengl data: %v", data) + gpuInfo.Msg = fmt.Sprintf("invalid graphics.opengl data: %v", data) return } + // gpu + gpuInfo.DeviceUtilization = convert2Int64(raw["Device Utilization %"]) + gpuInfo.TilerUtilization = convert2Int64(raw["Tiler Utilization %"]) + gpuInfo.RendererUtilization = convert2Int64(raw["Renderer Utilization %"]) + // fps fpsInfo.FPS = int(convert2Int64(raw["CoreAnimationFramesPerSecond"])) } -func (c *perfdClient) startGetGPU(ctx context.Context) ( - cancel context.CancelFunc, err error) { - - if _, err = c.i.call( - instrumentsServiceGraphicsOpengl, - "setSamplingRate:", - float64(c.option.OutputInterval)/100, - ); err != nil { - return nil, err - } - - if _, err = c.i.call( - instrumentsServiceGraphicsOpengl, - "startSamplingAtTimeInterval:", - 0, - ); err != nil { - return nil, err - } - - ctx, cancel = context.WithCancel(ctx) - c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { - select { - case <-ctx.Done(): - c.i.call(instrumentsServiceGraphicsOpengl, "stopSampling") - return - default: - c.parseGPU(m.Obj) - } - }) - - return -} - -func (c *perfdClient) parseGPU(data interface{}) { - // data example: - // map[ - // Alloc system memory:50167808 - // Allocated PB Size:1179648 - // CoreAnimationFramesPerSecond:0 - // Device Utilization %:0 // device - // IOGLBundleName:Built-In - // In use system memory:10633216 - // Renderer Utilization %:0 // renderer - // SplitSceneCount:0 - // TiledSceneBytes:0 - // Tiler Utilization %:0 // tiler - // XRVideoCardRunTimeStamp:1010679 - // recoveryCount:0 - // ] - - gpuInfo := GPUData{ - PerfDataBase: PerfDataBase{ - Type: "gpu", - TimeStamp: time.Now().Unix(), - }, - } - - defer func() { - gpuBytes, _ := json.Marshal(gpuInfo) - c.chanGPU <- gpuBytes - }() - - raw, ok := data.(map[string]interface{}) - if !ok { - gpuInfo.Msg = fmt.Sprintf("invalid graphics.opengl data: %v", data) - return - } - - // gpu - gpuInfo.DeviceUtilization = convert2Int64(raw["Device Utilization %"]) - gpuInfo.TilerUtilization = convert2Int64(raw["Tiler Utilization %"]) - gpuInfo.RendererUtilization = convert2Int64(raw["Renderer Utilization %"]) -} - -type GPUData struct { - PerfDataBase // gpu - TilerUtilization int64 `json:"tiler_utilization"` // 处理顶点的 GPU 时间占比 - DeviceUtilization int64 `json:"device_utilization"` // 设备利用率 - RendererUtilization int64 `json:"renderer_utilization"` // 渲染器利用率 -} +type GPUData struct { + PerfDataBase // gpu + TilerUtilization int64 `json:"tiler_utilization"` // 处理顶点的 GPU 时间占比 + DeviceUtilization int64 `json:"device_utilization"` // 设备利用率 + RendererUtilization int64 `json:"renderer_utilization"` // 渲染器利用率 +} type FPSData struct { PerfDataBase // fps FPS int `json:"fps"` } -func (c *perfdClient) registerSysmontap(ctx context.Context) ( - cancel context.CancelFunc, err error) { - - // set config - config := map[string]interface{}{ - "bm": 0, - "cpuUsage": true, - "sampleInterval": time.Second * 1, // 1s - "ur": c.option.OutputInterval, // 输出频率 - "procAttrs": c.option.ProcessAttributes, // process performance - "sysAttrs": c.option.SystemAttributes, // system performance - } - if _, err = c.i.call( - instrumentsServiceSysmontap, - "setConfig:", - config, - ); err != nil { - return nil, err - } - - // start - if _, err = c.i.call( - instrumentsServiceSysmontap, - "start", - ); err != nil { - return nil, err - } - - // register listener - ctx, cancel = context.WithCancel(ctx) - c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) { - select { - case <-ctx.Done(): - c.i.call(instrumentsServiceSysmontap, "stop") - return - default: - dataArray, ok := m.Obj.([]interface{}) - if !ok || len(dataArray) != 2 { - return - } - - if c.option.Pid != 0 { - c.parseProcessData(dataArray) - } else { - c.parseSystemData(dataArray) - } - } - }) - - return cancel, err -} - -func (c *perfdClient) parseProcessData(dataArray []interface{}) { - // dataArray example: - // [ - // map[ - // CPUCount:2 - // EnabledCPUs:2 - // PerCPUUsage:[ - // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:3.6363636363636402 CPU_UserLoad:-1] - // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:2.7272727272727195 CPU_UserLoad:-1] - // ] - // System:[36408520704 6897049600 3031160 773697 15596 61940 1297 26942 588 17020 127346 1835008 119718056 107009899 174046 103548] - // SystemCPUUsage:map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:6.36363636363636 CPU_UserLoad:-1] - // StartMachAbsTime:5896602132889 - // EndMachAbsTime:5896628486761 - // Type:41 - // ] - // map[ - // Processes:map[ - // 0:[1.3582834340402803 0] - // 124:[0.011456702068519481 124] - // 136:[0.05468332721703649 136] - // ] - // StartMachAbsTime:5896602295095 - // EndMachAbsTime:5896628780514 - // Type:5 - // ] - // ] - - processData := make(map[string]interface{}) - processData["type"] = "process" - processData["timestamp"] = time.Now().Unix() - processData["pid"] = c.option.Pid - - defer func() { - processBytes, _ := json.Marshal(processData) - c.chanProcess <- processBytes - }() - - systemInfo := dataArray[0].(map[string]interface{}) - processInfo := dataArray[1].(map[string]interface{}) - if _, ok := systemInfo["System"]; !ok { - systemInfo, processInfo = processInfo, systemInfo - } - - var targetProcessValue []interface{} - processList := processInfo["Processes"].(map[string]interface{}) - for pid, v := range processList { - if pid != strconv.Itoa(c.option.Pid) { - continue - } - targetProcessValue = v.([]interface{}) - } - - if targetProcessValue == nil { - processData["msg"] = fmt.Sprintf("process %d not found", c.option.Pid) - return - } - - processAttributesMap := make(map[string]interface{}) - for idx, value := range c.option.ProcessAttributes { - processAttributesMap[value] = targetProcessValue[idx] - } - processData["proc_perf"] = processAttributesMap - - systemAttributesValue := systemInfo["System"].([]interface{}) - systemAttributesMap := make(map[string]int64) - for idx, value := range c.option.SystemAttributes { - systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx]) - } - processData["sys_perf"] = systemAttributesMap -} - -func (c *perfdClient) parseSystemData(dataArray []interface{}) { - timestamp := time.Now().Unix() - var systemInfo map[string]interface{} - data1 := dataArray[0].(map[string]interface{}) - data2 := dataArray[1].(map[string]interface{}) - if _, ok := data1["SystemCPUUsage"]; ok { - systemInfo = data1 - } else { - systemInfo = data2 - } - - // systemInfo example: - // map[ - // CPUCount:2 - // EnabledCPUs:2 - // PerCPUUsage:[ - // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:3.9215686274509807 CPU_UserLoad:-1] - // map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:11.650485436893206 CPU_UserLoad:-1]] - // ] - // System:[704211 35486281728 6303789056 3001119 1001 11033 52668 1740 40022 2114 17310 126903 1835008 160323 107909856 95067 95808179] - // SystemCPUUsage:map[ - // CPU_NiceLoad:0 - // CPU_SystemLoad:-1 - // CPU_TotalLoad:15.572054064344186 - // CPU_UserLoad:-1 - // ] - // StartMachAbsTime:5339240248449 - // EndMachAbsTime:5339264441260 - // Type:41 - // ] - - if c.option.SysCPU { - sysCPUUsage := systemInfo["SystemCPUUsage"].(map[string]interface{}) - sysCPUInfo := SystemCPUData{ - PerfDataBase: PerfDataBase{ - Type: "sys_cpu", - TimeStamp: timestamp, - }, - NiceLoad: sysCPUUsage["CPU_NiceLoad"].(float64), - SystemLoad: sysCPUUsage["CPU_SystemLoad"].(float64), - TotalLoad: sysCPUUsage["CPU_TotalLoad"].(float64), - UserLoad: sysCPUUsage["CPU_UserLoad"].(float64), - } - cpuBytes, _ := json.Marshal(sysCPUInfo) - c.chanSysCPU <- cpuBytes - } - - systemAttributesValue := systemInfo["System"].([]interface{}) - systemAttributesMap := make(map[string]int64) - for idx, value := range c.option.SystemAttributes { - systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx]) - } - - if c.option.SysMem { - kernelPageSize := int64(1) // why 16384 ? - appMemory := (systemAttributesMap["vmIntPageCount"] - systemAttributesMap["vmPurgeableCount"]) * kernelPageSize - cachedFiles := (systemAttributesMap["vmExtPageCount"] - systemAttributesMap["vmPurgeableCount"]) * kernelPageSize - compressed := systemAttributesMap["vmCompressorPageCount"] * kernelPageSize - usedMemory := (systemAttributesMap["vmUsedCount"] - systemAttributesMap["vmExtPageCount"]) * kernelPageSize - wiredMemory := systemAttributesMap["vmWireCount"] * kernelPageSize - swapUsed := systemAttributesMap["__vmSwapUsage"] - freeMemory := systemAttributesMap["vmFreeCount"] * kernelPageSize - - sysMemInfo := SystemMemData{ - PerfDataBase: PerfDataBase{ - Type: "sys_mem", - TimeStamp: timestamp, - }, - AppMemory: appMemory, - UsedMemory: usedMemory, - WiredMemory: wiredMemory, - FreeMemory: freeMemory, - CachedFiles: cachedFiles, - Compressed: compressed, - SwapUsed: swapUsed, - } - memBytes, _ := json.Marshal(sysMemInfo) - c.chanSysMem <- memBytes - } - - if c.option.SysDisk { - diskBytesRead := systemAttributesMap["diskBytesRead"] - diskBytesWritten := systemAttributesMap["diskBytesWritten"] - diskReadOps := systemAttributesMap["diskReadOps"] - diskWriteOps := systemAttributesMap["diskWriteOps"] - - sysDiskInfo := SystemDiskData{ - PerfDataBase: PerfDataBase{ - Type: "sys_disk", - TimeStamp: timestamp, - }, - DataRead: diskBytesRead, - DataWritten: diskBytesWritten, - ReadOps: diskReadOps, - WriteOps: diskWriteOps, - } - diskBytes, _ := json.Marshal(sysDiskInfo) - c.chanSysDisk <- diskBytes - } - - if c.option.SysNetwork { - netBytesIn := systemAttributesMap["netBytesIn"] - netBytesOut := systemAttributesMap["netBytesOut"] - netPacketsIn := systemAttributesMap["netPacketsIn"] - netPacketsOut := systemAttributesMap["netPacketsOut"] - - sysNetworkInfo := SystemNetworkData{ - PerfDataBase: PerfDataBase{ - Type: "sys_network", - TimeStamp: timestamp, - }, - BytesIn: netBytesIn, - BytesOut: netBytesOut, - PacketsIn: netPacketsIn, - PacketsOut: netPacketsOut, - } - networkBytes, _ := json.Marshal(sysNetworkInfo) - c.chanSysNetwork <- networkBytes - } -} - -type SystemCPUData struct { - PerfDataBase // system cpu - NiceLoad float64 `json:"nice_load"` - SystemLoad float64 `json:"system_load"` - TotalLoad float64 `json:"total_load"` - UserLoad float64 `json:"user_load"` -} - -type SystemMemData struct { - PerfDataBase // mem - AppMemory int64 `json:"app_memory"` - FreeMemory int64 `json:"free_memory"` - UsedMemory int64 `json:"used_memory"` - WiredMemory int64 `json:"wired_memory"` - CachedFiles int64 `json:"cached_files"` - Compressed int64 `json:"compressed"` - SwapUsed int64 `json:"swap_used"` -} - -type SystemDiskData struct { - PerfDataBase // disk - DataRead int64 `json:"data_read"` - DataWritten int64 `json:"data_written"` - ReadOps int64 `json:"reads_in"` - WriteOps int64 `json:"writes_out"` -} - -type SystemNetworkData struct { - PerfDataBase // network - BytesIn int64 `json:"bytes_in"` - BytesOut int64 `json:"bytes_out"` - PacketsIn int64 `json:"packets_in"` - PacketsOut int64 `json:"packets_out"` -} - func convert2Int64(num interface{}) int64 { switch value := num.(type) { case int64: diff --git a/perfd_test.go b/perfd_test.go index 33abcaf..14cf607 100644 --- a/perfd_test.go +++ b/perfd_test.go @@ -128,3 +128,32 @@ func TestPerfNetwork(t *testing.T) { } } } + +func TestPerfAll(t *testing.T) { + setupLockdownSrv(t) + + data, err := dev.PerfStart( + WithPerfSystemCPU(true), + WithPerfSystemMem(true), + WithPerfSystemDisk(true), + WithPerfSystemNetwork(true), + WithPerfNetwork(true), + WithPerfFPS(true), + WithPerfGPU(true), + WithPerfBundleID("com.apple.mobilesafari"), + ) + if err != nil { + t.Fatal(err) + } + + timer := time.NewTimer(time.Duration(time.Second * 10)) + for { + select { + case <-timer.C: + dev.PerfStop() + return + case d := <-data: + fmt.Println(string(d)) + } + } +}