1. 調整檔案結構 變動 import 的路徑與方法

2. 新增 mainOrchestrator.py 作為接下來開發介面化統合工具的主要檔案
chiyu
Chiyu Chen 6 months ago
parent 2c4fc4583e
commit 7af138b02a

@ -33,6 +33,15 @@ Package 簡述
2. fc_network_adapter
建立、維護與飛控韌體的連接
構築 mavlink 封包
同時處理與 Gazebo 的 ardupilot_plugin 溝通的 FDM/JSON 訊息
處理無線模組的通訊格式 (XBee)
--同時處理與 Gazebo 的 ardupilot_plugin 溝通的 FDM/JSON 訊息 (移除)--
N. logs 是執行時期的記錄檔
===
請一律在 ~/AirTrapMine/src/ 資料夾下 以模組化啟動程式
例如 在 ~/AirTrapMine/src/ 資料夾下
> python -m fc_network_adapter.fc_network_adapter.mainOrchestrator
> python -m fc_network_adapter.tests.test_ringBuffer

@ -0,0 +1,262 @@
'''
主要調配流程的程式
這個檔案包含 Terminal Utility Layer (TUL) 作為人機互動介面並調用 mavlinkDevice mavlinkObject 來處理 MAVLink 通訊和物件管理
'''
import os
import time
import sys
import curses
import threading
import queue
import signal
# 自定義的 import
from . import mavlinkObject as mo
from . import mavlinkDevice as md
from .utils import RingBuffer, setup_logger
logger = setup_logger(os.path.basename(__file__))
class MenuNode:
def __init__(self, name, desc="", action=None, children=None):
self.name = name
self.desc = desc
self.action = action # 可以是函式或特殊字串
self.children = children or [] # 子選單列表
class ControlPanel:
def __init__(self):
pass
def menu_tree(self):
"""建立多層選單結構"""
return MenuNode("Main Menu", children=[
MenuNode("MavLink Object Control", children=[
MenuNode("New+", "新增一個連結口", "ADD_MAV_OBJECT"),
MenuNode("Remove", "移除某個連結口", "REMOVE_MAV_OBJECT"),
MenuNode("ListAll", "顯示連結口列表", "LIST_MAV_OBJECT"),
MenuNode("Inspection", "顯示連結口資訊", "INSPECT_MAV_OBJECT"),
]),
MenuNode("參數設定", children=[
MenuNode("Speed", children=[
MenuNode("Increase Speed", "增加速度", "ADJUST_SPEED"),
MenuNode("Decrease Speed", "減少速度", "ADJUST_SPEED"),
]),
MenuNode("輸入文字", "鍵盤輸入模式", "INPUT_TEXT"),
]),
MenuNode("資訊查看", children=[
MenuNode("顯示狀態", "查看當前狀態", "SHOW_STATUS"),
MenuNode("顯示輸入", "查看用戶輸入", "SHOW_INPUT"),
]),
MenuNode("返回上層", "回到上一層選單", "BACK"),
MenuNode("關閉面板", "關閉控制面板", "QUIT"),
])
def panel_thread(self, cmd_q: queue.Queue, stop_evt: threading.Event):
stdscr = None
def cleanup():
"""清理 curses 狀態"""
if stdscr:
stdscr.keypad(False)
curses.nocbreak()
curses.echo()
curses.endwin()
def draw_menu(screen):
nonlocal stdscr
stdscr = screen
curses.curs_set(0)
stdscr.nodelay(False) # 阻塞讀鍵
stdscr.keypad(True)
# 選單導航狀態
menu_stack = [self.menu_tree()] # 選單堆疊
idx_stack = [0] # 索引堆疊
while not stop_evt.is_set():
# 檢查是否需要退出
if stop_evt.is_set():
break
current_menu = menu_stack[-1]
current_idx = idx_stack[-1]
stdscr.clear()
stdscr.border()
stdscr.addstr(0, 10, " MavLink MiddleWare ", curses.A_BOLD)
stdscr.addstr(1, 2, f"mavlink bridge state : ")
stdscr.addstr(2, 2, f"object manager state : ")
stdscr.addstr(3, 2, f"Node state : ")
# # Header - 顯示選單路徑
# path = " → ".join([menu.name for menu in menu_stack])
# stdscr.addstr(0, 2, f"控制面板: {path}", curses.A_BOLD)
# stdscr.addstr(1, 2, f"狀態: {state.status} | 速度: {state.speed} | 計數: {state.counter}")
# if state.user_input:
# stdscr.addstr(2, 2, f"輸入: {state.user_input[:50]}...")
# 顯示當前選單項目
start_line = 5
for i, child in enumerate(current_menu.children):
marker = "" if i == current_idx else " "
line = f"{marker}{child.name:15s} {child.desc}"
attr = curses.A_REVERSE if i == current_idx else curses.A_NORMAL
stdscr.addstr(start_line + i, 4, line, attr)
# 操作說明
help_line = start_line + len(current_menu.children) + 2
stdscr.addstr(help_line, 2, "操作: ↑↓選擇 Enter確認 ←返回上層 ←→調參數 q退出", curses.A_DIM)
stdscr.refresh()
# 設定短暫的 timeout讓執行緒能夠響應 stop_evt
stdscr.timeout(100) # 100ms timeout
ch = stdscr.getch()
if ch == -1: # timeout繼續檢查 stop_evt
continue
# 處理按鍵
if ch in (curses.KEY_UP, ord('k')):
idx_stack[-1] = (current_idx - 1) % len(current_menu.children)
elif ch in (curses.KEY_DOWN, ord('j')):
idx_stack[-1] = (current_idx + 1) % len(current_menu.children)
elif ch == curses.KEY_LEFT:
# 返回上層或調整參數
if len(menu_stack) > 1:
menu_stack.pop()
idx_stack.pop()
else:
# 在根選單,檢查是否為調整參數
selected = current_menu.children[current_idx]
if selected.action == "ADJUST_SPEED":
state.speed = max(1, state.speed - 1)
elif ch == curses.KEY_RIGHT:
# 調整參數
selected = current_menu.children[current_idx]
if selected.action == "ADJUST_SPEED":
state.speed = min(10, state.speed + 1)
elif ch in (curses.KEY_ENTER, 10, 13):
selected = current_menu.children[current_idx]
# 處理不同類型的動作
if selected.children: # 有子選單
menu_stack.append(selected)
idx_stack.append(0)
elif selected.action == "BACK":
if len(menu_stack) > 1:
menu_stack.pop()
idx_stack.pop()
elif selected.action == "QUIT":
break
elif selected.action == "INPUT_TEXT":
# 進入輸入模式
result = input_dialog(stdscr, "請輸入文字: ")
if result is not None:
cmd_q.put(lambda: state.set_user_input(result))
elif selected.action == "SHOW_STATUS":
# 顯示狀態訊息
stdscr.clear()
stdscr.addstr(5, 2, f"當前狀態: {state.status}")
stdscr.addstr(6, 2, f"速度設定: {state.speed}")
stdscr.addstr(7, 2, f"計數器: {state.counter}")
stdscr.addstr(9, 2, "按任意鍵返回...")
stdscr.refresh()
stdscr.getch()
elif selected.action == "SHOW_INPUT":
# 顯示用戶輸入
stdscr.clear()
stdscr.addstr(5, 2, f"用戶輸入內容:")
stdscr.addstr(6, 2, f"{state.user_input}")
stdscr.addstr(8, 2, "按任意鍵返回...")
stdscr.refresh()
stdscr.getch()
elif callable(selected.action):
# 執行函式
cmd_q.put(selected.action)
elif ch in (ord('q'), 27):
break
try:
curses.wrapper(draw_menu)
except KeyboardInterrupt:
pass
finally:
cleanup()
def main():
logger.warning(f"Hello this is mainOrchestrator.py")
pp = ControlPanel()
cmd_q = queue.Queue()
stop_evt = threading.Event()
panel_thread_obj = None
def signal_handler(signum, frame):
"""處理 Ctrl+C 信號"""
print("\n收到中斷信號,正在安全退出...")
stop_evt.set()
if panel_thread_obj and panel_thread_obj.is_alive():
panel_thread_obj.join(timeout=2)
sys.exit(0)
# 註冊信號處理器
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# 啟動控制面板(改為非 daemon
panel_thread_obj = threading.Thread(target=pp.panel_thread, args=(cmd_q, stop_evt))
panel_thread_obj.start()
print("多層選單控制面板啟動。Ctrl+C 結束程式。")
try:
while not stop_evt.is_set():
# 取出面板丟過來的「動作」
try:
fn = cmd_q.get_nowait()
fn() # 執行對應動作
except queue.Empty:
pass
# # 模擬你的長跑邏輯
# if state.status == "running":
# # 依 speed 前進
# state.counter += state.speed
time.sleep(0.1)
except KeyboardInterrupt:
print("\n收到 Ctrl+C準備結束...")
finally:
stop_evt.set()
if panel_thread_obj.is_alive():
panel_thread_obj.join(timeout=2)
pass
if __name__ == "__main__":
main()

