From 524c0031f0f33010ac196b5a2a4366964eefae24 Mon Sep 17 00:00:00 2001 From: caibingcheng Date: Fri, 26 Jun 2026 15:17:53 +0800 Subject: [PATCH 1/3] feat(build): use setuptools_scm for dynamic versioning from git tags --- pyproject.toml | 5 +++++ server/__init__.py | 6 ++++++ setup.py | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9bf6df9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,5 @@ +[build-system] +requires = ["setuptools>=64", "setuptools_scm>=8"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] diff --git a/server/__init__.py b/server/__init__.py index e69de29..f9dfc5f 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -0,0 +1,6 @@ +from importlib.metadata import PackageNotFoundError, version + +try: + __version__ = version("plotop") +except PackageNotFoundError: + __version__ = "unknown" diff --git a/setup.py b/setup.py index 023fc4d..60fd9f3 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="plotop", - version="0.1.0", + use_scm_version=True, packages=find_packages(include=["server", "server.*", "server.templates", "server.templates.*"]), package_data={ "server": [ From 47c819d2153bf7ca2d115af6ee28b5e4604deec7 Mon Sep 17 00:00:00 2001 From: caibingcheng Date: Fri, 26 Jun 2026 15:39:52 +0800 Subject: [PATCH 2/3] docs(readme): update installation and usage instructions, correct TCP data collection description --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 16f94c2..dcef5d9 100644 --- a/README.md +++ b/README.md @@ -8,23 +8,23 @@ 开发Plotop的主要目的是解决在嵌入式系统上做性能统计的困难。以往,我们通过top在嵌入式系统上观察性能数据,但是top信息往往是一小段时间内的快照,无法判断长时间运行的性能数据。针对这个问题,我开发过一些top解析脚本,通过将top信息写入文件,然后再解析问题,但是这种方案不尽我意。 -Plotop由一个轻量的client和一个server组成。client负责采集性能数据并发送到server,client仅做简单的数据采集,避免对被测系统的影响。server可以一拖多用,支持多台client同时采集数据。server提供了一个web界面,可以实时查看性能数据,可以做数据分析,统计一段时间的性能数据。 +Plotop由一个**轻量**的client和一个server组成。client负责采集性能数据并发送到server,client仅做简单的数据采集,避免对被测系统的影响。server可以一拖多用,支持多台client同时采集数据。server提供了一个web界面,可以实时查看性能数据,可以做数据分析,统计一段时间的性能数据。 ![demo](statics/demo.png) 当前已实现以下核心组件: -- **C++17 客户端**:实现基础网络数据采集 +- **C++17 客户端**:实现基础网络数据采集(TCP),仅做采集,确保client对host的影响小 - **Python Flask 服务端**:实现数据接收和存储 - **Web界面**:实时数据图表展示 ### 主要功能 -✅ 基础HTTP数据采集 +✅ 基础TCP数据采集 ✅ 简单数据缓存处理 ✅ 静态图表展示 -✅ 支持按照pid或进程名过滤数据 -🚧 可视化配置界面(开发中) +✅ 支持按照pid或进程名过滤数据(Web端) +✅ 可视化配置界面 ### 快速开始 @@ -32,7 +32,6 @@ Plotop由一个轻量的client和一个server组成。client负责采集性能 - Python 3.8+ - C++17 兼容编译器 -- CMake 3.12+ #### 安装步骤 @@ -46,14 +45,17 @@ pip install -r requirements.txt # 编译客户端 make + +# 启动交叉编译 +CROSSCOMPILER=aarch64-linux-gnu- make ``` 或者 -通过pip安装服务端: +通过pipx安装服务端: ```bash -pip install git+https://github.com/caibingcheng/plotop.git +pipx install git+https://github.com/caibingcheng/plotop.git ``` 从[https://github.com/caibingcheng/plotop/releases/latest](https://github.com/caibingcheng/plotop/releases/latest)下载最新的客户端。 @@ -63,19 +65,17 @@ pip install git+https://github.com/caibingcheng/plotop.git ```bash # 启动服务端 python -m server -# plotop +# 或者通过 pipx 安装后 +plotop # 运行客户端 ./plotop -i -p -d -# 按进程名过滤 -./plotop -i -p -d -P - -# 按 PID 过滤(可多次指定) -./plotop -i -p -d -I -I - # 查看帮助 ./plotop --help + +# 在 Web 界面按进程名或 PID 过滤数据 +# 打开 http://:5000/?ip=,点击 "Select Processes" ``` ### 项目结构 From a9edf3efe7c6f879da416ad8fb57544aff2bddd3 Mon Sep 17 00:00:00 2001 From: caibingcheng Date: Fri, 26 Jun 2026 15:40:49 +0800 Subject: [PATCH 3/3] feat(cli): add --help and --version for server and client, share version from git tags ci(build): fetch full git history for version detection ci(build): verify binary version after build ci(release): install and verify python package version before release --- .github/workflows/build.yml | 19 ++++++++++++ client/cmdline.h | 21 +++++++++++++ makefile | 5 +++- scripts/get_version.py | 59 +++++++++++++++++++++++++++++++++++++ server/app.py | 25 ++++++++++++---- 5 files changed, 123 insertions(+), 6 deletions(-) create mode 100755 scripts/get_version.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d3a7b9a..587f56a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,8 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup toolchain run: | @@ -33,6 +35,10 @@ jobs: make CROSSCOMPILER="$CROSSCOMPILER" mv plotop plotop-${{ matrix.arch }} + - name: Verify version + run: | + ./plotop-${{ matrix.arch }} --version + - name: Upload Artifact uses: actions/upload-artifact@v4 with: @@ -55,6 +61,19 @@ jobs: fetch-depth: 0 ref: ${{ github.ref }} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install plotop + run: | + pip install . + + - name: Verify Python package version + run: | + plotop --version + - name: Download Artifact uses: actions/download-artifact@v4 diff --git a/client/cmdline.h b/client/cmdline.h index 5cb6a10..07cfbd3 100644 --- a/client/cmdline.h +++ b/client/cmdline.h @@ -10,6 +10,10 @@ #include #include +#ifndef PLOTOP_VERSION +#define PLOTOP_VERSION "unknown" +#endif + class Cmdline { struct ArgumentInfo { char short_name; @@ -23,6 +27,8 @@ class Cmdline { Cmdline() { arguments_["h"] = arguments_["help"] = [&](const std::string &) { help_requested_ = true; }; args_info_.push_back({'h', "help", "Show this help message and exit", "", false}); + arguments_["v"] = arguments_["version"] = [&](const std::string &) { version_requested_ = true; }; + args_info_.push_back({'v', "version", "Show version information and exit", "", false}); } ~Cmdline() {} @@ -93,6 +99,11 @@ class Cmdline { continue; } + if (name == "v" || name == "version") { + version_requested_ = true; + continue; + } + for (i++; i < argc; i++) { if (argv[i][0] == '-') { i--; @@ -111,10 +122,15 @@ class Cmdline { help(); return false; } + if (version_requested_) { + version(); + return false; + } return true; } void help() const { + std::cout << program_name_ << " " << PLOTOP_VERSION << "\n\n"; std::cout << "Usage: " << program_name_ << " [options]\n\n"; std::cout << "Options:\n"; @@ -143,11 +159,16 @@ class Cmdline { } } + void version() const { + std::cout << program_name_ << " " << PLOTOP_VERSION << "\n"; + } + private: std::unordered_map> arguments_; std::vector args_info_; std::string program_name_ = "plotop"; bool help_requested_ = false; + bool version_requested_ = false; }; #endif // PLOTOP_CMDLINE_H diff --git a/makefile b/makefile index f5e8ae4..ca2a220 100644 --- a/makefile +++ b/makefile @@ -1,11 +1,14 @@ PLATFORM ?= linux CROSSCOMPILER ?= +# Version from git tag (same format as setuptools_scm) +VERSION := $(shell python3 scripts/get_version.py 2>/dev/null || echo "unknown") + # Compiler CXX := $(CROSSCOMPILER)g++ # Compiler flags -CXXFLAGS := -std=c++17 -Wall +CXXFLAGS := -std=c++17 -Wall -DPLOTOP_VERSION=\"$(VERSION)\" # Linker flags LDFLAGS := -lstdc++fs -pthread -static-libstdc++ -static-libgcc diff --git a/scripts/get_version.py b/scripts/get_version.py new file mode 100755 index 0000000..7c6f486 --- /dev/null +++ b/scripts/get_version.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Generate version string matching setuptools_scm default format.""" + +import re +import subprocess +import sys +from datetime import datetime, timezone + + +def _bump_version(tag: str) -> str: + """Bump the last numeric segment of a version tag.""" + # Strip leading 'v' or 'V' + version = tag.lstrip("vV") + parts = version.split(".") + if parts and parts[-1].isdigit(): + parts[-1] = str(int(parts[-1]) + 1) + else: + parts.append("1") + return ".".join(parts) + + +def _git_describe() -> str: + result = subprocess.run( + ["git", "describe", "--tags", "--long", "--always", "--abbrev=9"], + capture_output=True, + text=True, + ) + if result.returncode != 0: + return "unknown" + return result.stdout.strip() + + +def _fallback_version() -> str: + desc = _git_describe() + if desc == "unknown": + return "unknown" + + # Format: v0.1.3-12-g524c003 + match = re.match(r"^[vV]?(.*)-(\d+)-g([0-9a-f]+)$", desc) + if not match: + return desc.lstrip("vV") + + tag, distance, node = match.groups() + next_version = _bump_version(tag) + today = datetime.now(timezone.utc).strftime("%Y%m%d") + return f"{next_version}.dev{distance}+g{node}.d{today}" + + +def get_version() -> str: + try: + from setuptools_scm import get_version as scm_get_version + + return scm_get_version() + except Exception: + return _fallback_version() + + +if __name__ == "__main__": + print(get_version()) diff --git a/server/app.py b/server/app.py index 2d58b79..f4965e3 100644 --- a/server/app.py +++ b/server/app.py @@ -1,13 +1,17 @@ -from flask import Flask, request, jsonify, render_template -from flask_socketio import SocketIO, emit +import argparse +from datetime import datetime, timedelta import json import logging +import os +import queue import socket import threading import time -import queue -import os -from datetime import datetime, timedelta + +from flask import Flask, request, jsonify, render_template +from flask_socketio import SocketIO, emit + +from server import __version__ logging.basicConfig(level=logging.INFO) @@ -301,6 +305,17 @@ def handle_clear_data(message): def main(): + parser = argparse.ArgumentParser( + prog="plotop", + description="Real-time performance plotting server" + ) + parser.add_argument( + "-v", "--version", + action="version", + version=f"%(prog)s {__version__}" + ) + parser.parse_args() + run_socket_server() app.logger.info("Web server starting on http://127.0.0.1:5000") # 禁用开发服务器的警告日志