hidartraceutil   [plain text]


#!/usr/bin/python

import sys, getopt, collections, subprocess, argparse
from operator import itemgetter


class color:
   PURPLE = '\033[95m'
   CYAN = '\033[96m'
   DARKCYAN = '\033[36m'
   BLUE = '\033[94m'
   GREEN = '\033[92m'
   YELLOW = '\033[93m'
   RED = '\033[91m'
   BOLD = '\033[1m'
   UNDERLINE = '\033[4m'
   END = '\033[0m'

event_type_string = [
    "kIOHIDEventTypeNULL",
    "kIOHIDEventTypeVendorDefined",
    "kIOHIDEventTypeButton",
    "kIOHIDEventTypeKeyboard",
    "kIOHIDEventTypeTranslation",
    "kIOHIDEventTypeRotation",
    "kIOHIDEventTypeScroll",
    "kIOHIDEventTypeScale",
    "kIOHIDEventTypeZoom",
    "kIOHIDEventTypeVelocity",
    "kIOHIDEventTypeOrientation",
    "kIOHIDEventTypeDigitizer",
    "kIOHIDEventTypeAmbientLightSensor",
    "kIOHIDEventTypeAccelerometer",
    "kIOHIDEventTypeProximity",
    "kIOHIDEventTypeTemperature",
    "kIOHIDEventTypeNavigationSwipe",
    "kIOHIDEventTypePointer",
    "kIOHIDEventTypeProgress",
    "kIOHIDEventTypeMultiAxisPointer",
    "kIOHIDEventTypeGyro",
    "kIOHIDEventTypeCompass",
    "kIOHIDEventTypeZoomToggle",
    "kIOHIDEventTypeDockSwipe",
    "kIOHIDEventTypeSymbolicHotKey",
    "kIOHIDEventTypePower",
    "kIOHIDEventTypeLED",
    "kIOHIDEventTypeFluidTouchGesture",
    "kIOHIDEventTypeBoundaryScroll",
    "kIOHIDEventTypeBiometric",
    "kIOHIDEventTypeUnicode",
    "kIOHIDEventTypeAtmosphericPressure",
    "kIOHIDEventTypeForce",
    "kIOHIDEventTypeMotionActivity",
    "kIOHIDEventTypeMotionGesture",
    "kIOHIDEventTypeGameController",
    "kIOHIDEventTypeHumidity",
    "kIOHIDEventTypeCollection",
    "kIOHIDEventTypeBrightness",
    "kIOHIDEventTypeGenericGesture"]

# Debug code

kIOHIDDebugCode_DispatchHIDEvent    = 0x5230048
kIOHIDDebugCode_HandleReport   	  	= 0x523003C
kHIDDK_Dev_InputReport   			= 0x523C008

kHID_ES_Service_Callback			= 0x5238100
kHID_ES_Client_QueueCallback        = 0x5238080

kHID_ES_ClientsEnqueue              = 0x523804c

DBG_FUNC_END						= 0x2

EventTypeToStr = [
 	'kIOHIDEventTypeNULL',
    'kIOHIDEventTypeVendorDefined',
    'kIOHIDEventTypeButton',                  
    'kIOHIDEventTypeKeyboard',                     
    'kIOHIDEventTypeTranslation',
    'kIOHIDEventTypeRotation',
    'kIOHIDEventTypeScroll',
    'kIOHIDEventTypeScale',           
    'kIOHIDEventTypeZoom',                       
    'kIOHIDEventTypeVelocity',                     
    'kIOHIDEventTypeOrientation',
    'kIOHIDEventTypeDigitizer',
    'kIOHIDEventTypeAmbientLightSensor',
    'kIOHIDEventTypeAccelerometer',      
    'kIOHIDEventTypeProximity',                    
    'kIOHIDEventTypeTemperature',
    'kIOHIDEventTypeNavigationSwipe',
    'kIOHIDEventTypePointer',
    'kIOHIDEventTypeProgress',   
    'kIOHIDEventTypeMultiAxisPointer',      
    'kIOHIDEventTypeGyro',
    'kIOHIDEventTypeCompass',
    'kIOHIDEventTypeZoomToggle',
    'kIOHIDEventTypeDockSwipe',
    'kIOHIDEventTypeSymbolicHotKey',   
    'kIOHIDEventTypePower',                      
    'kIOHIDEventTypeLED',                          
    'kIOHIDEventTypeFluidTouchGesture',
    'kIOHIDEventTypeBoundaryScroll',
    'kIOHIDEventTypeBiometric',
    'kIOHIDEventTypeUnicode',            
    'kIOHIDEventTypeAtmosphericPressure',
    'kIOHIDEventTypeForce',
    'kIOHIDEventTypeMotionActivity',
    'kIOHIDEventTypeMotionGesture',
    'kIOHIDEventTypeGameController',        
    'kIOHIDEventTypeHumidity',                     
    'kIOHIDEventTypeCollection',
    'kIOHIDEventTypeBrightness',
    'kIOHIDEventTypeGenericGesture',
]


