package gidevice import ( "encoding/json" "fmt" "github.com/httprunner/httprunner/v4/hrp/pkg/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" // 获取 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" // 监控应用状态 ) const ( instrumentsServiceXcodeNetworkStatistics = "com.apple.xcode.debug-gauge-data-providers.NetworkStatistics" // 获取单进程网络数据 instrumentsServiceXcodeEnergyStatistics = "com.apple.xcode.debug-gauge-data-providers.Energy" // 获取功耗数据 ) var _ Instruments = (*instruments)(nil) func newInstruments(client *libimobiledevice.InstrumentsClient) *instruments { return &instruments{ client: client, } } type instruments struct { client *libimobiledevice.InstrumentsClient } func (i *instruments) notifyOfPublishedCapabilities() (err error) { _, err = i.client.NotifyOfPublishedCapabilities() return } 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) 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 = "" opt.options = map[string]interface{}{ "StartSuspendedKey": uint64(0), "KillExisting": uint64(0), } if len(opts) != 0 { for _, optFunc := range opts { optFunc(opt) } } var id uint32 if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil { return 0, err } args := libimobiledevice.NewAuxBuffer() if err = args.AppendObject(opt.appPath); err != nil { return 0, err } if err = args.AppendObject(bundleID); err != nil { return 0, err } if err = args.AppendObject(opt.environment); err != nil { return 0, err } if err = args.AppendObject(opt.arguments); err != nil { return 0, err } if err = args.AppendObject(opt.options); err != nil { return 0, err } var result *libimobiledevice.DTXMessageResult selector := "launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options:" if result, err = i.client.Invoke(selector, args, id, true); err != nil { return 0, err } if nsErr, ok := result.Obj.(libimobiledevice.NSError); ok { return 0, fmt.Errorf("%s", nsErr.NSUserInfo.(map[string]interface{})["NSLocalizedDescription"]) } return int(result.Obj.(uint64)), nil } func (i *instruments) appProcess(bundleID string) (err error) { var id uint32 if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil { return err } args := libimobiledevice.NewAuxBuffer() if err = args.AppendObject(bundleID); err != nil { return err } selector := "processIdentifierForBundleIdentifier:" if _, err = i.client.Invoke(selector, args, id, true); err != nil { return err } return } func (i *instruments) startObserving(pid int) (err error) { var id uint32 if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil { return err } args := libimobiledevice.NewAuxBuffer() if err = args.AppendObject(pid); err != nil { return err } var result *libimobiledevice.DTXMessageResult selector := "startObservingPid:" if result, err = i.client.Invoke(selector, args, id, true); err != nil { return err } if nsErr, ok := result.Obj.(libimobiledevice.NSError); ok { return fmt.Errorf("%s", nsErr.NSUserInfo.(map[string]interface{})["NSLocalizedDescription"]) } return } func (i *instruments) AppKill(pid int) (err error) { var id uint32 if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil { return err } args := libimobiledevice.NewAuxBuffer() if err = args.AppendObject(pid); err != nil { return err } selector := "killPid:" if _, err = i.client.Invoke(selector, args, id, false); err != nil { return err } return } func (i *instruments) AppRunningProcesses() (processes []Process, err error) { var id uint32 if id, err = i.requestChannel(instrumentsServiceDeviceInfo); err != nil { return nil, err } selector := "runningProcesses" var result *libimobiledevice.DTXMessageResult if result, err = i.client.Invoke(selector, libimobiledevice.NewAuxBuffer(), id, true); err != nil { return nil, err } objs := result.Obj.([]interface{}) processes = make([]Process, 0, len(objs)) for _, v := range objs { m := v.(map[string]interface{}) var data []byte if data, err = json.Marshal(m); err != nil { debugLog(fmt.Sprintf("process marshal: %v\n%v\n", err, m)) err = nil continue } var tp Process if err = json.Unmarshal(data, &tp); err != nil { debugLog(fmt.Sprintf("process unmarshal: %v\n%v\n", err, m)) err = nil continue } processes = append(processes, tp) } return } func (i *instruments) AppList(opts ...AppListOption) (apps []Application, err error) { opt := new(appListOption) opt.updateToken = "" opt.appsMatching = make(map[string]interface{}) if len(opts) != 0 { for _, optFunc := range opts { optFunc(opt) } } var id uint32 if id, err = i.requestChannel(instrumentsServiceDeviceApplictionListing); err != nil { return nil, err } args := libimobiledevice.NewAuxBuffer() if err = args.AppendObject(opt.appsMatching); err != nil { return nil, err } if err = args.AppendObject(opt.updateToken); err != nil { return nil, err } selector := "installedApplicationsMatching:registerUpdateToken:" var result *libimobiledevice.DTXMessageResult if result, err = i.client.Invoke(selector, args, id, true); err != nil { return nil, err } objs := result.Obj.([]interface{}) for _, v := range objs { m := v.(map[string]interface{}) var data []byte if data, err = json.Marshal(m); err != nil { debugLog(fmt.Sprintf("application marshal: %v\n%v\n", err, m)) err = nil continue } var app Application if err = json.Unmarshal(data, &app); err != nil { debugLog(fmt.Sprintf("application unmarshal: %v\n%v\n", err, m)) err = nil continue } apps = append(apps, app) } return } func (i *instruments) DeviceInfo() (devInfo *DeviceInfo, err error) { var id uint32 if id, err = i.requestChannel(instrumentsServiceDeviceInfo); err != nil { return nil, err } selector := "systemInformation" var result *libimobiledevice.DTXMessageResult if result, err = i.client.Invoke(selector, libimobiledevice.NewAuxBuffer(), id, true); err != nil { return nil, err } data, err := json.Marshal(result.Obj) if err != nil { return nil, err } devInfo = new(DeviceInfo) err = json.Unmarshal(data, devInfo) return } func (i *instruments) registerCallback(obj string, cb func(m libimobiledevice.DTXMessageResult)) { i.client.RegisterCallback(obj, cb) } type Application struct { AppExtensionUUIDs []string `json:"AppExtensionUUIDs,omitempty"` BundlePath string `json:"BundlePath"` CFBundleIdentifier string `json:"CFBundleIdentifier"` ContainerBundleIdentifier string `json:"ContainerBundleIdentifier,omitempty"` ContainerBundlePath string `json:"ContainerBundlePath,omitempty"` DisplayName string `json:"DisplayName"` ExecutableName string `json:"ExecutableName,omitempty"` Placeholder bool `json:"Placeholder,omitempty"` PluginIdentifier string `json:"PluginIdentifier,omitempty"` PluginUUID string `json:"PluginUUID,omitempty"` Restricted int `json:"Restricted"` Type string `json:"Type"` Version string `json:"Version"` } type DeviceInfo struct { Description string `json:"_deviceDescription"` DisplayName string `json:"_deviceDisplayName"` Identifier string `json:"_deviceIdentifier"` Version string `json:"_deviceVersion"` ProductType string `json:"_productType"` ProductVersion string `json:"_productVersion"` XRDeviceClassName string `json:"_xrdeviceClassName"` }