@ -1,10 +1,12 @@
import os
# 自定義的 import
from .theLogger import setup_logger
from .utils import setup_logger
# ====================== 分割線 =====================
logger = setup_logger("mavlinkDevice.py")
logger = setup_logger(os.path.basename(__file__))
# 用來記錄每個 system 的資訊
# 資料格式 { sysid : mavlink_device object }

@ -12,6 +12,7 @@
# 基礎功能的 import
import threading
import os
# import queue
import time
import asyncio
@ -27,12 +28,12 @@ from rclpy.node import Node
# 自定義的 import
from .mavlinkDevice import mavlink_device, mavlink_systems
from .mavlinkPublish import mavlink_publisher
from .theLogger import setup_logger
from .ringBuffer import RingBuffer
from .utils import RingBuffer, setup_logger
# ====================== 分割線 =====================
logger = setup_logger("mavlinkObject.py")
logger = setup_logger(os.path.basename(__file__))
stream_bridge_ring = RingBuffer(capacity=1024, buffer_id=255)
return_packet_ring = RingBuffer(capacity=256, buffer_id=254)
@ -328,7 +329,7 @@ class mavlink_object:
while self.state == MavlinkObjectState.RUNNING:
timestamp = time.time()
try:
try: #TODO 這邊的錯誤處理要再想想看怎麼做比較好
# 處理接收到的封包
mavlinkMsg = self.mavlink_socket.recv_msg()
# except Exception as e:
@ -368,7 +369,7 @@ class mavlink_object:
if target_port != self.socket_id and target_port in self.mavlinkObjects:
target_obj = self.mavlinkObjects[target_port]
if target_obj.state == MavlinkObjectState.RUNNING:
try:
try: # TODO 藉由 if 的檢測 確定目標端口是開啟狀態後 再進行寫出 之後刪掉 try except
target_obj.mavlink_socket.write(mavlinkMsg.get_msgbuf())
except Exception as e:
logger.error(f"Error forwarding message to port {target_port}: {e}")
@ -449,7 +450,7 @@ class mavlink_object:
try:
# 使用鎖保護共享資源訪問
with self.outgoing_msgs_lock:
with self.outgoing_msg_lock:
self.outgoing_msgs.append(message_bytes)
return True
except Exception as e:
@ -559,7 +560,7 @@ class async_io_manager:
task = self.managed_objects[socket_id]
if task.done():
try:
try: # TODO 這邊的錯誤處理要再想想看怎麼做比較好
exc = task.exception()
if exc:
logger.error(f"Task for mavlink_object {socket_id} raised exception: {exc}")