# event / report metadata
event_tarce = collections.OrderedDict()
dk_handle_report = collections.OrderedDict()
kernel_handle_report  = collections.OrderedDict()
kernel_dispatch_event = collections.OrderedDict()
verboseLog = False

filter_trace = []

def print_bold (s):
	print color.BOLD + s + color.END

def process_event (filter, line, trace_line):
	code = int (trace_line[2], 16)
	if (code >> 16) != 0x0523:
		return
	arg0 = int (trace_line[3], 16)
	if filter == arg0:
		filter_trace.append(line)
	if code == kHIDDK_Dev_InputReport and filter == int (trace_line[4], 16) :
		filter_trace.append(line)
		 


def  process_all (trace_line, clients):
	code = int (trace_line[2], 16)

	if (code >> 16) != 0x0523:
		return

	arg0 = int (trace_line[3], 16)
	ts   = float (trace_line[0])   

	if code == kHIDDK_Dev_InputReport:
		dk_report_timestamp = int (trace_line[4], 16)
		dk_handle_report[dk_report_timestamp] =  ts
		return 

	if code == kIOHIDDebugCode_HandleReport:
		kernel_report_timestamp = int (trace_line[4], 16)
		kernel_handle_report[kernel_report_timestamp] =  ts
		return

	if code == kIOHIDDebugCode_DispatchHIDEvent:
		kernel_dispatch_event[arg0] = ts
		return
	
	if code == kHID_ES_Service_Callback and arg0 not in event_tarce:
		service = int (trace_line[4], 16)
		event_type = int (trace_line[5], 16)
		event_tarce [arg0] = [event_type, service, ts, 0, 0, trace_line[0], 0, set()]
		return

	if code == (kHID_ES_ClientsEnqueue | DBG_FUNC_END) and arg0 in event_tarce:
		(event_tarce [arg0])[3] = ts
		return

	if code == kHID_ES_Client_QueueCallback and arg0 in event_tarce:
		client = trace_line[9]
		((event_tarce[arg0])[7]).add(client)
		if len(clients) > 0:
			if client not in clients:
				return	
		if (event_tarce[arg0])[4] < ts:
			(event_tarce [arg0])[4] = ts

def  print_summary ():
	eventTypeSummary = {}
	eventLatencies  = []
	for  key in event_tarce.keys():
		event = event_tarce[key]
		if event[4] == 0 or event[3] == 0:
			continue
		eventType =  event[0]

		if eventType in eventTypeSummary.keys():
			eventTypeSummary[eventType] += 1
		else:
			eventTypeSummary[eventType] = 1	  

		eventLatencies.append (event[3] - event[2])
	
	print "Summary"
	print "Events:"
	for  key in eventTypeSummary.keys():
		print "  type:%d(%s) count:%d" % (key, EventTypeToStr[key], eventTypeSummary[key])

	print "  total:%d" % (len(event_tarce))
	print "Latency:"
	eventLatencies.sort()
	minLatecy="" 
	for i in range (0,5):
		minLatecy += "%.1f " % (eventLatencies[i])
	print "  min:%s" % (minLatecy)
	maxLatecy="" 
	for i in range (len(eventLatencies) - 5, len(eventLatencies)):
		maxLatecy += "%.1f " % (eventLatencies[i])
	print "  max:%s" % (maxLatecy)
	latecyAvg=0 
	for i in range (0, (len(eventLatencies) * 95) / 100):
		latecyAvg += eventLatencies[i]
	latecyAvg = latecyAvg / ((len(eventLatencies) * 95) / 100)
	print "  average:%.1f " % (latecyAvg)

def  print_all ():
	for  key in event_tarce.keys():
		event = event_tarce[key]
		if event[4] == 0 or event[3] == 0:
			continue

		event_log_str =  "event:0x%x(%d) type:0x%02x service:0x%x thidd-total:%.1f thidd-to-client:%.1f thidd-event:%s" % (key, key, event[0], event[1],  event[3] - event[2],  event[4] - event[2],  event[5])

		treport = 0
		tbegin = float(event[5])
		tend = event[4]

		if  key in kernel_dispatch_event:
			event_time = kernel_dispatch_event[key]
			event_log_str = "%s tkern-event:%.1f" % (event_log_str, event_time)
			tbegin = event_time
		
		if  key in dk_handle_report:
			treport = dk_handle_report[key]
		elif key in kernel_handle_report:
			treport = kernel_handle_report[key]

		if  treport:
			event_log_str = "%s treport:%.1f" % (event_log_str, treport)
			tbegin = treport

		event_log_str = "%s ttotal:%.1f" % (event_log_str, tend - tbegin)

		print  event_log_str

