telemetry: pluggable Unity sender; add MCP_DISABLE_TELEMETRY; server reads version file; locks for milestones

main
David Sarno 2025-09-08 20:45:45 -07:00
parent f6a5568865
commit 2abca24e9d
4 changed files with 66 additions and 21 deletions

View File

@ -12,6 +12,7 @@ namespace MCPForUnity.Editor.Helpers
{
private const string TELEMETRY_DISABLED_KEY = "MCPForUnity.TelemetryDisabled";
private const string CUSTOMER_UUID_KEY = "MCPForUnity.CustomerUUID";
private static Action<Dictionary<string, object>> s_sender;
/// <summary>
/// Check if telemetry is enabled (can be disabled via Environment Variable or EditorPrefs)
@ -34,7 +35,15 @@ namespace MCPForUnity.Editor.Helpers
{
return false;
}
// Honor protocol-wide opt-out as well
var mcpDisable = Environment.GetEnvironmentVariable("MCP_DISABLE_TELEMETRY");
if (!string.IsNullOrEmpty(mcpDisable) &&
(mcpDisable.Equals("true", StringComparison.OrdinalIgnoreCase) || mcpDisable == "1"))
{
return false;
}
// Check EditorPrefs
return !UnityEditor.EditorPrefs.GetBool(TELEMETRY_DISABLED_KEY, false);
}
@ -110,6 +119,14 @@ namespace MCPForUnity.Editor.Helpers
}
}
/// <summary>
/// Allows the bridge to register a concrete sender for telemetry payloads.
/// </summary>
public static void RegisterTelemetrySender(Action<Dictionary<string, object>> sender)
{
s_sender = sender;
}
/// <summary>
/// Record bridge startup event
/// </summary>
@ -162,15 +179,28 @@ namespace MCPForUnity.Editor.Helpers
private static void SendTelemetryToPythonServer(Dictionary<string, object> telemetryData)
{
// This would integrate with the existing bridge communication system
// For now, we'll just log it when debug is enabled
var sender = s_sender;
if (sender != null)
{
try
{
sender(telemetryData);
return;
}
catch (Exception e)
{
if (IsDebugEnabled())
{
Debug.LogWarning($"Telemetry sender error (non-blocking): {e.Message}");
}
}
}
// Fallback: log when debug is enabled
if (IsDebugEnabled())
{
Debug.Log($"<b><color=#2EA3FF>MCP-TELEMETRY</color></b>: {telemetryData["event_type"]}");
}
// TODO: Integrate with MCPForUnityBridge command system
// We would send this as a special telemetry command to the Python server
}
private static bool IsDebugEnabled()

View File

@ -0,0 +1 @@
3.3.0

View File

@ -27,8 +27,15 @@ async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
# Record server startup telemetry
start_time = time.time()
start_clk = time.perf_counter()
try:
from pathlib import Path
ver_path = Path(__file__).parent / "server-version.txt"
server_version = ver_path.read_text(encoding="utf-8").strip()
except Exception:
server_version = "unknown"
record_telemetry(RecordType.STARTUP, {
"server_version": "3.0.2",
"server_version": server_version,
"startup_time": start_time
})
@ -45,15 +52,23 @@ async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
"connection_time_ms": (time.time() - start_time) * 1000
})
except Exception as e:
logger.warning(f"Could not connect to Unity on startup: {str(e)}")
except ConnectionError as e:
logger.warning("Could not connect to Unity on startup: %s", e)
_unity_connection = None
# Record connection failure
record_telemetry(RecordType.UNITY_CONNECTION, {
"status": "failed",
"error": str(e)[:200],
"connection_time_ms": (time.time() - start_time) * 1000
"connection_time_ms": (time.perf_counter() - start_clk) * 1000
})
except Exception as e:
logger.warning("Unexpected error connecting to Unity on startup: %s", e)
_unity_connection = None
record_telemetry(RecordType.UNITY_CONNECTION, {
"status": "failed",
"error": str(e)[:200],
"connection_time_ms": (time.perf_counter() - start_clk) * 1000
})
try:

View File

@ -157,6 +157,7 @@ class TelemetryCollector:
self.config = TelemetryConfig()
self._customer_uuid: Optional[str] = None
self._milestones: Dict[str, Dict[str, Any]] = {}
self._lock: threading.Lock = threading.Lock()
self._load_persistent_data()
def _load_persistent_data(self):
@ -199,18 +200,16 @@ class TelemetryCollector:
"""Record a milestone event, returns True if this is the first occurrence"""
if not self.config.enabled:
return False
milestone_key = milestone.value
if milestone_key in self._milestones:
return False # Already recorded
milestone_data = {
"timestamp": time.time(),
"data": data or {}
}
self._milestones[milestone_key] = milestone_data
self._save_milestones()
with self._lock:
if milestone_key in self._milestones:
return False # Already recorded
milestone_data = {
"timestamp": time.time(),
"data": data or {},
}
self._milestones[milestone_key] = milestone_data
self._save_milestones()
# Also send as telemetry record
self.record(