119 lines
3.5 KiB
Python
119 lines
3.5 KiB
Python
|
|
import argparse
|
||
|
|
import statistics
|
||
|
|
import time
|
||
|
|
|
||
|
|
import erpc
|
||
|
|
|
||
|
|
from servo_service import client
|
||
|
|
|
||
|
|
|
||
|
|
DEFAULT_PORT = "COM8"
|
||
|
|
DEFAULT_BAUD = 115200
|
||
|
|
DEFAULT_ITERATIONS = 100
|
||
|
|
DEFAULT_JOINT_COUNT = 26
|
||
|
|
DEFAULT_WARMUP = 5
|
||
|
|
|
||
|
|
|
||
|
|
def positive_int(value):
|
||
|
|
parsed = int(value)
|
||
|
|
if parsed <= 0:
|
||
|
|
raise argparse.ArgumentTypeError("value must be > 0")
|
||
|
|
return parsed
|
||
|
|
|
||
|
|
|
||
|
|
def non_negative_float(value):
|
||
|
|
parsed = float(value)
|
||
|
|
if parsed < 0:
|
||
|
|
raise argparse.ArgumentTypeError("value must be >= 0")
|
||
|
|
return parsed
|
||
|
|
|
||
|
|
|
||
|
|
def percentile(values, ratio):
|
||
|
|
if not values:
|
||
|
|
raise ValueError("values must not be empty")
|
||
|
|
ordered = sorted(values)
|
||
|
|
index = int(ratio * (len(ordered) - 1))
|
||
|
|
return ordered[index]
|
||
|
|
|
||
|
|
|
||
|
|
def measure_movej_latency(api, angles, iterations, warmup):
|
||
|
|
for index in range(warmup):
|
||
|
|
result = api.moveJ(angles)
|
||
|
|
# if result != 0:
|
||
|
|
# raise RuntimeError(f"Warmup moveJ failed at iteration {index}: {result}")
|
||
|
|
|
||
|
|
costs_ms = []
|
||
|
|
for index in range(iterations):
|
||
|
|
t0 = time.perf_counter()
|
||
|
|
result = api.moveJ(angles)
|
||
|
|
elapsed_ms = (time.perf_counter() - t0) * 1000.0
|
||
|
|
# if result != 0:
|
||
|
|
# raise RuntimeError(f"moveJ failed at iteration {index}: {result}")
|
||
|
|
costs_ms.append(elapsed_ms)
|
||
|
|
return costs_ms
|
||
|
|
|
||
|
|
|
||
|
|
def print_summary(costs_ms):
|
||
|
|
print(f"count = {len(costs_ms)}")
|
||
|
|
print(f"avg = {statistics.mean(costs_ms):.3f} ms")
|
||
|
|
print(f"min = {min(costs_ms):.3f} ms")
|
||
|
|
print(f"max = {max(costs_ms):.3f} ms")
|
||
|
|
print(f"p50 = {statistics.median(costs_ms):.3f} ms")
|
||
|
|
print(f"p95 = {percentile(costs_ms, 0.95):.3f} ms")
|
||
|
|
|
||
|
|
|
||
|
|
def build_parser():
|
||
|
|
parser = argparse.ArgumentParser(description="Measure moveJ RPC latency.")
|
||
|
|
parser.add_argument("--port", default=DEFAULT_PORT, help=f"Serial port, default: {DEFAULT_PORT}")
|
||
|
|
parser.add_argument("--baud", type=positive_int, default=DEFAULT_BAUD, help=f"Serial baud, default: {DEFAULT_BAUD}")
|
||
|
|
parser.add_argument(
|
||
|
|
"--iterations",
|
||
|
|
type=positive_int,
|
||
|
|
default=DEFAULT_ITERATIONS,
|
||
|
|
help=f"Measured moveJ calls, default: {DEFAULT_ITERATIONS}",
|
||
|
|
)
|
||
|
|
parser.add_argument(
|
||
|
|
"--warmup",
|
||
|
|
type=positive_int,
|
||
|
|
default=DEFAULT_WARMUP,
|
||
|
|
help=f"Warmup moveJ calls before measuring, default: {DEFAULT_WARMUP}",
|
||
|
|
)
|
||
|
|
parser.add_argument(
|
||
|
|
"--joint-count",
|
||
|
|
type=positive_int,
|
||
|
|
default=DEFAULT_JOINT_COUNT,
|
||
|
|
help=f"Length of angles_rad list, default: {DEFAULT_JOINT_COUNT}",
|
||
|
|
)
|
||
|
|
parser.add_argument(
|
||
|
|
"--angle-rad",
|
||
|
|
type=non_negative_float,
|
||
|
|
default=0.0,
|
||
|
|
help="Angle value written into every joint slot, default: 0.0",
|
||
|
|
)
|
||
|
|
return parser
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
args = build_parser().parse_args()
|
||
|
|
angles = [args.angle_rad] * args.joint_count
|
||
|
|
|
||
|
|
transport = erpc.transport.SerialTransport(args.port, args.baud)
|
||
|
|
try:
|
||
|
|
client_mgr = erpc.client.ClientManager(transport, erpc.basic_codec.BasicCodec)
|
||
|
|
api = client.servo_serviceClient(client_mgr)
|
||
|
|
print(f"port = {args.port}")
|
||
|
|
print(f"baud = {args.baud}")
|
||
|
|
print(f"iterations = {args.iterations}")
|
||
|
|
print(f"warmup = {args.warmup}")
|
||
|
|
print(f"joint_count = {args.joint_count}")
|
||
|
|
print(f"angle_rad = {args.angle_rad}")
|
||
|
|
costs_ms = measure_movej_latency(api, angles, args.iterations, args.warmup)
|
||
|
|
print_summary(costs_ms)
|
||
|
|
finally:
|
||
|
|
if hasattr(transport, "close"):
|
||
|
|
transport.close()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|