Skip to main content
Glama

compare_profiles

Compare two Scalene profiles to identify performance improvements and regressions after code optimization. Returns runtime and memory changes with a summary.

Instructions

Compare two profiles to measure optimization impact.

Args: before_id: Profile ID from original code after_id: Profile ID from optimized code

Returns: {runtime_change_pct, memory_change_pct, improvements, regressions, summary_text}

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
before_idYes
after_idYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The compare_profiles tool handler: takes before_id and after_id, validates they exist in recent_profiles, delegates to comparator.compare(), and returns the ProfileComparison as a dict.
    async def compare_profiles(
        before_id: str,
        after_id: str,
    ) -> dict[str, Any]:
        """Compare two profiles to measure optimization impact.
        
        Args:
            before_id: Profile ID from original code
            after_id: Profile ID from optimized code
            
        Returns: {runtime_change_pct, memory_change_pct, improvements, regressions, summary_text}
        """
        if before_id not in recent_profiles:
            raise ValueError(f"Profile not found: {before_id}")
        if after_id not in recent_profiles:
            raise ValueError(f"Profile not found: {after_id}")
    
        before = recent_profiles[before_id]
        after = recent_profiles[after_id]
    
        comparison = comparator.compare(before, after)
        return comparison.model_dump()
  • Registration of compare_profiles as an MCP tool via server.tool()
    server.tool(compare_profiles)
  • ProfileComparator.compare() - the core comparison logic between two ProfileResult objects. Computes runtime, memory, and CPU change percentages, identifies improvements and regressions, and returns a ProfileComparison model.
    def compare(
        self,
        before: ProfileResult,
        after: ProfileResult,
    ) -> ProfileComparison:
        """
        Compare two profiles to identify performance changes.
    
        Args:
            before: Profile from before optimization
            after: Profile from after optimization
    
        Returns:
            ProfileComparison with detailed change analysis
        """
        # Runtime comparison
        runtime_before = before.summary.elapsed_time_sec
        runtime_after = after.summary.elapsed_time_sec
        runtime_change_percent = (
            ((runtime_after - runtime_before) / runtime_before * 100)
            if runtime_before > 0
            else 0.0
        )
        runtime_improved = runtime_change_percent < 0
    
        # Memory comparison
        memory_before = before.summary.max_memory_mb
        memory_after = after.summary.max_memory_mb
        memory_change_percent = (
            ((memory_after - memory_before) / memory_before * 100)
            if memory_before > 0
            else 0.0
        )
        memory_improved = memory_change_percent < 0
    
        # CPU comparison
        cpu_before = before.summary.total_cpu_samples
        cpu_after = after.summary.total_cpu_samples
        cpu_change_percent = (
            ((cpu_after - cpu_before) / cpu_before * 100)
            if cpu_before > 0
            else 0.0
        )
        cpu_improved = cpu_change_percent < 0
    
        # Collect improvements and regressions
        improvements = self._identify_improvements(before, after)
        regressions = self._identify_regressions(before, after)
    
        # Generate summary
        summary = self._generate_comparison_summary(
            runtime_improved,
            memory_improved,
            cpu_improved,
            runtime_change_percent,
            memory_change_percent,
            improvements,
            regressions,
        )
    
        return ProfileComparison(
            before_id=before.profile_id or "unknown",
            after_id=after.profile_id or "unknown",
            runtime_before_sec=runtime_before,
            runtime_after_sec=runtime_after,
            runtime_change_percent=runtime_change_percent,
            runtime_improved=runtime_improved,
            memory_before_mb=memory_before,
            memory_after_mb=memory_after,
            memory_change_percent=memory_change_percent,
            memory_improved=memory_improved,
            cpu_before_samples=cpu_before,
            cpu_after_samples=cpu_after,
            cpu_change_percent=cpu_change_percent,
            cpu_improved=cpu_improved,
            improvements=improvements,
            regressions=regressions,
            overall_improved=(
                runtime_improved or memory_improved or cpu_improved
            )
            and len(regressions) == 0,
            summary_text=summary,
        )
  • ProfileComparison pydantic model - the output schema returned by compare_profiles. Contains before_id, after_id, runtime/memory/cpu metrics, improvements list, regressions list, overall_improved boolean, and summary_text.
    class ProfileComparison(BaseModel):
        """Comparison between two profiles."""
    
        before_id: str
        after_id: str
    
        # Runtime changes
        runtime_before_sec: float
        runtime_after_sec: float
        runtime_change_percent: float
        runtime_improved: bool
    
        # Memory changes
        memory_before_mb: float
        memory_after_mb: float
        memory_change_percent: float
        memory_improved: bool
    
        # CPU changes
        cpu_before_samples: int
        cpu_after_samples: int
        cpu_change_percent: float
        cpu_improved: bool
    
        # Detailed changes
        improvements: list[str] = Field(default_factory=list)
        regressions: list[str] = Field(default_factory=list)
    
        # Overall
        overall_improved: bool
        summary_text: str
  • Helper methods in ProfileComparator: _identify_improvements, _identify_regressions, _generate_comparison_summary - supporting logic used by compare()
    def _identify_improvements(
        self, before: ProfileResult, after: ProfileResult
    ) -> list[str]:
        """
        Identify specific improvements between profiles.
    
        Args:
            before: Profile from before optimization
            after: Profile from after optimization
    
        Returns:
            List of improvement descriptions
        """
        improvements = []
    
        # Runtime improvements
        runtime_change = (
            (after.summary.elapsed_time_sec - before.summary.elapsed_time_sec)
            / before.summary.elapsed_time_sec
            * 100
            if before.summary.elapsed_time_sec > 0
            else 0
        )
        if runtime_change < -5:  # At least 5% improvement
            improvements.append(
                f"Runtime improved by {abs(runtime_change):.1f}% "
                f"({before.summary.elapsed_time_sec:.2f}s → "
                f"{after.summary.elapsed_time_sec:.2f}s)"
            )
    
        # Memory improvements
        memory_change = (
            (after.summary.max_memory_mb - before.summary.max_memory_mb)
            / before.summary.max_memory_mb
            * 100
            if before.summary.max_memory_mb > 0
            else 0
        )
        if memory_change < -5:  # At least 5% improvement
            improvements.append(
                f"Peak memory reduced by {abs(memory_change):.1f}% "
                f"({before.summary.max_memory_mb:.1f}MB → "
                f"{after.summary.max_memory_mb:.1f}MB)"
            )
    
        # Leak improvements
        leaks_before = len(before.summary.detected_leaks)
        leaks_after = len(after.summary.detected_leaks)
        if leaks_after < leaks_before:
            improvements.append(
                f"Memory leaks reduced from {leaks_before} to {leaks_after}"
            )
    
        # Allocation improvements
        alloc_change = (
            (
                after.summary.total_allocations_mb
                - before.summary.total_allocations_mb
            )
            / before.summary.total_allocations_mb
            * 100
            if before.summary.total_allocations_mb > 0
            else 0
        )
        if alloc_change < -10:  # At least 10% improvement
            improvements.append(
                f"Total allocations reduced by {abs(alloc_change):.1f}%"
            )
    
        return improvements
    
    def _identify_regressions(
        self, before: ProfileResult, after: ProfileResult
    ) -> list[str]:
        """
        Identify performance regressions between profiles.
    
        Args:
            before: Profile from before optimization
            after: Profile from after optimization
    
        Returns:
            List of regression descriptions
        """
        regressions = []
    
        # Runtime regressions
        runtime_change = (
            (after.summary.elapsed_time_sec - before.summary.elapsed_time_sec)
            / before.summary.elapsed_time_sec
            * 100
            if before.summary.elapsed_time_sec > 0
            else 0
        )
        if runtime_change > 5:  # At least 5% regression
            regressions.append(
                f"⚠️ Runtime increased by {runtime_change:.1f}% "
                f"({before.summary.elapsed_time_sec:.2f}s → "
                f"{after.summary.elapsed_time_sec:.2f}s)"
            )
    
        # Memory regressions
        memory_change = (
            (after.summary.max_memory_mb - before.summary.max_memory_mb)
            / before.summary.max_memory_mb
            * 100
            if before.summary.max_memory_mb > 0
            else 0
        )
        if memory_change > 5:  # At least 5% regression
            regressions.append(
                f"⚠️ Peak memory increased by {memory_change:.1f}% "
                f"({before.summary.max_memory_mb:.1f}MB → "
                f"{after.summary.max_memory_mb:.1f}MB)"
            )
    
        # Leak regressions
        leaks_before = len(before.summary.detected_leaks)
        leaks_after = len(after.summary.detected_leaks)
        if leaks_after > leaks_before:
            regressions.append(
                f"⚠️ Memory leaks increased from {leaks_before} to {leaks_after}"
            )
    
        return regressions
    
    def _generate_comparison_summary(
        self,
        runtime_improved: bool,
        memory_improved: bool,
        cpu_improved: bool,
        runtime_change: float,
        memory_change: float,
        improvements: list[str],
        regressions: list[str],
    ) -> str:
        """
        Generate human-readable comparison summary.
    
        Args:
            runtime_improved: Whether runtime improved
            memory_improved: Whether memory improved
            cpu_improved: Whether CPU usage improved
            runtime_change: Runtime change percentage
            memory_change: Memory change percentage
            improvements: List of improvements
            regressions: List of regressions
    
        Returns:
            Formatted markdown summary
        """
        summary_parts = ["# Profile Comparison\n"]
    
        # Overall assessment
        if improvements and not regressions:
            summary_parts.append("✅ **Overall: Improved**\n\n")
        elif regressions and not improvements:
            summary_parts.append("⚠️ **Overall: Regressed**\n\n")
        elif improvements and regressions:
            summary_parts.append("⚡ **Overall: Mixed Results**\n\n")
        else:
            summary_parts.append("➡️ **Overall: No Significant Change**\n\n")
    
        # Key metrics
        summary_parts.append("## Key Metrics\n\n")
    
        runtime_emoji = "✅" if runtime_improved else "⚠️" if runtime_change > 5 else "➡️"
        summary_parts.append(
            f"{runtime_emoji} **Runtime**: {runtime_change:+.1f}%\n"
        )
    
        memory_emoji = "✅" if memory_improved else "⚠️" if memory_change > 5 else "➡️"
        summary_parts.append(
            f"{memory_emoji} **Memory**: {memory_change:+.1f}%\n\n"
        )
    
        # Improvements
        if improvements:
            summary_parts.append("## Improvements\n\n")
            for improvement in improvements:
                summary_parts.append(f"- {improvement}\n")
            summary_parts.append("\n")
    
        # Regressions
        if regressions:
            summary_parts.append("## Regressions\n\n")
            for regression in regressions:
                summary_parts.append(f"- {regression}\n")
            summary_parts.append("\n")
    
        # Recommendations
        if regressions:
            summary_parts.append("## Recommendations\n\n")
            summary_parts.append(
                "- Review changes that may have introduced regressions\n"
            )
            if any("Runtime" in r for r in regressions):
                summary_parts.append(
                    "- Profile CPU hotspots to identify performance bottlenecks\n"
                )
            if any("memory" in r.lower() for r in regressions):
                summary_parts.append(
                    "- Check for increased allocations or memory leaks\n"
                )
    
        return "".join(summary_parts)
    
    def get_file_changes(
        self, before: ProfileResult, after: ProfileResult, min_change_percent: float = 5.0
    ) -> dict[str, dict[str, Any]]:
        """
        Get per-file performance changes.
    
        Args:
            before: Profile from before optimization
            after: Profile from after optimization
            min_change_percent: Minimum change percentage to report
    
        Returns:
            Dictionary mapping filenames to their change metrics
        """
        changes: dict[str, dict[str, Any]] = {}
    
        # Get all files from both profiles
        all_files = set(before.files.keys()) | set(after.files.keys())
    
        for filename in all_files:
            before_metrics = before.files.get(filename)
            after_metrics = after.files.get(filename)
    
            if before_metrics and after_metrics:
                # File exists in both profiles
                cpu_change = (
                    (
                        after_metrics.total_cpu_percent
                        - before_metrics.total_cpu_percent
                    )
                    / before_metrics.total_cpu_percent
                    * 100
                    if before_metrics.total_cpu_percent > 0
                    else 0.0
                )
    
                if abs(cpu_change) >= min_change_percent:
                    changes[filename] = {
                        "cpu_before": before_metrics.total_cpu_percent,
                        "cpu_after": after_metrics.total_cpu_percent,
                        "cpu_change_percent": cpu_change,
                        "improved": cpu_change < 0,
                    }
            elif before_metrics and not after_metrics:
                # File removed (likely optimization)
                changes[filename] = {
                    "status": "removed",
                    "cpu_before": before_metrics.total_cpu_percent,
                }
            elif after_metrics and not before_metrics:
                # File added (potential regression)
                changes[filename] = {
                    "status": "added",
                    "cpu_after": after_metrics.total_cpu_percent,
                }
    
        return changes
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses that the tool compares two profiles (non-destructive, read operation) and returns a structured result with fields like runtime_change_pct and memory_change_pct. It does not discuss permissions, errors, or side effects, but the nature of the tool suggests no destructive behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and well-structured: a one-sentence purpose statement followed by parameter descriptions and return fields. Every sentence adds value, and it is front-loaded with the core purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (comparison with multiple metrics) and the existence of an output schema, the description adequately covers the purpose, parameters, and return structure. It does not explain all fields in detail, but the output schema presumably provides those details.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It adds meaning by labeling 'before_id' as from original code and 'after_id' from optimized code. However, it does not specify format, constraints (e.g., must exist), or validation rules beyond the schema types.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Compare two profiles to measure optimization impact.' It uses a specific verb ('compare') and resource ('profiles'), and it distinguishes itself from sibling tools like 'profile' (which likely runs profiling) and 'list_profiles' (which lists profiles).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use it (after obtaining two profile IDs, typically before and after optimization) and provides parameter roles ('before_id from original code', 'after_id from optimized code'). It does not explicitly state when not to use or mention alternatives, but the context from sibling names helps differentiate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ptmorris05/scalene-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server