From 6a95c27937abf6c9dc2e1afb42c153ef70d665d3 Mon Sep 17 00:00:00 2001 From: fish-sauce Date: Thu, 17 Oct 2024 17:10:34 -0500 Subject: [PATCH 01/10] Add battery ioregistry stats --- ios/diagnostics/diagnostics.go | 21 +++++++++++++++++ ios/diagnostics/ioregistry.go | 43 +++++++++++++++++++--------------- ios/diagnostics/request.go | 17 ++++++++++---- main.go | 26 ++++++++++++++++++-- 4 files changed, 82 insertions(+), 25 deletions(-) diff --git a/ios/diagnostics/diagnostics.go b/ios/diagnostics/diagnostics.go index 52d7452a..32f536c3 100644 --- a/ios/diagnostics/diagnostics.go +++ b/ios/diagnostics/diagnostics.go @@ -33,6 +33,27 @@ func Reboot(device ios.DeviceEntry) error { return service.Close() } +// Battery extracts the battery ioregistry stats like Temperature, Voltage, CurrentCapacity +func (diagnosticsConn *Connection) Battery() (IORegistry, error) { + req := newIORegistryRequest() + req.addClass("IOPMPowerSource") + + reader := diagnosticsConn.deviceConn.Reader() + encoded, err := req.encoded() + if err != nil { + return IORegistry{}, err + } + err = diagnosticsConn.deviceConn.Send(encoded) + if err != nil { + return IORegistry{}, err + } + response, err := diagnosticsConn.plistCodec.Decode(reader) + if err != nil { + return IORegistry{}, err + } + return diagnosticsfromBytes(response).Diagnostics.IORegistry, nil +} + func (diagnosticsConn *Connection) Reboot() error { req := rebootRequest{Request: "Restart", WaitForDisconnect: true, DisplayFail: true, DisplayPass: true} reader := diagnosticsConn.deviceConn.Reader() diff --git a/ios/diagnostics/ioregistry.go b/ios/diagnostics/ioregistry.go index 551ce2ac..c05d2a12 100644 --- a/ios/diagnostics/ioregistry.go +++ b/ios/diagnostics/ioregistry.go @@ -2,27 +2,32 @@ package diagnostics import ios "github.com/danielpaulus/go-ios/ios" -func ioregentryRequest(key string) []byte { - requestMap := map[string]interface{}{ - "Request": "IORegistry", - "EntryName": key, - } - bt, err := ios.PlistCodec{}.Encode(requestMap) - if err != nil { - panic("query request encoding should never fail") - } - return bt +type ioregistryRequest struct { + reqMap map[string]string } -func (diagnosticsConn *Connection) IORegEntryQuery(key string) (interface{}, error) { - err := diagnosticsConn.deviceConn.Send(ioregentryRequest(key)) - if err != nil { - return "", err - } - respBytes, err := diagnosticsConn.plistCodec.Decode(diagnosticsConn.deviceConn.Reader()) +func newIORegistryRequest() *ioregistryRequest { + return &ioregistryRequest{map[string]string{ + "Request": "IORegistry", + }} +} + +func (req *ioregistryRequest) addPlane(plane string) { + req.reqMap["CurrentPlane"] = plane +} + +func (req *ioregistryRequest) addName(name string) { + req.reqMap["EntryName"] = name +} + +func (req *ioregistryRequest) addClass(class string) { + req.reqMap["EntryClass"] = class +} + +func (req *ioregistryRequest) encoded() ([]byte, error) { + bt, err := ios.PlistCodec{}.Encode(req.reqMap) if err != nil { - return "", err + return nil, err } - plist, err := ios.ParsePlist(respBytes) - return plist, err + return bt, nil } diff --git a/ios/diagnostics/request.go b/ios/diagnostics/request.go index 7fe07538..a834b4cb 100644 --- a/ios/diagnostics/request.go +++ b/ios/diagnostics/request.go @@ -30,10 +30,19 @@ type allDiagnosticsResponse struct { } type Diagnostics struct { - GasGauge GasGauge - HDMI HDMI - NAND NAND - WiFi WiFi + GasGauge GasGauge + HDMI HDMI + NAND NAND + WiFi WiFi + IORegistry IORegistry +} + +type IORegistry struct { + InstantAmperage int + Temperature int + Voltage int + IsCharging bool + CurrentCapacity int } type WiFi struct { diff --git a/main.go b/main.go index 4d2fe381..a93cc458 100644 --- a/main.go +++ b/main.go @@ -127,6 +127,7 @@ Usage: ios zoomtouch (enable | disable | toggle | get) [--force] [options] ios diskspace [options] ios batterycheck [options] + ios batteryregistry [options] ios tunnel start [options] [--pair-record-path=] [--userspace] ios tunnel ls [options] ios tunnel stopagent @@ -239,6 +240,7 @@ The commands work as following: ios timeformat (24h | 12h | toggle | get) [--force] [options] Sets, or returns the state of the "time format". iOS 11+ only (Use --force to try on older versions). ios diskspace [options] Prints disk space info. ios batterycheck [options] Prints battery info. + ios batteryregistry [options] Prints battery registry stats like Temperature, Voltage. ios tunnel start [options] [--pair-record-path=] [--enabletun] Creates a tunnel connection to the device. If the device was not paired with the host yet, device pairing will also be executed. > On systems with System Integrity Protection enabled the argument '--pair-record-path=default' can be used to point to /var/db/lockdown/RemotePairing/user_501. > If nothing is specified, the current dir is used for the pair record. @@ -926,6 +928,11 @@ The commands work as following: } } + b, _ = arguments.Bool("batteryregistry") + if b { + printBatteryRegistry(device) + } + b, _ = arguments.Bool("reboot") if b { err := diagnostics.Reboot(device) @@ -1557,8 +1564,8 @@ func startAx(device ios.DeviceEntry, arguments docopt.Opts) { /* conn.GetElement() time.Sleep(time.Second) conn.TurnOff()*/ - //conn.GetElement() - //conn.GetElement() + // conn.GetElement() + // conn.GetElement() exitIfError("ax failed", err) }() @@ -1674,6 +1681,21 @@ func printBatteryDiagnostics(device ios.DeviceEntry) { fmt.Println(convertToJSONString(battery)) } +func printBatteryRegistry(device ios.DeviceEntry) { + conn, err := diagnostics.New(device) + if err != nil { + exitIfError("failed diagnostics service", err) + } + defer conn.Close() + + stats, err := conn.Battery() + if err != nil { + exitIfError("failed to get battery stats", err) + } + + fmt.Println(convertToJSONString(stats)) +} + func printDeviceDate(device ios.DeviceEntry) { allValues, err := ios.GetValues(device) exitIfError("failed getting values", err) From 456904f86b6fe77972e8aec1a539e3e07671442c Mon Sep 17 00:00:00 2001 From: fish-sauce Date: Tue, 22 Oct 2024 11:05:47 -0500 Subject: [PATCH 02/10] Add sysmontap cpu usage stats --- ios/dtx_codec/channel.go | 24 +++++ ios/dtx_codec/connection.go | 1 + ios/instruments/instruments_deviceinfo.go | 18 ++++ ios/instruments/instruments_sysmontap.go | 112 ++++++++++++++++++++++ main.go | 43 +++++++++ 5 files changed, 198 insertions(+) create mode 100644 ios/instruments/instruments_sysmontap.go diff --git a/ios/dtx_codec/channel.go b/ios/dtx_codec/channel.go index 700b7973..10a98624 100644 --- a/ios/dtx_codec/channel.go +++ b/ios/dtx_codec/channel.go @@ -9,12 +9,21 @@ import ( log "github.com/sirupsen/logrus" ) +type messageReceiver struct { + msgChannel chan Message +} + +func newMessageReceiver() *messageReceiver { + return &messageReceiver{msgChannel: make(chan Message)} +} + type Channel struct { channelCode int channelName string messageIdentifier int connection *Connection messageDispatcher Dispatcher + messageReceiver *messageReceiver responseWaiters map[int]chan Message defragmenters map[int]*FragmentDecoder registeredMethods map[string]chan Message @@ -123,6 +132,15 @@ func (d *Channel) SendAndAwaitReply(expectsReply bool, messageType MessageType, } } +func (d *Channel) Receive() (Message, error) { + select { + case response := <-d.messageReceiver.msgChannel: + return response, nil + case <-time.After(30 * time.Second): + return Message{}, fmt.Errorf("exceeded waiting time message:%d channel:%d", d.messageIdentifier, d.channelCode) + } +} + func (d *Channel) Dispatch(msg Message) { d.mutex.Lock() if msg.Identifier >= d.messageIdentifier { @@ -136,6 +154,12 @@ func (d *Channel) Dispatch(msg Message) { return } } + if msg.PayloadHeader.MessageType == UnknownTypeOne { + d.mutex.Unlock() + d.messageReceiver.msgChannel <- msg + + return + } d.mutex.Unlock() if msg.ConversationIndex > 0 || msg.IsFragment() { d.mutex.Lock() diff --git a/ios/dtx_codec/connection.go b/ios/dtx_codec/connection.go index ebec76da..be4b3894 100644 --- a/ios/dtx_codec/connection.go +++ b/ios/dtx_codec/connection.go @@ -149,6 +149,7 @@ func newDtxConnection(conn ios.DeviceConnectionInterface) (*Connection, error) { channelCode: 0, messageIdentifier: 5, channelName: "global_channel", connection: dtxConnection, messageDispatcher: NewGlobalDispatcher(requestChannelMessages, dtxConnection), + messageReceiver: newMessageReceiver(), responseWaiters: map[int]chan Message{}, registeredMethods: map[string]chan Message{}, defragmenters: map[int]*FragmentDecoder{}, diff --git a/ios/instruments/instruments_deviceinfo.go b/ios/instruments/instruments_deviceinfo.go index be4e2ca1..a6db4611 100644 --- a/ios/instruments/instruments_deviceinfo.go +++ b/ios/instruments/instruments_deviceinfo.go @@ -20,6 +20,24 @@ type ProcessInfo struct { StartDate time.Time } +// ProcessAttributes returns the attributes list which can be used for monitoring +func (d DeviceInfoService) ProcessAttributes() ([]interface{}, error) { + resp, err := d.channel.MethodCall("sysmonProcessAttributes") + if err != nil { + return nil, err + } + return resp.Payload[0].([]interface{}), nil +} + +// SystemAttributes returns the attributes list which can be used for monitoring +func (d DeviceInfoService) SystemAttributes() ([]interface{}, error) { + resp, err := d.channel.MethodCall("sysmonSystemAttributes") + if err != nil { + return nil, err + } + return resp.Payload[0].([]interface{}), nil +} + // ProcessList returns a []ProcessInfo, one for each process running on the iOS device func (d DeviceInfoService) ProcessList() ([]ProcessInfo, error) { resp, err := d.channel.MethodCall("runningProcesses") diff --git a/ios/instruments/instruments_sysmontap.go b/ios/instruments/instruments_sysmontap.go new file mode 100644 index 00000000..0ead6e01 --- /dev/null +++ b/ios/instruments/instruments_sysmontap.go @@ -0,0 +1,112 @@ +package instruments + +import ( + "fmt" + + "github.com/danielpaulus/go-ios/ios" + dtx "github.com/danielpaulus/go-ios/ios/dtx_codec" +) + +const sysmontapName = "com.apple.instruments.server.services.sysmontap" + +type SysmontapService struct { + channel *dtx.Channel + conn *dtx.Connection + plistCodec ios.PlistCodec +} + +func NewSysmontapService(device ios.DeviceEntry) (*SysmontapService, error) { + dtxConn, err := connectInstruments(device) + if err != nil { + return nil, err + } + processControlChannel := dtxConn.RequestChannelIdentifier(sysmontapName, loggingDispatcher{dtxConn}) + + return &SysmontapService{channel: processControlChannel, conn: dtxConn, plistCodec: ios.NewPlistCodec()}, nil +} + +// Close closes up the DTX connection +func (s *SysmontapService) Close() { + s.conn.Close() +} + +// Start sends a start method call async and waits until the cpu info & stats come back +func (s *SysmontapService) Start() (SysmontapMessage, error) { + err := s.channel.MethodCallAsync("start") + if err != nil { + return SysmontapMessage{}, err + } + + globalChannel := s.conn.GlobalChannel() + + // Receive() will block until the message with the CPU usage is delivered + msg, err := globalChannel.Receive() + if err != nil { + return SysmontapMessage{}, err + } + + sysmontapMessage, err := mapToCPUUsage(msg) + if err != nil { + return SysmontapMessage{}, err + } + + return sysmontapMessage, nil +} + +func (s *SysmontapService) SetConfig(procAttrs, sysAttrs []interface{}) error { + config := map[string]interface{}{ + "ur": 500, + "bm": 0, + "procAttrs": procAttrs, + "sysAttrs": sysAttrs, + "cpuUsage": true, + "physFootprint": true, + "sampleInterval": 500000000, + } + + _, err := s.channel.MethodCall("setConfig:", config) + + if err != nil { + return err + } + + return nil +} + +type SysmontapMessage struct { + CPUCount uint64 + EnabledCPUs uint64 + EndMachAbsTime uint64 + Type uint64 + SystemCPUUsage CPUUsage +} + +type CPUUsage struct { + CPU_TotalLoad float64 +} + +func mapToCPUUsage(msg dtx.Message) (SysmontapMessage, error) { + payload := msg.Payload + if len(payload) != 1 { + return SysmontapMessage{}, fmt.Errorf("payload of message should have only one element: %+v", msg) + } + + resultArray := payload[0].([]interface{}) + resultMap := resultArray[0].(map[string]interface{}) + cpuCount := resultMap["CPUCount"].(uint64) + enabledCPUs := resultMap["EnabledCPUs"].(uint64) + endMachAbsTime := resultMap["EndMachAbsTime"].(uint64) + typ := resultMap["Type"].(uint64) + sysmontapMessageMap := resultMap["SystemCPUUsage"].(map[string]interface{}) + cpuTotalLoad := sysmontapMessageMap["CPU_TotalLoad"].(float64) + cpuUsage := CPUUsage{CPU_TotalLoad: cpuTotalLoad} + + sysmontapMessage := SysmontapMessage{ + cpuCount, + enabledCPUs, + endMachAbsTime, + typ, + cpuUsage, + } + return sysmontapMessage, nil +} diff --git a/main.go b/main.go index a93cc458..5253d4be 100644 --- a/main.go +++ b/main.go @@ -104,6 +104,7 @@ Usage: ios forward [options] ios dproxy [--binary] [--mode=] [--iface=] [options] ios readpair [options] + ios sysmontap [options] ios pcap [options] [--pid=] [--process=] ios install --path= [options] ios uninstall [options] @@ -213,6 +214,7 @@ The commands work as following: > to stop usbmuxd and load to start it again should the proxy mess up things. > The --binary flag will dump everything in raw binary without any decoding. ios readpair Dump detailed information about the pairrecord for a device. + ios sysmontap Get system stats like MEM, CPU ios install --path= [options] Specify a .app folder or an installable ipa file that will be installed. ios pcap [options] [--pid=] [--process=] Starts a pcap dump of network traffic, use --pid or --process to filter specific processes. ios apps [--system] [--all] [--list] [--filesharing] Retrieves a list of installed applications. --system prints out preinstalled system apps. --all prints all apps, including system, user, and hidden apps. --list only prints bundle ID, bundle name and version number. --filesharing only prints apps which enable documents sharing. @@ -796,6 +798,11 @@ The commands work as following: } } + b, _ = arguments.Bool("sysmontap") + if b { + printSysmontapStats(device) + } + b, _ = arguments.Bool("kill") if b { var response []installationproxy.AppInfo @@ -1082,6 +1089,42 @@ The commands work as following: } } +func printSysmontapStats(device ios.DeviceEntry) { + deviceInfoService, err := instruments.NewDeviceInfoService(device) + if err != nil { + exitIfError("NewDeviceInfoService creation error", err) + } + defer deviceInfoService.Close() + + sysAttrs, err := deviceInfoService.SystemAttributes() + if err != nil { + exitIfError("Reading SystemAttributes error", err) + } + + procAttrs, err := deviceInfoService.ProcessAttributes() + if err != nil { + exitIfError("Reading ProcessAttributes error", err) + } + + sysmontapService, err := instruments.NewSysmontapService(device) + if err != nil { + exitIfError("NewSysmontapService creation error", err) + } + defer sysmontapService.Close() + + err = sysmontapService.SetConfig(procAttrs, sysAttrs) + if err != nil { + exitIfError("SetConfig call error", err) + } + + sysmontapMsg, err := sysmontapService.Start() + if err != nil { + exitIfError("Start call error", err) + } + + log.WithFields(log.Fields{"cpuUsage": sysmontapMsg}).Info("total cpu usage on the device") +} + func mobileGestaltCommand(device ios.DeviceEntry, arguments docopt.Opts) bool { b, _ := arguments.Bool("mobilegestalt") if b { From 85675376b0d290277e241273db1f59f2e145d6d9 Mon Sep 17 00:00:00 2001 From: fish-sauce Date: Wed, 23 Oct 2024 09:50:58 -0500 Subject: [PATCH 03/10] Add doc --- ios/diagnostics/request.go | 1 + ios/dtx_codec/channel.go | 1 + ios/instruments/instruments_sysmontap.go | 2 ++ 3 files changed, 4 insertions(+) diff --git a/ios/diagnostics/request.go b/ios/diagnostics/request.go index a834b4cb..708607ef 100644 --- a/ios/diagnostics/request.go +++ b/ios/diagnostics/request.go @@ -37,6 +37,7 @@ type Diagnostics struct { IORegistry IORegistry } +// IORegistry relates to the battery stats type IORegistry struct { InstantAmperage int Temperature int diff --git a/ios/dtx_codec/channel.go b/ios/dtx_codec/channel.go index 10a98624..512429b1 100644 --- a/ios/dtx_codec/channel.go +++ b/ios/dtx_codec/channel.go @@ -132,6 +132,7 @@ func (d *Channel) SendAndAwaitReply(expectsReply bool, messageType MessageType, } } +// Receive receives a message from a global channel (0) func (d *Channel) Receive() (Message, error) { select { case response := <-d.messageReceiver.msgChannel: diff --git a/ios/instruments/instruments_sysmontap.go b/ios/instruments/instruments_sysmontap.go index 0ead6e01..35ab83f0 100644 --- a/ios/instruments/instruments_sysmontap.go +++ b/ios/instruments/instruments_sysmontap.go @@ -15,6 +15,7 @@ type SysmontapService struct { plistCodec ios.PlistCodec } +// Creates a new SysmontapService func NewSysmontapService(device ios.DeviceEntry) (*SysmontapService, error) { dtxConn, err := connectInstruments(device) if err != nil { @@ -53,6 +54,7 @@ func (s *SysmontapService) Start() (SysmontapMessage, error) { return sysmontapMessage, nil } +// SetConfig sets configuration to allow the sysmontap service getting desired data points func (s *SysmontapService) SetConfig(procAttrs, sysAttrs []interface{}) error { config := map[string]interface{}{ "ur": 500, From dfbcce090e7aa82356758a54dbf35b1385aebfdf Mon Sep 17 00:00:00 2001 From: fish-sauce Date: Fri, 1 Nov 2024 09:51:29 -0500 Subject: [PATCH 04/10] fix: safe type assertions --- ios/instruments/instruments_sysmontap.go | 40 +++++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/ios/instruments/instruments_sysmontap.go b/ios/instruments/instruments_sysmontap.go index 35ab83f0..6b061744 100644 --- a/ios/instruments/instruments_sysmontap.go +++ b/ios/instruments/instruments_sysmontap.go @@ -93,14 +93,38 @@ func mapToCPUUsage(msg dtx.Message) (SysmontapMessage, error) { return SysmontapMessage{}, fmt.Errorf("payload of message should have only one element: %+v", msg) } - resultArray := payload[0].([]interface{}) - resultMap := resultArray[0].(map[string]interface{}) - cpuCount := resultMap["CPUCount"].(uint64) - enabledCPUs := resultMap["EnabledCPUs"].(uint64) - endMachAbsTime := resultMap["EndMachAbsTime"].(uint64) - typ := resultMap["Type"].(uint64) - sysmontapMessageMap := resultMap["SystemCPUUsage"].(map[string]interface{}) - cpuTotalLoad := sysmontapMessageMap["CPU_TotalLoad"].(float64) + resultArray, ok := payload[0].([]interface{}) + if !ok { + return SysmontapMessage{}, fmt.Errorf("expected resultArray of type []interface{}: %+v", payload[0]) + } + resultMap, ok := resultArray[0].(map[string]interface{}) + if !ok { + return SysmontapMessage{}, fmt.Errorf("expected resultMap of type map[string]interface{} as a single element of resultArray: %+v", resultArray[0]) + } + cpuCount, ok := resultMap["CPUCount"].(uint64) + if !ok { + return SysmontapMessage{}, fmt.Errorf("expected CPUCount of type uint64 of resultMap: %+v", resultMap) + } + enabledCPUs, ok := resultMap["EnabledCPUs"].(uint64) + if !ok { + return SysmontapMessage{}, fmt.Errorf("expected EnabledCPUs of type uint64 of resultMap: %+v", resultMap) + } + endMachAbsTime, ok := resultMap["EndMachAbsTime"].(uint64) + if !ok { + return SysmontapMessage{}, fmt.Errorf("expected EndMachAbsTime of type uint64 of resultMap: %+v", resultMap) + } + typ, ok := resultMap["Type"].(uint64) + if !ok { + return SysmontapMessage{}, fmt.Errorf("expected Type of type uint64 of resultMap: %+v", resultMap) + } + sysmontapMessageMap, ok := resultMap["SystemCPUUsage"].(map[string]interface{}) + if !ok { + return SysmontapMessage{}, fmt.Errorf("expected SystemCPUUsage of type map[string]interface{} of resultMap: %+v", resultMap) + } + cpuTotalLoad, ok := sysmontapMessageMap["CPU_TotalLoad"].(float64) + if !ok { + return SysmontapMessage{}, fmt.Errorf("expected CPU_TotalLoad of type uint64 of sysmontapMessageMap: %+v", sysmontapMessageMap) + } cpuUsage := CPUUsage{CPU_TotalLoad: cpuTotalLoad} sysmontapMessage := SysmontapMessage{ From 58ba2d7fab87fd65cbce2c5b30201a965d68971a Mon Sep 17 00:00:00 2001 From: fish-sauce Date: Fri, 1 Nov 2024 10:29:58 -0500 Subject: [PATCH 05/10] fix: add system monitor wrapper --- ios/instruments/instruments_deviceinfo.go | 8 ++-- ios/instruments/instruments_sysmontap.go | 26 ++++++------ ios/instruments/system_monitor.go | 52 +++++++++++++++++++++++ main.go | 33 +++----------- 4 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 ios/instruments/system_monitor.go diff --git a/ios/instruments/instruments_deviceinfo.go b/ios/instruments/instruments_deviceinfo.go index a6db4611..9f8c45a5 100644 --- a/ios/instruments/instruments_deviceinfo.go +++ b/ios/instruments/instruments_deviceinfo.go @@ -20,8 +20,8 @@ type ProcessInfo struct { StartDate time.Time } -// ProcessAttributes returns the attributes list which can be used for monitoring -func (d DeviceInfoService) ProcessAttributes() ([]interface{}, error) { +// processAttributes returns the attributes list which can be used for monitoring +func (d DeviceInfoService) processAttributes() ([]interface{}, error) { resp, err := d.channel.MethodCall("sysmonProcessAttributes") if err != nil { return nil, err @@ -29,8 +29,8 @@ func (d DeviceInfoService) ProcessAttributes() ([]interface{}, error) { return resp.Payload[0].([]interface{}), nil } -// SystemAttributes returns the attributes list which can be used for monitoring -func (d DeviceInfoService) SystemAttributes() ([]interface{}, error) { +// systemAttributes returns the attributes list which can be used for monitoring +func (d DeviceInfoService) systemAttributes() ([]interface{}, error) { resp, err := d.channel.MethodCall("sysmonSystemAttributes") if err != nil { return nil, err diff --git a/ios/instruments/instruments_sysmontap.go b/ios/instruments/instruments_sysmontap.go index 6b061744..5dd37c1c 100644 --- a/ios/instruments/instruments_sysmontap.go +++ b/ios/instruments/instruments_sysmontap.go @@ -9,30 +9,30 @@ import ( const sysmontapName = "com.apple.instruments.server.services.sysmontap" -type SysmontapService struct { - channel *dtx.Channel - conn *dtx.Connection - plistCodec ios.PlistCodec +type sysmontapService struct { + channel *dtx.Channel + conn *dtx.Connection } -// Creates a new SysmontapService -func NewSysmontapService(device ios.DeviceEntry) (*SysmontapService, error) { +// Creates a new sysmontapService +func newSysmontapService(device ios.DeviceEntry) (*sysmontapService, error) { dtxConn, err := connectInstruments(device) if err != nil { return nil, err } processControlChannel := dtxConn.RequestChannelIdentifier(sysmontapName, loggingDispatcher{dtxConn}) - return &SysmontapService{channel: processControlChannel, conn: dtxConn, plistCodec: ios.NewPlistCodec()}, nil + return &sysmontapService{channel: processControlChannel, conn: dtxConn}, nil } // Close closes up the DTX connection -func (s *SysmontapService) Close() { - s.conn.Close() +func (s *sysmontapService) Close() error { + return s.conn.Close() } -// Start sends a start method call async and waits until the cpu info & stats come back -func (s *SysmontapService) Start() (SysmontapMessage, error) { +// start sends a start method call async and waits until the cpu info & stats come back +// the method is a part of the @protocol DTTapAuthorizedAPI +func (s *sysmontapService) start() (SysmontapMessage, error) { err := s.channel.MethodCallAsync("start") if err != nil { return SysmontapMessage{}, err @@ -54,8 +54,8 @@ func (s *SysmontapService) Start() (SysmontapMessage, error) { return sysmontapMessage, nil } -// SetConfig sets configuration to allow the sysmontap service getting desired data points -func (s *SysmontapService) SetConfig(procAttrs, sysAttrs []interface{}) error { +// setConfig sets configuration to allow the sysmontap service getting desired data points +func (s *sysmontapService) setConfig(procAttrs, sysAttrs []interface{}) error { config := map[string]interface{}{ "ur": 500, "bm": 0, diff --git a/ios/instruments/system_monitor.go b/ios/instruments/system_monitor.go new file mode 100644 index 00000000..48e5ef85 --- /dev/null +++ b/ios/instruments/system_monitor.go @@ -0,0 +1,52 @@ +package instruments + +import "github.com/danielpaulus/go-ios/ios" + +type systemMonitor struct { + deviceInfoService *DeviceInfoService + sysmontapService *sysmontapService +} + +// NewSystemMonitor creates a new instance of systemMonitor +func NewSystemMonitor(device ios.DeviceEntry) (*systemMonitor, error) { + deviceInfoService, err := NewDeviceInfoService(device) + if err != nil { + return nil, err + } + sysmontapService, err := newSysmontapService(device) + if err != nil { + return nil, err + } + + return &systemMonitor{deviceInfoService, sysmontapService}, nil +} + +func (s *systemMonitor) Close() error { + s.deviceInfoService.Close() + return s.sysmontapService.Close() +} + +// GetCPUUsage send a request to get CPU usage data and waits until the data back +func (s *systemMonitor) GetCPUUsage() (SysmontapMessage, error) { + sysAttrs, err := s.deviceInfoService.systemAttributes() + if err != nil { + return SysmontapMessage{}, err + } + + procAttrs, err := s.deviceInfoService.processAttributes() + if err != nil { + return SysmontapMessage{}, err + } + + err = s.sysmontapService.setConfig(procAttrs, sysAttrs) + if err != nil { + return SysmontapMessage{}, err + } + + sysmontapMsg, err := s.sysmontapService.start() + if err != nil { + return SysmontapMessage{}, err + } + + return sysmontapMsg, nil +} diff --git a/main.go b/main.go index 5253d4be..1cabde3e 100644 --- a/main.go +++ b/main.go @@ -1090,39 +1090,18 @@ The commands work as following: } func printSysmontapStats(device ios.DeviceEntry) { - deviceInfoService, err := instruments.NewDeviceInfoService(device) + sysmon, err := instruments.NewSystemMonitor(device) if err != nil { - exitIfError("NewDeviceInfoService creation error", err) + exitIfError("SystemMonitor creation error", err) } - defer deviceInfoService.Close() + defer sysmon.Close() - sysAttrs, err := deviceInfoService.SystemAttributes() + cpuUsage, err := sysmon.GetCPUUsage() if err != nil { - exitIfError("Reading SystemAttributes error", err) + exitIfError("GetCPUUsage error", err) } - procAttrs, err := deviceInfoService.ProcessAttributes() - if err != nil { - exitIfError("Reading ProcessAttributes error", err) - } - - sysmontapService, err := instruments.NewSysmontapService(device) - if err != nil { - exitIfError("NewSysmontapService creation error", err) - } - defer sysmontapService.Close() - - err = sysmontapService.SetConfig(procAttrs, sysAttrs) - if err != nil { - exitIfError("SetConfig call error", err) - } - - sysmontapMsg, err := sysmontapService.Start() - if err != nil { - exitIfError("Start call error", err) - } - - log.WithFields(log.Fields{"cpuUsage": sysmontapMsg}).Info("total cpu usage on the device") + log.WithFields(log.Fields{"cpuUsage": cpuUsage}).Info("total cpu usage on the device") } func mobileGestaltCommand(device ios.DeviceEntry, arguments docopt.Opts) bool { From 9b48bb46582db769ebb7000352b0808f07e3e9d3 Mon Sep 17 00:00:00 2001 From: fish-sauce Date: Mon, 4 Nov 2024 05:58:09 -0600 Subject: [PATCH 06/10] fix: simplify to just channel --- ios/dtx_codec/channel.go | 14 +++----------- ios/dtx_codec/connection.go | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/ios/dtx_codec/channel.go b/ios/dtx_codec/channel.go index 512429b1..5286b60d 100644 --- a/ios/dtx_codec/channel.go +++ b/ios/dtx_codec/channel.go @@ -9,21 +9,13 @@ import ( log "github.com/sirupsen/logrus" ) -type messageReceiver struct { - msgChannel chan Message -} - -func newMessageReceiver() *messageReceiver { - return &messageReceiver{msgChannel: make(chan Message)} -} - type Channel struct { channelCode int channelName string messageIdentifier int connection *Connection messageDispatcher Dispatcher - messageReceiver *messageReceiver + messageReceiver chan Message responseWaiters map[int]chan Message defragmenters map[int]*FragmentDecoder registeredMethods map[string]chan Message @@ -135,7 +127,7 @@ func (d *Channel) SendAndAwaitReply(expectsReply bool, messageType MessageType, // Receive receives a message from a global channel (0) func (d *Channel) Receive() (Message, error) { select { - case response := <-d.messageReceiver.msgChannel: + case response := <-d.messageReceiver: return response, nil case <-time.After(30 * time.Second): return Message{}, fmt.Errorf("exceeded waiting time message:%d channel:%d", d.messageIdentifier, d.channelCode) @@ -157,7 +149,7 @@ func (d *Channel) Dispatch(msg Message) { } if msg.PayloadHeader.MessageType == UnknownTypeOne { d.mutex.Unlock() - d.messageReceiver.msgChannel <- msg + d.messageReceiver <- msg return } diff --git a/ios/dtx_codec/connection.go b/ios/dtx_codec/connection.go index be4b3894..c521e378 100644 --- a/ios/dtx_codec/connection.go +++ b/ios/dtx_codec/connection.go @@ -149,7 +149,7 @@ func newDtxConnection(conn ios.DeviceConnectionInterface) (*Connection, error) { channelCode: 0, messageIdentifier: 5, channelName: "global_channel", connection: dtxConnection, messageDispatcher: NewGlobalDispatcher(requestChannelMessages, dtxConnection), - messageReceiver: newMessageReceiver(), + messageReceiver: make(chan Message), responseWaiters: map[int]chan Message{}, registeredMethods: map[string]chan Message{}, defragmenters: map[int]*FragmentDecoder{}, From 83b2403f859aa677893399599967f90edfed30ba Mon Sep 17 00:00:00 2001 From: fish-sauce Date: Mon, 4 Nov 2024 09:55:42 -0600 Subject: [PATCH 07/10] Dispatch to dedicated Dispatcher in Connection --- ios/dtx_codec/channel.go | 17 --------- ios/dtx_codec/connection.go | 25 +++++++++++- ios/instruments/helper.go | 12 ++++++ ios/instruments/instruments_sysmontap.go | 48 ++++++++++++++++-------- 4 files changed, 69 insertions(+), 33 deletions(-) diff --git a/ios/dtx_codec/channel.go b/ios/dtx_codec/channel.go index 5286b60d..700b7973 100644 --- a/ios/dtx_codec/channel.go +++ b/ios/dtx_codec/channel.go @@ -15,7 +15,6 @@ type Channel struct { messageIdentifier int connection *Connection messageDispatcher Dispatcher - messageReceiver chan Message responseWaiters map[int]chan Message defragmenters map[int]*FragmentDecoder registeredMethods map[string]chan Message @@ -124,16 +123,6 @@ func (d *Channel) SendAndAwaitReply(expectsReply bool, messageType MessageType, } } -// Receive receives a message from a global channel (0) -func (d *Channel) Receive() (Message, error) { - select { - case response := <-d.messageReceiver: - return response, nil - case <-time.After(30 * time.Second): - return Message{}, fmt.Errorf("exceeded waiting time message:%d channel:%d", d.messageIdentifier, d.channelCode) - } -} - func (d *Channel) Dispatch(msg Message) { d.mutex.Lock() if msg.Identifier >= d.messageIdentifier { @@ -147,12 +136,6 @@ func (d *Channel) Dispatch(msg Message) { return } } - if msg.PayloadHeader.MessageType == UnknownTypeOne { - d.mutex.Unlock() - d.messageReceiver <- msg - - return - } d.mutex.Unlock() if msg.ConversationIndex > 0 || msg.IsFragment() { d.mutex.Lock() diff --git a/ios/dtx_codec/connection.go b/ios/dtx_codec/connection.go index c521e378..28d9ef64 100644 --- a/ios/dtx_codec/connection.go +++ b/ios/dtx_codec/connection.go @@ -5,6 +5,7 @@ import ( "errors" "io" "math" + "reflect" "strings" "sync" "time" @@ -30,6 +31,14 @@ type Connection struct { mutex sync.Mutex requestChannelMessages chan Message + // MessageDispatcher use this prop to catch messages from GlobalDispatcher + // and handle it accordingly in a custom dispatcher of the dedicated service + // + // Set this prop when creating a connection instance + // + // Refer to end-to-end example of `instruments/instruments_sysmontap.go` + MessageDispatcher Dispatcher + closed chan struct{} err error closeOnce sync.Once @@ -87,6 +96,18 @@ func NewGlobalDispatcher(requestChannelMessages chan Message, dtxConnection *Con return dispatcher } +// Dispatch to a MessageDispatcher of the Connection if set +func (dtxConn *Connection) Dispatch(msg Message) { + msgDispatcher := dtxConn.MessageDispatcher + if msgDispatcher != nil { + log.Debugf("msg dispatcher found: %v", reflect.TypeOf(msgDispatcher)) + msgDispatcher.Dispatch(msg) + return + } + + log.Errorf("no connection dispatcher registered for global channel, msg: %v", msg) +} + // Dispatch prints log messages and errors when they are received and also creates local Channels when requested by the device. func (g GlobalDispatcher) Dispatch(msg Message) { SendAckIfNeeded(g.dtxConnection, msg) @@ -94,6 +115,9 @@ func (g GlobalDispatcher) Dispatch(msg Message) { if requestChannel == msg.Payload[0] { g.requestChannelMessages <- msg } + if msg.PayloadHeader.MessageType == UnknownTypeOne { + g.dtxConnection.Dispatch(msg) + } // TODO: use the dispatchFunctions map if "outputReceived:fromProcess:atTime:" == msg.Payload[0] { logmsg, err := nskeyedarchiver.Unarchive(msg.Auxiliary.GetArguments()[0].([]byte)) @@ -149,7 +173,6 @@ func newDtxConnection(conn ios.DeviceConnectionInterface) (*Connection, error) { channelCode: 0, messageIdentifier: 5, channelName: "global_channel", connection: dtxConnection, messageDispatcher: NewGlobalDispatcher(requestChannelMessages, dtxConnection), - messageReceiver: make(chan Message), responseWaiters: map[int]chan Message{}, registeredMethods: map[string]chan Message{}, defragmenters: map[int]*FragmentDecoder{}, diff --git a/ios/instruments/helper.go b/ios/instruments/helper.go index 42d19d1b..fcece086 100644 --- a/ios/instruments/helper.go +++ b/ios/instruments/helper.go @@ -2,6 +2,7 @@ package instruments import ( "fmt" + "reflect" "github.com/danielpaulus/go-ios/ios" dtx "github.com/danielpaulus/go-ios/ios/dtx_codec" @@ -24,6 +25,17 @@ func (p loggingDispatcher) Dispatch(m dtx.Message) { log.Debug(m) } +func connectInstrumentsWithMsgDispatcher(device ios.DeviceEntry, dispatcher dtx.Dispatcher) (*dtx.Connection, error) { + dtxConn, err := connectInstruments(device) + if err != nil { + return nil, err + } + dtxConn.MessageDispatcher = dispatcher + log.Debugf("msg dispatcher: %v attached to instruments connection", reflect.TypeOf(dispatcher)) + + return dtxConn, nil +} + func connectInstruments(device ios.DeviceEntry) (*dtx.Connection, error) { if device.SupportsRsd() { log.Debugf("Connecting to %s", serviceNameRsd) diff --git a/ios/instruments/instruments_sysmontap.go b/ios/instruments/instruments_sysmontap.go index 5dd37c1c..c1628a6a 100644 --- a/ios/instruments/instruments_sysmontap.go +++ b/ios/instruments/instruments_sysmontap.go @@ -2,27 +2,44 @@ package instruments import ( "fmt" + "time" "github.com/danielpaulus/go-ios/ios" dtx "github.com/danielpaulus/go-ios/ios/dtx_codec" + log "github.com/sirupsen/logrus" ) +type sysmontapMsgDispatcher struct { + channel chan dtx.Message +} + +func newSysmontapMsgDispatcher() *sysmontapMsgDispatcher { + return &sysmontapMsgDispatcher{make(chan dtx.Message)} +} + +func (p *sysmontapMsgDispatcher) Dispatch(m dtx.Message) { + p.channel <- m +} + const sysmontapName = "com.apple.instruments.server.services.sysmontap" type sysmontapService struct { - channel *dtx.Channel - conn *dtx.Connection + channel *dtx.Channel + conn *dtx.Connection + msgDispatcher *sysmontapMsgDispatcher } // Creates a new sysmontapService func newSysmontapService(device ios.DeviceEntry) (*sysmontapService, error) { - dtxConn, err := connectInstruments(device) + msgDispatcher := newSysmontapMsgDispatcher() + dtxConn, err := connectInstrumentsWithMsgDispatcher(device, msgDispatcher) if err != nil { return nil, err } + processControlChannel := dtxConn.RequestChannelIdentifier(sysmontapName, loggingDispatcher{dtxConn}) - return &sysmontapService{channel: processControlChannel, conn: dtxConn}, nil + return &sysmontapService{channel: processControlChannel, conn: dtxConn, msgDispatcher: msgDispatcher}, nil } // Close closes up the DTX connection @@ -38,20 +55,21 @@ func (s *sysmontapService) start() (SysmontapMessage, error) { return SysmontapMessage{}, err } - globalChannel := s.conn.GlobalChannel() + for { + select { + case msg := <-s.msgDispatcher.channel: + sysmontapMessage, err := mapToCPUUsage(msg) + if err != nil { + log.Debug(fmt.Sprintf("expected `sysmontapMessage` from global channel, but was %v", msg)) + continue + } - // Receive() will block until the message with the CPU usage is delivered - msg, err := globalChannel.Receive() - if err != nil { - return SysmontapMessage{}, err - } + return sysmontapMessage, nil - sysmontapMessage, err := mapToCPUUsage(msg) - if err != nil { - return SysmontapMessage{}, err + case <-time.After(30 * time.Second): + return SysmontapMessage{}, fmt.Errorf("exceeded waiting time message") + } } - - return sysmontapMessage, nil } // setConfig sets configuration to allow the sysmontap service getting desired data points From a7e61d21a36d65fdf4a71a67b62da06ab69f8884 Mon Sep 17 00:00:00 2001 From: fish-sauce Date: Tue, 5 Nov 2024 05:55:21 -0600 Subject: [PATCH 08/10] fix: close channel --- ios/dtx_codec/connection.go | 9 ++++----- ios/instruments/instruments_sysmontap.go | 11 ++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/ios/dtx_codec/connection.go b/ios/dtx_codec/connection.go index 28d9ef64..e883f755 100644 --- a/ios/dtx_codec/connection.go +++ b/ios/dtx_codec/connection.go @@ -5,7 +5,6 @@ import ( "errors" "io" "math" - "reflect" "strings" "sync" "time" @@ -100,7 +99,7 @@ func NewGlobalDispatcher(requestChannelMessages chan Message, dtxConnection *Con func (dtxConn *Connection) Dispatch(msg Message) { msgDispatcher := dtxConn.MessageDispatcher if msgDispatcher != nil { - log.Debugf("msg dispatcher found: %v", reflect.TypeOf(msgDispatcher)) + log.Debugf("msg dispatcher found: %T", msgDispatcher) msgDispatcher.Dispatch(msg) return } @@ -115,9 +114,6 @@ func (g GlobalDispatcher) Dispatch(msg Message) { if requestChannel == msg.Payload[0] { g.requestChannelMessages <- msg } - if msg.PayloadHeader.MessageType == UnknownTypeOne { - g.dtxConnection.Dispatch(msg) - } // TODO: use the dispatchFunctions map if "outputReceived:fromProcess:atTime:" == msg.Payload[0] { logmsg, err := nskeyedarchiver.Unarchive(msg.Auxiliary.GetArguments()[0].([]byte)) @@ -135,6 +131,9 @@ func (g GlobalDispatcher) Dispatch(msg Message) { if msg.HasError() { log.Error(msg.Payload[0]) } + if msg.PayloadHeader.MessageType == UnknownTypeOne { + g.dtxConnection.Dispatch(msg) + } } func notifyOfPublishedCapabilities(msg Message) { diff --git a/ios/instruments/instruments_sysmontap.go b/ios/instruments/instruments_sysmontap.go index c1628a6a..d48167d8 100644 --- a/ios/instruments/instruments_sysmontap.go +++ b/ios/instruments/instruments_sysmontap.go @@ -2,7 +2,6 @@ package instruments import ( "fmt" - "time" "github.com/danielpaulus/go-ios/ios" dtx "github.com/danielpaulus/go-ios/ios/dtx_codec" @@ -10,7 +9,7 @@ import ( ) type sysmontapMsgDispatcher struct { - channel chan dtx.Message + messages chan dtx.Message } func newSysmontapMsgDispatcher() *sysmontapMsgDispatcher { @@ -18,7 +17,7 @@ func newSysmontapMsgDispatcher() *sysmontapMsgDispatcher { } func (p *sysmontapMsgDispatcher) Dispatch(m dtx.Message) { - p.channel <- m + p.messages <- m } const sysmontapName = "com.apple.instruments.server.services.sysmontap" @@ -44,6 +43,7 @@ func newSysmontapService(device ios.DeviceEntry) (*sysmontapService, error) { // Close closes up the DTX connection func (s *sysmontapService) Close() error { + close(s.msgDispatcher.messages) return s.conn.Close() } @@ -57,7 +57,7 @@ func (s *sysmontapService) start() (SysmontapMessage, error) { for { select { - case msg := <-s.msgDispatcher.channel: + case msg := <-s.msgDispatcher.messages: sysmontapMessage, err := mapToCPUUsage(msg) if err != nil { log.Debug(fmt.Sprintf("expected `sysmontapMessage` from global channel, but was %v", msg)) @@ -65,9 +65,6 @@ func (s *sysmontapService) start() (SysmontapMessage, error) { } return sysmontapMessage, nil - - case <-time.After(30 * time.Second): - return SysmontapMessage{}, fmt.Errorf("exceeded waiting time message") } } } From d8b2609c082d7f2232e2fd7149ddfa9e26fe0415 Mon Sep 17 00:00:00 2001 From: fish-sauce Date: Tue, 5 Nov 2024 06:49:38 -0600 Subject: [PATCH 09/10] fix: deliver CPU sample continuosly --- ios/instruments/instruments_sysmontap.go | 94 ++++++++++++++---------- ios/instruments/system_monitor.go | 52 ------------- main.go | 34 +++++++-- 3 files changed, 84 insertions(+), 96 deletions(-) delete mode 100644 ios/instruments/system_monitor.go diff --git a/ios/instruments/instruments_sysmontap.go b/ios/instruments/instruments_sysmontap.go index d48167d8..cbb58f36 100644 --- a/ios/instruments/instruments_sysmontap.go +++ b/ios/instruments/instruments_sysmontap.go @@ -23,13 +23,20 @@ func (p *sysmontapMsgDispatcher) Dispatch(m dtx.Message) { const sysmontapName = "com.apple.instruments.server.services.sysmontap" type sysmontapService struct { - channel *dtx.Channel - conn *dtx.Connection - msgDispatcher *sysmontapMsgDispatcher + channel *dtx.Channel + conn *dtx.Connection + + deviceInfoService *DeviceInfoService + msgDispatcher *sysmontapMsgDispatcher } -// Creates a new sysmontapService -func newSysmontapService(device ios.DeviceEntry) (*sysmontapService, error) { +// NewSysmontapService creates a new sysmontapService +func NewSysmontapService(device ios.DeviceEntry) (*sysmontapService, error) { + deviceInfoService, err := NewDeviceInfoService(device) + if err != nil { + return nil, err + } + msgDispatcher := newSysmontapMsgDispatcher() dtxConn, err := connectInstrumentsWithMsgDispatcher(device, msgDispatcher) if err != nil { @@ -38,39 +45,16 @@ func newSysmontapService(device ios.DeviceEntry) (*sysmontapService, error) { processControlChannel := dtxConn.RequestChannelIdentifier(sysmontapName, loggingDispatcher{dtxConn}) - return &sysmontapService{channel: processControlChannel, conn: dtxConn, msgDispatcher: msgDispatcher}, nil -} - -// Close closes up the DTX connection -func (s *sysmontapService) Close() error { - close(s.msgDispatcher.messages) - return s.conn.Close() -} - -// start sends a start method call async and waits until the cpu info & stats come back -// the method is a part of the @protocol DTTapAuthorizedAPI -func (s *sysmontapService) start() (SysmontapMessage, error) { - err := s.channel.MethodCallAsync("start") + sysAttrs, err := deviceInfoService.systemAttributes() if err != nil { - return SysmontapMessage{}, err + return nil, err } - for { - select { - case msg := <-s.msgDispatcher.messages: - sysmontapMessage, err := mapToCPUUsage(msg) - if err != nil { - log.Debug(fmt.Sprintf("expected `sysmontapMessage` from global channel, but was %v", msg)) - continue - } - - return sysmontapMessage, nil - } + procAttrs, err := deviceInfoService.processAttributes() + if err != nil { + return nil, err } -} -// setConfig sets configuration to allow the sysmontap service getting desired data points -func (s *sysmontapService) setConfig(procAttrs, sysAttrs []interface{}) error { config := map[string]interface{}{ "ur": 500, "bm": 0, @@ -80,16 +64,52 @@ func (s *sysmontapService) setConfig(procAttrs, sysAttrs []interface{}) error { "physFootprint": true, "sampleInterval": 500000000, } + _, err = processControlChannel.MethodCall("setConfig:", config) + if err != nil { + return nil, err + } - _, err := s.channel.MethodCall("setConfig:", config) - + err = processControlChannel.MethodCallAsync("start") if err != nil { - return err + return nil, err } - return nil + return &sysmontapService{processControlChannel, dtxConn, deviceInfoService, msgDispatcher}, nil +} + +// Close closes up the DTX connection, message dispatcher and dtx.Message channel +func (s *sysmontapService) Close() error { + close(s.msgDispatcher.messages) + + s.deviceInfoService.Close() + return s.conn.Close() +} + +// ReceiveCPUUsage returns a chan of SysmontapMessage with CPU Usage info +// The method will close the result channel automatically as soon as sysmontapMsgDispatcher's +// dtx.Message channel is closed. +func (s *sysmontapService) ReceiveCPUUsage() chan SysmontapMessage { + messages := make(chan SysmontapMessage) + go func() { + defer close(messages) + + for msg := range s.msgDispatcher.messages { + sysmontapMessage, err := mapToCPUUsage(msg) + if err != nil { + log.Debugf("expected `sysmontapMessage` from global channel, but received %v", msg) + continue + } + + messages <- sysmontapMessage + } + + log.Infof("sysmontap message dispatcher channel closed") + }() + + return messages } +// SysmontapMessage is a wrapper struct for incoming CPU samples type SysmontapMessage struct { CPUCount uint64 EnabledCPUs uint64 diff --git a/ios/instruments/system_monitor.go b/ios/instruments/system_monitor.go deleted file mode 100644 index 48e5ef85..00000000 --- a/ios/instruments/system_monitor.go +++ /dev/null @@ -1,52 +0,0 @@ -package instruments - -import "github.com/danielpaulus/go-ios/ios" - -type systemMonitor struct { - deviceInfoService *DeviceInfoService - sysmontapService *sysmontapService -} - -// NewSystemMonitor creates a new instance of systemMonitor -func NewSystemMonitor(device ios.DeviceEntry) (*systemMonitor, error) { - deviceInfoService, err := NewDeviceInfoService(device) - if err != nil { - return nil, err - } - sysmontapService, err := newSysmontapService(device) - if err != nil { - return nil, err - } - - return &systemMonitor{deviceInfoService, sysmontapService}, nil -} - -func (s *systemMonitor) Close() error { - s.deviceInfoService.Close() - return s.sysmontapService.Close() -} - -// GetCPUUsage send a request to get CPU usage data and waits until the data back -func (s *systemMonitor) GetCPUUsage() (SysmontapMessage, error) { - sysAttrs, err := s.deviceInfoService.systemAttributes() - if err != nil { - return SysmontapMessage{}, err - } - - procAttrs, err := s.deviceInfoService.processAttributes() - if err != nil { - return SysmontapMessage{}, err - } - - err = s.sysmontapService.setConfig(procAttrs, sysAttrs) - if err != nil { - return SysmontapMessage{}, err - } - - sysmontapMsg, err := s.sysmontapService.start() - if err != nil { - return SysmontapMessage{}, err - } - - return sysmontapMsg, nil -} diff --git a/main.go b/main.go index 1cabde3e..25a3c78a 100644 --- a/main.go +++ b/main.go @@ -1090,18 +1090,38 @@ The commands work as following: } func printSysmontapStats(device ios.DeviceEntry) { - sysmon, err := instruments.NewSystemMonitor(device) + sysmon, err := instruments.NewSysmontapService(device) if err != nil { - exitIfError("SystemMonitor creation error", err) + exitIfError("systemMonitor creation error", err) } defer sysmon.Close() - cpuUsage, err := sysmon.GetCPUUsage() - if err != nil { - exitIfError("GetCPUUsage error", err) - } + cpuUsageChannel := sysmon.ReceiveCPUUsage() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + + log.Info("starting to monitor CPU usage... Press CTRL+C to stop.") - log.WithFields(log.Fields{"cpuUsage": cpuUsage}).Info("total cpu usage on the device") + for { + select { + case cpuUsageMsg, ok := <-cpuUsageChannel: + if !ok { + log.Info("CPU usage channel closed.") + return + } + log.WithFields(log.Fields{ + "cpu_count": cpuUsageMsg.CPUCount, + "enabled_cpus": cpuUsageMsg.EnabledCPUs, + "end_time": cpuUsageMsg.EndMachAbsTime, + "cpu_total_load": cpuUsageMsg.SystemCPUUsage.CPU_TotalLoad, + }).Info("received CPU usage data") + + case <-c: + log.Info("shutting down sysmontap") + return + } + } } func mobileGestaltCommand(device ios.DeviceEntry, arguments docopt.Opts) bool { From 65b391526a86deed3f755579d5ebddbe6932fd16 Mon Sep 17 00:00:00 2001 From: fish-sauce Date: Tue, 5 Nov 2024 08:04:06 -0600 Subject: [PATCH 10/10] Specify sampling rate --- ios/instruments/instruments_sysmontap.go | 8 ++++++-- main.go | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ios/instruments/instruments_sysmontap.go b/ios/instruments/instruments_sysmontap.go index cbb58f36..37c883da 100644 --- a/ios/instruments/instruments_sysmontap.go +++ b/ios/instruments/instruments_sysmontap.go @@ -31,7 +31,11 @@ type sysmontapService struct { } // NewSysmontapService creates a new sysmontapService -func NewSysmontapService(device ios.DeviceEntry) (*sysmontapService, error) { +// - samplingInterval is the rate how often to get samples, i.e Xcode's default is 10, which results in sampling output +// each 1 second, with 500 the samples are retrieved every 15 seconds. It doesn't make any correlation between +// the expected rate and the actual rate of samples delivery. We can only conclude, that the lower the rate in digits, +// the faster the samples are delivered +func NewSysmontapService(device ios.DeviceEntry, samplingInterval int) (*sysmontapService, error) { deviceInfoService, err := NewDeviceInfoService(device) if err != nil { return nil, err @@ -56,7 +60,7 @@ func NewSysmontapService(device ios.DeviceEntry) (*sysmontapService, error) { } config := map[string]interface{}{ - "ur": 500, + "ur": samplingInterval, "bm": 0, "procAttrs": procAttrs, "sysAttrs": sysAttrs, diff --git a/main.go b/main.go index 25a3c78a..91edc2e3 100644 --- a/main.go +++ b/main.go @@ -1090,7 +1090,8 @@ The commands work as following: } func printSysmontapStats(device ios.DeviceEntry) { - sysmon, err := instruments.NewSysmontapService(device) + const xcodeDefaultSamplingRate = 10 + sysmon, err := instruments.NewSysmontapService(device, xcodeDefaultSamplingRate) if err != nil { exitIfError("systemMonitor creation error", err) }