rseq-slice-hist.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. #!/usr/bin/python3
  2. #
  3. # trace-cmd record -e hrtimer_start -e hrtimer_cancel -e hrtimer_expire_entry -- $cmd
  4. #
  5. from tracecmd import *
  6. def load_kallsyms(file_path='/proc/kallsyms'):
  7. """
  8. Parses /proc/kallsyms into a dictionary.
  9. Returns: { address_int: symbol_name }
  10. """
  11. kallsyms_map = {}
  12. try:
  13. with open(file_path, 'r') as f:
  14. for line in f:
  15. # The format is: [address] [type] [name] [module]
  16. parts = line.split()
  17. if len(parts) < 3:
  18. continue
  19. addr = int(parts[0], 16)
  20. name = parts[2]
  21. kallsyms_map[addr] = name
  22. except PermissionError:
  23. print(f"Error: Permission denied reading {file_path}. Try running with sudo.")
  24. except FileNotFoundError:
  25. print(f"Error: {file_path} not found.")
  26. return kallsyms_map
  27. ksyms = load_kallsyms()
  28. # pending[timer_ptr] = {'ts': timestamp, 'comm': comm}
  29. pending = {}
  30. # histograms[comm][bucket] = count
  31. histograms = {}
  32. class OnlineHarmonicMean:
  33. def __init__(self):
  34. self.n = 0 # Count of elements
  35. self.S = 0.0 # Cumulative sum of reciprocals
  36. def update(self, x):
  37. if x == 0:
  38. raise ValueError("Harmonic mean is undefined for zero.")
  39. self.n += 1
  40. self.S += 1.0 / x
  41. return self.n / self.S
  42. @property
  43. def mean(self):
  44. return self.n / self.S if self.n > 0 else 0
  45. ohms = {}
  46. def handle_start(record):
  47. func_name = ksyms[record.num_field("function")]
  48. if "rseq_slice_expired" in func_name:
  49. timer_ptr = record.num_field("hrtimer")
  50. pending[timer_ptr] = {
  51. 'ts': record.ts,
  52. 'comm': record.comm
  53. }
  54. return None
  55. def handle_cancel(record):
  56. timer_ptr = record.num_field("hrtimer")
  57. if timer_ptr in pending:
  58. start_data = pending.pop(timer_ptr)
  59. duration_ns = record.ts - start_data['ts']
  60. duration_us = duration_ns // 1000
  61. comm = start_data['comm']
  62. if comm not in ohms:
  63. ohms[comm] = OnlineHarmonicMean()
  64. ohms[comm].update(duration_ns)
  65. if comm not in histograms:
  66. histograms[comm] = {}
  67. histograms[comm][duration_us] = histograms[comm].get(duration_us, 0) + 1
  68. return None
  69. def handle_expire(record):
  70. timer_ptr = record.num_field("hrtimer")
  71. if timer_ptr in pending:
  72. start_data = pending.pop(timer_ptr)
  73. comm = start_data['comm']
  74. if comm not in histograms:
  75. histograms[comm] = {}
  76. # Record -1 bucket for expired (failed to cancel)
  77. histograms[comm][-1] = histograms[comm].get(-1, 0) + 1
  78. return None
  79. if __name__ == "__main__":
  80. t = Trace("trace.dat")
  81. for cpu in range(0, t.cpus):
  82. ev = t.read_event(cpu)
  83. while ev:
  84. if "hrtimer_start" in ev.name:
  85. handle_start(ev)
  86. if "hrtimer_cancel" in ev.name:
  87. handle_cancel(ev)
  88. if "hrtimer_expire_entry" in ev.name:
  89. handle_expire(ev)
  90. ev = t.read_event(cpu)
  91. print("\n" + "="*40)
  92. print("RSEQ SLICE HISTOGRAM (us)")
  93. print("="*40)
  94. for comm, buckets in histograms.items():
  95. print(f"\nTask: {comm} Mean: {ohms[comm].mean:.3f} ns")
  96. print(f" {'Latency (us)':<15} | {'Count'}")
  97. print(f" {'-'*30}")
  98. # Sort buckets numerically, putting -1 at the top
  99. for bucket in sorted(buckets.keys()):
  100. label = "EXPIRED" if bucket == -1 else f"{bucket} us"
  101. print(f" {label:<15} | {buckets[bucket]}")