def  print_trace (trace):
	for  line in trace:
		print  line.rstrip("\r\n")


def print_report ():
	eventLatency = []
	eventType = {}
	eventTypeForClients = {}
	for  key in event_tarce.keys():
		event = event_tarce[key]
		if event[4] == 0 or event[3] == 0:
			continue
		eventLatency.append (event[3] - event[2])
		clients = event[7]
		for client in clients:
			eventTypeForClient = {}
			if eventTypeForClients.get(client) == None:
				eventTypeForClients [client] = eventTypeForClient
			else:
				eventTypeForClient = eventTypeForClients[client]

			if eventTypeForClient.get(event[0]) == None:
				eventTypeForClient[event[0]] = 1
			else:
				eventTypeForClient[event[0]] += 1

		if event[0] in eventType.keys():
			eventType[event[0]] += 1
		else:
			eventType[event[0]] = 1
			
	print_bold("Events:")
	for key in eventType.keys():
		print "  type: %2d count: %8d (%s)" % (key, eventType[key], event_type_string[key]) 
	
	print_bold("Latencies:")
	
	eventLatency.sort();

	print_bold("  min:") 
	for i in range (0, 5):
		print  "    %0.1f" % (eventLatency[i])

	print_bold("  max:") 
	for i in range (len(eventLatency) - 5, len(eventLatency)):
		print  "    %0.1f" % (eventLatency[i])

	average = 0;

	print_bold("  average:") 

	for i in range (0,(len(eventLatency) * 95) / 100):
		average += eventLatency[i]
	print "    95%%: %0.1f" % (average / ((len(eventLatency) * 95) / 100))

	average = 0;
	for i in range (0,(len(eventLatency) * 50) / 100):
		average += eventLatency[i]
	print "    50%%: %0.1f" % (average / ((len(eventLatency) * 50) / 100))

	for client in eventTypeForClients.keys():
		print_bold("Events received by %s:" % client)
		eventType = eventTypeForClients[client]
		for key in eventType.keys():
			print "  type: %2d count: %8d (%s)" % (key, eventType[key], event_type_string[key]) 


def  main (argv):
	global verboseLog
	oevent = 0
	osummary = False
	oclients = []
	oreport = False
	opts, args = getopt.getopt(argv, "c:e:hvr", ["clients=", "event=", "help", "verbose", "report"])
	for opt, arg in opts:
		if opt in ("-e", "--event"):
			oevent = int (arg, 16) 
		if opt in ("-c", "--clients"):
			oclients = arg.split(",") 
		if opt in ("-v", "--verbose"):
			verboseLog = True
		if opt in ("-r", "--report"):
			oreport = True
		if opt in ("-h", "--help"):
			print "Extract HID events info from  artrace/tailspin/ktrace"
			print "Usage:"
			print "     hidartraceutil [-e event_timestamp] [-c client_process_name,...] [-h] [-r]\n"
			print "Flags:"
			print "  	-c,--clients  client process or string of comma separated client process names (-c  hidutil or -c  hidutil,hidEventSystemMonitor)"
			print "  	-r,--report   print summary report for event types count latencies"
			print "  	-e,--event    print print trace points for event with specific timestamp\n"
			print "Examples:"
			print "  	hidartraceutil /tmp/trace001.artrace"
			print "  	hidartraceutil -r /tmp/trace001.artrace"
			print "  	hidartraceutil -c hidutil /tmp/trace001.artrace"
			print "  	hidartraceutil -e 0x71d1755621c24 /tmp/trace001.artrace\n"
			print "Other:"
			print "		artrace  --trace-ktrace-class 5 (collect HID trace point used by hidartraceutil)"

			return

	trace_data = sys.stdin

	if args:
		if verboseLog:
			print "Loading trace: file:%s" % (args[0])

		if "tailspin" in args[0]:
			trace_data = subprocess.check_output("ktrace trace -A -N -R \"%s\"" % (args[0]), shell=True, universal_newlines=True).splitlines()		

		if "artrace" in args[0]:
			trace_data = subprocess.check_output("artrace_tool dumptrace --nocodefile \"%s\"" % (args[0]), shell=True, universal_newlines=True).splitlines()		


	if verboseLog:
		if trace_data == sys.stdin:
			length = 0
		else:
			length = len(trace_data)
		print "Process trace: length:%d event:%x clients:%s" % (length, oevent, oclients)

	for line in trace_data:
		trace_line = line.split()
		if not (trace_line and trace_line[0].replace('.','1').isdigit()):
			continue
		if oevent != 0:
			process_event (oevent, line, trace_line)
		else:
			process_all (trace_line, oclients)

	if oevent != 0:
		print "Filter trace for event 0x%x" % (oevent)
		print_trace (filter_trace)
	else:
		print "Events:"
		print_all ()

	if oreport:
		print_report ()


if __name__ == "__main__":
	main (sys.argv[1:])