@ -9,6 +9,8 @@
publisher topic name 命名規則為 <前綴詞>/s<sysid>/<具體 topic>
'''
import os
# ROS2 的 import
import std_msgs.msg
import sensor_msgs.msg
@ -18,9 +20,9 @@ import mavros_msgs.msg
import math
# 自定義的 import
from .theLogger import setup_logger
from .utils import setup_logger
logger = setup_logger("mavlinkPublish.py")
logger = setup_logger(os.path.basename(__file__))
class mavlink_publisher():

@ -0,0 +1,7 @@
"""
共用工具模組
"""
from .ringBuffer import RingBuffer
from .theLogger import setup_logger
__all__ = ['RingBuffer', 'setup_logger']

@ -5,7 +5,7 @@ from logging.handlers import TimedRotatingFileHandler
# 全域 Logger 實例
_global_logger = None
def setup_logger(name: str, log_dir: str = "log", level=logging.DEBUG) -> logging.Logger:
def setup_logger(name: str, log_dir: str = "logs", level=logging.DEBUG) -> logging.Logger:
global _global_logger
if _global_logger is None:

@ -20,6 +20,7 @@ setup(
tests_require=['pytest'],
entry_points={
'console_scripts': [
'mavlink_orchestrator = fc_network_adapter.mainOrchestrator:main',
],
},
)

@ -13,12 +13,12 @@ import rclpy
from pymavlink import mavutil
# 自定義的 import
from fc_network_adapter import mavlinkObject as mo
from fc_network_adapter import mavlinkDevice as md
from ..fc_network_adapter import mavlinkObject as mo
from ..fc_network_adapter import mavlinkDevice as md
# ====================== 分割線 =====================
test_item = 12
test_item = 10
running_time = 10000

@ -0,0 +1,152 @@
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import time
import threading
from ..fc_network_adapter.utils import RingBuffer
def producer(buffer, count, interval=0.01):
"""生產者:向緩衝區添加資料"""
print(f"Producer started (thread {threading.get_ident()})")
for i in range(count):
# 嘗試寫入數據,直到成功
while not buffer.put(f"Item-{i}"):
print(f"Buffer full, producer waiting... (thread {threading.get_ident()})")
time.sleep(0.1)
print(f"Produced: Item-{i}, buffer size: {buffer.size()}")
time.sleep(interval) # 模擬生產過程
print(f"Producer finished (thread {threading.get_ident()})")
def consumer(buffer, max_items, interval=0.05):
"""消費者:從緩衝區讀取資料"""
print(f"Consumer started (thread {threading.get_ident()})")
items_consumed = 0
while items_consumed < max_items:
# 嘗試讀取數據
item = buffer.get()
if item:
print(f"Consumed: {item}, buffer size: {buffer.size()}")
items_consumed += 1
else:
print(f"Buffer empty, consumer waiting... (thread {threading.get_ident()})")
time.sleep(interval) # 模擬消費過程
print(f"Consumer finished (thread {threading.get_ident()})")
def batch_consumer(buffer, interval=0.2):
"""批量消費者:一次性讀取緩衝區中的所有資料"""
print(f"Batch consumer started (thread {threading.get_ident()})")
for _ in range(5): # 執行5次批量讀取
time.sleep(interval) # 等待緩衝區積累數據
items = buffer.get_all()
if items:
print(f"Batch consumed {len(items)} items: {items}")
else:
print("Buffer empty for batch consumer")
print(f"Batch consumer finished (thread {threading.get_ident()})")
def demonstrate_multi_writer():
"""示範多個寫入執行緒同時操作緩衝區"""
print("\n=== Demonstrating Multiple Writers ===")
buffer = RingBuffer(capacity=80)
# 創建多個生產者執行緒
threads = []
for i in range(3):
thread = threading.Thread(target=producer, args=(buffer, 5, 0.1 * (i+1)))
threads.append(thread)
thread.start()
# 等待所有執行緒完成
for thread in threads:
thread.join()
buffer.print_stats() # 印出統計資訊
# 讀出所有剩餘資料
remaining = buffer.get_all()
print(f"Remaining items in buffer after multiple writers: {remaining}")
def demonstrate_basic_usage():
"""示範基本使用方式"""
print("\n=== Demonstrating Basic Usage ===")
# 創建緩衝區
buffer = RingBuffer(capacity=20, buffer_id=7)
# 檢查初始狀態
print(f"Initial buffer state - Empty: {buffer.is_empty()}, Full: {buffer.is_full()}, Content Size: {buffer.size()}")
# 添加幾個項目
for i in range(5):
buffer.put(f"Test-{i}")
# 檢查狀態
print(f"After adding 5 items - Empty: {buffer.is_empty()}, Full: {buffer.is_full()}, Content Size: {buffer.size()}")
# 讀取一個項目
item = buffer.get()
print(f"Read item: {item}")
print(f"After reading 1 item - Content Size: {buffer.size()}")
# 添加更多項目直到滿
items_added = 0
while not buffer.is_full():
buffer.put(f"Fill-{items_added}")
items_added += 1
print(f"Added {items_added} more items until full")
print(f"Buffer full state - Empty: {buffer.is_empty()}, Full: {buffer.is_full()}, Content Size: {buffer.size()}")
# 嘗試添加到已滿的緩衝區
result = buffer.put("Overflow")
print(f"Attempt to add to full buffer: {'Succeeded' if result else 'Failed'}")
# 獲取所有項目
all_items = buffer.get_all()
print(f"All items in buffer: {all_items}")
print(f"Buffer after get_all() - Empty: {buffer.is_empty()}, Content Size: {buffer.size()}")
# 印出統計資訊
buffer.print_stats()
def demonstrate_producer_consumer():
"""示範生產者-消費者模式"""
print("\n=== Demonstrating Producer-Consumer Pattern ===")
buffer = RingBuffer(capacity=16)
# 創建生產者和消費者執行緒
producer_thread = threading.Thread(target=producer, args=(buffer, 20, 0.1))
consumer_thread = threading.Thread(target=consumer, args=(buffer, 3, 0.2))
batch_thread = threading.Thread(target=batch_consumer, args=(buffer, 0.5))
# 啟動執行緒
producer_thread.start()
consumer_thread.start()
batch_thread.start()
# 等待執行緒完成
producer_thread.join()
consumer_thread.join()
batch_thread.join()
# 檢查最終狀態
print(f"Final buffer state - Empty: {buffer.is_empty()}, Size: {buffer.size()}")
buffer.print_stats()
if __name__ == "__main__":
# 展示各種使用場景
# demonstrate_basic_usage()
# demonstrate_producer_consumer()
demonstrate_multi_writer()
print("\nAll demonstrations completed!")

@ -16,7 +16,7 @@ import asyncio
from unittest.mock import MagicMock, patch
# 導入要測試的模組
from fc_network_adapter.mavlinkObject import (
from ..fc_network_adapter.mavlinkObject import (
mavlink_object,
async_io_manager,
MavlinkObjectState,
@ -466,3 +466,4 @@ class TestIntegration(unittest.TestCase):
if __name__ == "__main__":
unittest.main(defaultTest="TestMavlinkObject.test_send_message")
# unittest.main(defaultTest="TestAsyncIOManager")

@ -1,152 +1,296 @@
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
#!/usr/bin/env python
"""
測試 RingBuffer 類別的功能
"""
import time
import unittest
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from ..fc_network_adapter.utils import RingBuffer
class TestRingBuffer(unittest.TestCase):
"""測試 RingBuffer 基本功能"""
def setUp(self):
"""每個測試前的準備"""
self.buffer = RingBuffer(capacity=8)
def test_initialization(self):
"""測試初始化"""
self.assertEqual(self.buffer.capacity, 8)
self.assertTrue(self.buffer.is_empty())
self.assertFalse(self.buffer.is_full())
self.assertEqual(self.buffer.size(), 0)
def test_put_get_basic(self):
"""測試基本的放入和取出"""
# 測試放入
self.assertTrue(self.buffer.put("item1"))
self.assertTrue(self.buffer.put("item2"))
self.assertEqual(self.buffer.size(), 2)
self.assertFalse(self.buffer.is_empty())
# 測試取出
item1 = self.buffer.get()
self.assertEqual(item1, "item1")
self.assertEqual(self.buffer.size(), 1)
item2 = self.buffer.get()
self.assertEqual(item2, "item2")
self.assertTrue(self.buffer.is_empty())
# 空緩衝區取出應返回 None
self.assertIsNone(self.buffer.get())
def test_capacity_overflow(self):
"""測試緩衝區容量限制"""
# 填滿緩衝區 (容量-1因為需要預留一個位置)
for i in range(7): # 8-1=7
self.assertTrue(self.buffer.put(f"item{i}"))
self.assertTrue(self.buffer.is_full())
# 嘗試再放入一個應該失敗
self.assertFalse(self.buffer.put("overflow"))
self.assertEqual(self.buffer.overflow_count.value, 1)
def test_get_all(self):
"""測試取出所有項目"""
items = ["a", "b", "c", "d"]
for item in items:
self.buffer.put(item)
all_items = self.buffer.get_all()
self.assertEqual(all_items, items)
self.assertTrue(self.buffer.is_empty())
def test_clear(self):
"""測試清空緩衝區"""
for i in range(5):
self.buffer.put(f"item{i}")
self.buffer.clear()
self.assertTrue(self.buffer.is_empty())
self.assertEqual(self.buffer.size(), 0)
class TestRingBufferThreadSafety(unittest.TestCase):
"""測試 RingBuffer 的線程安全性"""
def setUp(self):
"""每個測試前的準備"""
self.buffer = RingBuffer(capacity=256)
self.results = []
self.write_count = 1000
self.read_count = 1000
def test_concurrent_producers_consumers(self):
"""測試多生產者多消費者場景"""
self.results = []
stats = self.buffer.get_stats()
self.assertEqual(stats['total_writes'], 0)
def producer(producer_id, count):
"""生產者函數"""
for i in range(count):
item = f"producer_{producer_id}_item_{i}"
while not self.buffer.put(item):
time.sleep(0.001) # 緩衝區滿時稍微等待
def consumer(consumer_id, count):
"""消費者函數"""
items = []
for _ in range(count):
item = None
while item is None:
item = self.buffer.get()
if item is None:
time.sleep(0.001) # 緩衝區空時稍微等待
items.append(item)
self.results.extend(items)
# 創建多個生產者和消費者
with ThreadPoolExecutor(max_workers=8) as executor:
# 2 個生產者,每個寫入 500 個項目
producer_futures = [
executor.submit(producer, 0, 500),
executor.submit(producer, 1, 500)
]
# 2 個消費者,每個讀取 500 個項目
consumer_futures = [
executor.submit(consumer, 0, 500),
executor.submit(consumer, 1, 500)
]
# 等待所有任務完成
for future in producer_futures + consumer_futures:
future.result()
# 驗證結果
self.assertEqual(len(self.results), 1000)
self.assertTrue(self.buffer.is_empty())
# 檢查統計數據
stats = self.buffer.get_stats()
self.assertEqual(stats['total_writes'], 1000)
self.assertGreater(stats['total_reads'], 1000) # 包含失敗的讀取嘗試
self.assertGreater(stats['write_threads'], 1)
self.assertGreater(stats['read_threads'], 1)
def test_high_throughput(self):
"""測試高吞吐量場景"""
items_per_thread = 10000
num_threads = 4
def writer():
for i in range(items_per_thread):
while not self.buffer.put(i):
pass # 忙等待
def reader():
items = []
for _ in range(items_per_thread):
item = None
while item is None:
item = self.buffer.get()
items.append(item)
self.results.extend(items)
start_time = time.time()
with ThreadPoolExecutor(max_workers=num_threads * 2) as executor:
# 啟動寫入線程
writer_futures = [executor.submit(writer) for _ in range(num_threads)]
# 啟動讀取線程
reader_futures = [executor.submit(reader) for _ in range(num_threads)]
# 等待完成
for future in writer_futures + reader_futures:
future.result()
end_time = time.time()
# 驗證結果
total_items = items_per_thread * num_threads
self.assertEqual(len(self.results), total_items)
# 性能統計
duration = end_time - start_time
throughput = total_items / duration
print(f"\nHigh Throughput Test Results:")
print(f"Total items: {total_items}")
print(f"Duration: {duration:.2f}s")
print(f"Throughput: {throughput:.0f} items/sec")
# 顯示詳細統計
self.buffer.print_stats()
class TestRingBufferStatistics(unittest.TestCase):
"""測試 RingBuffer 的統計功能"""
def test_statistics_tracking(self):
"""測試統計數據追蹤"""
buffer = RingBuffer(capacity=16)
# 寫入一些數據
for i in range(10):
buffer.put(f"item{i}")
# 讀取一些數據
for _ in range(5):
buffer.get()
stats = buffer.get_stats()
# 驗證基本統計
self.assertEqual(stats['total_writes'], 10)
self.assertEqual(stats['total_reads'], 5)
self.assertEqual(stats['current_size'], 5)
self.assertEqual(stats['write_threads'], 1)
self.assertEqual(stats['read_threads'], 1)
from fc_network_adapter.ringBuffer import RingBuffer
def test_reset_statistics(self):
"""測試重置統計數據"""
buffer = RingBuffer(capacity=16)
# 產生一些活動
for i in range(5):
buffer.put(f"item{i}")
for _ in range(3):
buffer.get()
# 重置統計
buffer.reset_stats()
stats = buffer.get_stats()
self.assertEqual(stats['total_writes'], 0)
self.assertEqual(stats['total_reads'], 0)
self.assertEqual(stats['concurrent_writes'], 0)
self.assertEqual(stats['concurrent_reads'], 0)
self.assertEqual(stats['overflow_count'], 0)
def benchmark_ringbuffer():
"""RingBuffer 性能基準測試"""
print("\n=== RingBuffer Performance Benchmark ===")
buffer = RingBuffer(capacity=1024)
num_operations = 100000
def producer(buffer, count, interval=0.01):
"""生產者:向緩衝區添加資料"""
print(f"Producer started (thread {threading.get_ident()})")
for i in range(count):
# 嘗試寫入數據,直到成功
while not buffer.put(f"Item-{i}"):
print(f"Buffer full, producer waiting... (thread {threading.get_ident()})")
time.sleep(0.1)
print(f"Produced: Item-{i}, buffer size: {buffer.size()}")
time.sleep(interval) # 模擬生產過程
print(f"Producer finished (thread {threading.get_ident()})")
def consumer(buffer, max_items, interval=0.05):
"""消費者:從緩衝區讀取資料"""
print(f"Consumer started (thread {threading.get_ident()})")
items_consumed = 0
while items_consumed < max_items:
# 嘗試讀取數據
item = buffer.get()
if item:
print(f"Consumed: {item}, buffer size: {buffer.size()}")
items_consumed += 1
else:
print(f"Buffer empty, consumer waiting... (thread {threading.get_ident()})")
time.sleep(interval) # 模擬消費過程
print(f"Consumer finished (thread {threading.get_ident()})")
def batch_consumer(buffer, interval=0.2):
"""批量消費者:一次性讀取緩衝區中的所有資料"""
print(f"Batch consumer started (thread {threading.get_ident()})")
for _ in range(5): # 執行5次批量讀取
time.sleep(interval) # 等待緩衝區積累數據
items = buffer.get_all()
if items:
print(f"Batch consumed {len(items)} items: {items}")
else:
print("Buffer empty for batch consumer")
print(f"Batch consumer finished (thread {threading.get_ident()})")
def demonstrate_multi_writer():
"""示範多個寫入執行緒同時操作緩衝區"""
print("\n=== Demonstrating Multiple Writers ===")
buffer = RingBuffer(capacity=80)
# 創建多個生產者執行緒
threads = []
for i in range(3):
thread = threading.Thread(target=producer, args=(buffer, 5, 0.1 * (i+1)))
threads.append(thread)
thread.start()
# 等待所有執行緒完成
for thread in threads:
thread.join()
buffer.print_stats() # 印出統計資訊
# 讀出所有剩餘資料
remaining = buffer.get_all()
print(f"Remaining items in buffer after multiple writers: {remaining}")
def demonstrate_basic_usage():
"""示範基本使用方式"""
print("\n=== Demonstrating Basic Usage ===")
# 創建緩衝區
buffer = RingBuffer(capacity=20, buffer_id=7)
# 檢查初始狀態
print(f"Initial buffer state - Empty: {buffer.is_empty()}, Full: {buffer.is_full()}, Content Size: {buffer.size()}")
# 添加幾個項目
for i in range(5):
buffer.put(f"Test-{i}")
# 檢查狀態
print(f"After adding 5 items - Empty: {buffer.is_empty()}, Full: {buffer.is_full()}, Content Size: {buffer.size()}")
# 讀取一個項目
item = buffer.get()
print(f"Read item: {item}")
print(f"After reading 1 item - Content Size: {buffer.size()}")
# 添加更多項目直到滿
items_added = 0
while not buffer.is_full():
buffer.put(f"Fill-{items_added}")
items_added += 1
print(f"Added {items_added} more items until full")
print(f"Buffer full state - Empty: {buffer.is_empty()}, Full: {buffer.is_full()}, Content Size: {buffer.size()}")
# 嘗試添加到已滿的緩衝區
result = buffer.put("Overflow")
print(f"Attempt to add to full buffer: {'Succeeded' if result else 'Failed'}")
# 獲取所有項目
all_items = buffer.get_all()
print(f"All items in buffer: {all_items}")
print(f"Buffer after get_all() - Empty: {buffer.is_empty()}, Content Size: {buffer.size()}")
# 印出統計資訊
buffer.print_stats()
def demonstrate_producer_consumer():
"""示範生產者-消費者模式"""
print("\n=== Demonstrating Producer-Consumer Pattern ===")
buffer = RingBuffer(capacity=16)
# 單線程性能測試
start_time = time.time()
for i in range(num_operations):
buffer.put(i)
for _ in range(num_operations):
buffer.get()
end_time = time.time()
# 創建生產者和消費者執行緒
producer_thread = threading.Thread(target=producer, args=(buffer, 20, 0.1))
consumer_thread = threading.Thread(target=consumer, args=(buffer, 3, 0.2))
batch_thread = threading.Thread(target=batch_consumer, args=(buffer, 0.5))
single_thread_time = end_time - start_time
throughput = (num_operations * 2) / single_thread_time
# 啟動執行緒
producer_thread.start()
consumer_thread.start()
batch_thread.start()
print(f"Single Thread: {throughput:.0f} ops/sec")
# 等待執行緒完成
producer_thread.join()
consumer_thread.join()
batch_thread.join()
# 多線程性能測試
buffer = RingBuffer(capacity=1024)
# 檢查最終狀態
print(f"Final buffer state - Empty: {buffer.is_empty()}, Size: {buffer.size()}")
def producer():
for i in range(num_operations // 2):
while not buffer.put(i):
pass
def consumer():
for _ in range(num_operations // 2):
while buffer.get() is None:
pass
start_time = time.time()
with ThreadPoolExecutor(max_workers=2) as executor:
future1 = executor.submit(producer)
future2 = executor.submit(consumer)
future1.result()
future2.result()
end_time = time.time()
multi_thread_time = end_time - start_time
throughput = num_operations / multi_thread_time
print(f"Multi Thread: {throughput:.0f} ops/sec")
print(f"Speedup: {single_thread_time/multi_thread_time:.2f}x")
buffer.print_stats()
if __name__ == "__main__":
# 展示各種使用場景
# demonstrate_basic_usage()
# demonstrate_producer_consumer()
demonstrate_multi_writer()
# 運行單元測試
unittest.main(argv=[''], exit=False, verbosity=2)
print("\nAll demonstrations completed!")
# 運行性能基準測試
benchmark_ringbuffer()
Loading…
Cancel
Save