This is an automated email from the ASF dual-hosted git repository.
harishgokul01 pushed a commit to branch development
in repository https://gitbox.apache.org/repos/asf/incubator-resilientdb.git
The following commit(s) were added to refs/heads/development by this push:
new 81148929 Add .env to .gitignore, enable global stats increment in PBFT
commitment, update response handling in Stats, and introduce ResLens Flamegraph
Analysis Service with Gunicorn configuration and startup scripts.
81148929 is described below
commit 811489294c0367af57ee6813b833f6b33ac90917
Author: harish876 <[email protected]>
AuthorDate: Sat Dec 20 04:19:52 2025 +0000
Add .env to .gitignore, enable global stats increment in PBFT commitment,
update response handling in Stats, and introduce ResLens Flamegraph Analysis
Service with Gunicorn configuration and startup scripts.
---
.gitignore | 1 +
platform/consensus/ordering/pbft/commitment.cpp | 4 +-
platform/statistic/stats.cpp | 5 +-
scripts/deploy/script/restart_reslens.sh | 14 ++
service/tools/config/server/server.config | 5 +-
.../tools/kv/api_tools/monitoring/gunicorn.conf.py | 38 ++++
.../tools/kv/api_tools/monitoring/requirements.txt | 10 +
.../api_tools/monitoring/reslens_tools_service.py | 243 +++++++++++++++++++++
.../monitoring/start_reslens_tools_service.sh | 75 +++++++
9 files changed, 389 insertions(+), 6 deletions(-)
diff --git a/.gitignore b/.gitignore
index acd52c56..d5db0cc9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ resdb/
gmon.out
.history/
service/tools/data/cert/
+*.env
# Generated LLM cache and Pocketflow outputs
llm_cache.json
ecosystem/pocketflow/.pf-output/
diff --git a/platform/consensus/ordering/pbft/commitment.cpp
b/platform/consensus/ordering/pbft/commitment.cpp
index 4d048331..4a1aacc0 100644
--- a/platform/consensus/ordering/pbft/commitment.cpp
+++ b/platform/consensus/ordering/pbft/commitment.cpp
@@ -245,7 +245,7 @@ int Commitment::ProcessPrepareMsg(std::unique_ptr<Context>
context,
return message_manager_->AddConsensusMsg(context->signature,
std::move(request));
}
- //global_stats_->IncPrepare();
+ global_stats_->IncPrepare();
std::unique_ptr<Request> commit_request = resdb::NewRequest(
Request::TYPE_COMMIT, *request, config_.GetSelfInfo().id());
commit_request->mutable_data_signature()->Clear();
@@ -288,7 +288,7 @@ int Commitment::ProcessCommitMsg(std::unique_ptr<Context>
context,
return message_manager_->AddConsensusMsg(context->signature,
std::move(request));
}
- //global_stats_->IncCommit();
+ global_stats_->IncCommit();
// Add request to message_manager.
// If it has received enough same requests(2f+1), message manager will
// commit the request.
diff --git a/platform/statistic/stats.cpp b/platform/statistic/stats.cpp
index 21f1524f..dfc1957d 100644
--- a/platform/statistic/stats.cpp
+++ b/platform/statistic/stats.cpp
@@ -167,12 +167,13 @@ void Stats::CrowRoute() {
res.set_header(
"Access-Control-Allow-Headers",
"Content-Type, Authorization"); // Specify allowed headers
-
+
+ res.body = "Not Enabled";
// Send your response
if (enable_faulty_switch_) {
make_faulty_.store(!make_faulty_.load());
+ res.body = "Success";
}
- res.body = "Success";
res.end();
});
CROW_ROUTE(app, "/transaction_data")
diff --git a/scripts/deploy/script/restart_reslens.sh
b/scripts/deploy/script/restart_reslens.sh
new file mode 100755
index 00000000..229fe830
--- /dev/null
+++ b/scripts/deploy/script/restart_reslens.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+cd /home/ubuntu/resilient-monitoring/ResLens-Middleware
+
+# Stop existing containers (if any)
+sudo docker-compose down || true
+
+# Start containers in detached mode
+sudo docker-compose up -d
+
+
+
diff --git a/service/tools/config/server/server.config
b/service/tools/config/server/server.config
index 88fd8c50..8b435651 100644
--- a/service/tools/config/server/server.config
+++ b/service/tools/config/server/server.config
@@ -47,8 +47,9 @@
block_cache_capacity: 100
},
require_txn_validation:true,
- enable_viewchange:false,
+ enable_viewchange:true,
enable_resview:true,
- enable_faulty_switch:false
+ enable_faulty_switch:false,
+ recovery_enabled:false,
}
diff --git a/service/tools/kv/api_tools/monitoring/gunicorn.conf.py
b/service/tools/kv/api_tools/monitoring/gunicorn.conf.py
new file mode 100644
index 00000000..d763514e
--- /dev/null
+++ b/service/tools/kv/api_tools/monitoring/gunicorn.conf.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+"""
+Gunicorn configuration for ResLens Flamegraph Analysis Service
+"""
+
+# Server socket
+bind = "0.0.0.0:8080"
+backlog = 2048
+
+# Worker processes
+workers = 1 # Single worker for this service
+worker_class = "sync"
+worker_connections = 1000
+max_requests = 1000
+max_requests_jitter = 50
+
+# Timeout
+timeout = 30
+keepalive = 2
+
+# Logging
+accesslog = "-"
+errorlog = "-"
+loglevel = "info"
+access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s"
"%(a)s"'
+
+# Process naming
+proc_name = "reslens-flamegraph-service"
+
+# Preload app for better performance
+preload_app = True
+
+# Worker timeout
+graceful_timeout = 30
+
+# Restart workers after this many requests
+max_requests = 1000
+max_requests_jitter = 50
\ No newline at end of file
diff --git a/service/tools/kv/api_tools/monitoring/requirements.txt
b/service/tools/kv/api_tools/monitoring/requirements.txt
new file mode 100644
index 00000000..81e680cb
--- /dev/null
+++ b/service/tools/kv/api_tools/monitoring/requirements.txt
@@ -0,0 +1,10 @@
+blinker==1.9.0
+click==8.2.1
+Flask==3.1.1
+flask-cors==6.0.1
+gunicorn==23.0.0
+itsdangerous==2.2.0
+Jinja2==3.1.6
+MarkupSafe==3.0.2
+packaging==25.0
+Werkzeug==3.1.3
diff --git a/service/tools/kv/api_tools/monitoring/reslens_tools_service.py
b/service/tools/kv/api_tools/monitoring/reslens_tools_service.py
new file mode 100644
index 00000000..964e7311
--- /dev/null
+++ b/service/tools/kv/api_tools/monitoring/reslens_tools_service.py
@@ -0,0 +1,243 @@
+#!/usr/bin/env python3
+"""
+ResLens Flamegraph Analysis Service
+
+A simple HTTP service for executing ResilientDB random data operations
+as part of the ResLens monitoring and analysis toolkit.
+
+This utility is used mainly to seed data to create a long running data seeding
job for flamegroah analysis as these flamegraph processes need to run for more
than 30s.
+"""
+
+import os
+import sys
+import json
+import subprocess
+import random
+import threading
+import time
+from flask import Flask, request, jsonify
+from flask_cors import CORS
+
+app = Flask(__name__)
+CORS(app)
+
+class ResLensToolsService:
+ def __init__(self):
+ self.project_root = self._find_project_root()
+ self.seeding_running = False
+ self.seeding_thread = None
+ self.seeding_lock = threading.Lock()
+ print(f"ResLens Tools Service - Using project root:
{self.project_root}")
+
+ def _find_project_root(self):
+ """Find the ResilientDB project root directory."""
+ resilientdb_root = os.getenv("RESILIENTDB_ROOT")
+ if resilientdb_root:
+ return resilientdb_root
+
+ # Since we're now in service/tools/kv/api_tools/monitoring, go up to
project root
+ current_dir = os.path.dirname(os.path.abspath(__file__))
+ project_root = os.path.join(current_dir, "../../../../../../")
+
+ # Verify this is the project root by checking for bazel-bin
+ if os.path.exists(os.path.join(project_root, "bazel-bin")):
+ return project_root
+
+ # Fallback to common paths
+ possible_paths = [
+ "/opt/resilientdb",
+ "/home/ubuntu/incubator-resilientdb",
+ os.path.join(os.path.expanduser("~"), "resilientdb")
+ ]
+
+ for path in possible_paths:
+ tool_path = os.path.join(path,
"bazel-bin/service/tools/kv/api_tools/kv_service_tools")
+ if os.path.exists(tool_path):
+ return path
+
+ return "/opt/resilientdb"
+
+ def _get_tool_path(self):
+ """Get the path to the kv_service_tools binary."""
+ return
f"{self.project_root}/bazel-bin/service/tools/kv/api_tools/kv_service_tools"
+
+ def _check_tool_exists(self):
+ """Check if the CLI tool exists."""
+ tool_path = self._get_tool_path()
+ return os.path.exists(tool_path) and os.access(tool_path, os.X_OK)
+
+ def start_seeding(self, count):
+ """Start data seeding job in background thread."""
+ if self.seeding_running:
+ return {
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "error",
+ "message": "Seeding job already running"
+ }
+
+ self.seeding_running = True
+
+ def seeding_worker():
+ for i in range(count):
+ if not self.seeding_running:
+ break
+
+ key = f"key{random.randint(0, 499)}"
+ value = f"value{random.randint(0, 499)}"
+
+ cmd = [
+ self._get_tool_path(),
+ "--config",
f"{self.project_root}/service/tools/config/interface/service.config",
+ "--cmd", "set",
+ "--key", key,
+ "--value", value
+ ]
+
+ try:
+ subprocess.run(cmd, capture_output=True, text=True,
timeout=30)
+ except (subprocess.TimeoutExpired, Exception):
+ # Silently continue on errors - no logging needed
+ pass
+
+ time.sleep(0.1)
+
+ with self.seeding_lock:
+ self.seeding_running = False
+
+ self.seeding_thread = threading.Thread(target=seeding_worker,
daemon=True)
+ self.seeding_thread.start()
+
+ return {
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "success",
+ "message": f"Started seeding job with {count} operations"
+ }
+
+ def stop_seeding(self):
+ """Stop the data seeding job."""
+ if not self.seeding_running:
+ return {
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "error",
+ "message": "No seeding job running"
+ }
+
+ self.seeding_running = False
+ if self.seeding_thread and self.seeding_thread.is_alive():
+ self.seeding_thread.join(timeout=5)
+
+ return {
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "success",
+ "message": "Seeding job stopped"
+ }
+
+ def get_seeding_status(self):
+ """Get current seeding job status."""
+ with self.seeding_lock:
+ return {
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "running" if self.seeding_running else "stopped"
+ }
+
+# Global service instance
+service = ResLensToolsService()
+
[email protected]('/health', methods=['GET'])
+def health():
+ """Health check endpoint."""
+ return jsonify({
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "ok"
+ })
+
[email protected]('/seed', methods=['POST'])
+def start_seeding():
+ """Start data seeding job."""
+ try:
+ data = request.get_json()
+ if not data:
+ return jsonify({
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "error",
+ "message": "No JSON data provided"
+ }), 400
+
+ count = data.get('count')
+ if count is None:
+ return jsonify({
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "error",
+ "message": "Missing 'count' parameter"
+ }), 400
+
+ if not isinstance(count, int) or count <= 0:
+ return jsonify({
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "error",
+ "message": "Count must be a positive integer"
+ }), 400
+
+ # Check if CLI tool exists before starting
+ if not service._check_tool_exists():
+ return jsonify({
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "error",
+ "message": f"CLI tool not found at {service._get_tool_path()}"
+ }), 503
+
+ return jsonify(service.start_seeding(count))
+
+ except Exception as e:
+ return jsonify({
+ "service": "ResLens Flamegraph Analysis Service",
+ "status": "error",
+ "message": str(e)
+ }), 500
+
[email protected]('/stop', methods=['POST'])
+def stop_seeding():
+ """Stop data seeding job."""
+ return jsonify(service.stop_seeding())
+
[email protected]('/status', methods=['GET'])
+def get_status():
+ """Get seeding job status."""
+ return jsonify(service.get_seeding_status())
+
[email protected]('/', methods=['GET'])
+def root():
+ """Root endpoint with service information."""
+ return jsonify({
+ "service": "ResLens Flamegraph Analysis Service",
+ "description": "HTTP service for executing ResilientDB random data
operations",
+ "endpoints": {
+ "GET /health": "Health check",
+ "POST /seed": "Start data seeding job (JSON body: {\"count\": 5})",
+ "POST /stop": "Stop data seeding job",
+ "GET /status": "Get seeding job status"
+ },
+ "example": {
+ "POST /seed": {
+ "body": {"count": 10},
+ "response": "Starts background job to execute 10 random set
operations"
+ },
+ "POST /stop": {
+ "body": "{}",
+ "response": "Stops the running seeding job"
+ }
+ }
+ })
+
+if __name__ == '__main__':
+ port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
+
+ print(f"Starting ResLens Flamegraph Analysis Service on port {port}")
+ print("Available endpoints:")
+ print(" GET /health - Health check")
+ print(" POST /seed - Start data seeding job")
+ print(" POST /stop - Stop data seeding job")
+ print(" GET /status - Get seeding job status")
+ print(" GET / - Service information")
+
+ app.run(host='0.0.0.0', port=port, debug=False, threaded=True)
\ No newline at end of file
diff --git
a/service/tools/kv/api_tools/monitoring/start_reslens_tools_service.sh
b/service/tools/kv/api_tools/monitoring/start_reslens_tools_service.sh
new file mode 100755
index 00000000..f4ac21f6
--- /dev/null
+++ b/service/tools/kv/api_tools/monitoring/start_reslens_tools_service.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+#
+# Startup script for ResLens Flamegraph Analysis Service
+#
+
+# Set the directory to the script location
+cd "$(dirname "$0")"
+
+# Function to find and activate virtual environment
+activate_venv() {
+ # Check for common virtual environment locations
+ local venv_paths=(
+ "venv"
+ "env"
+ ".venv"
+ ".env"
+ "../venv"
+ "../env"
+ "../../venv"
+ "../../env"
+ "/opt/resilientdb/venv"
+ "/opt/resilientdb/env"
+ )
+
+ for venv_path in "${venv_paths[@]}"; do
+ if [[ -d "$venv_path" && -f "$venv_path/bin/activate" ]]; then
+ echo "Found virtual environment at: $venv_path"
+ source "$venv_path/bin/activate"
+ return 0
+ fi
+ done
+
+ # Check if we're already in a virtual environment
+ if [[ -n "$VIRTUAL_ENV" ]]; then
+ echo "Already in virtual environment: $VIRTUAL_ENV"
+ return 0
+ fi
+
+ echo "No virtual environment found. Using system Python."
+ return 1
+}
+
+# Try to activate virtual environment
+if activate_venv; then
+ echo "Using virtual environment: $VIRTUAL_ENV"
+else
+ echo "Using system Python"
+fi
+
+# Check Python version
+python_version=$(python3 --version 2>&1)
+echo "Python version: $python_version"
+
+# Check if gunicorn is installed
+if ! python3 -c "import gunicorn" &> /dev/null; then
+ echo "Gunicorn not found. Installing..."
+ pip install gunicorn
+fi
+
+# Check if Flask is installed
+if ! python3 -c "import flask" &> /dev/null; then
+ echo "Flask not found. Installing..."
+ pip install flask flask-cors
+fi
+
+echo "Starting ResLens Flamegraph Analysis Service with Gunicorn..."
+
+# Run with gunicorn
+gunicorn \
+ --config gunicorn.conf.py \
+ --bind 0.0.0.0:8080 \
+ --workers 1 \
+ --timeout 30 \
+ --log-level info \
+ reslens_tools_service:app
\ No newline at end of file