hotpath 0.15.0

Simple async Rust profiler with memory and data-flow insights - quickly find and debug performance bottlenecks.
Documentation
use eyre::Result;
use hotpath::ProfilingMode;
use serde::Deserialize;
use serde_json::json;

#[derive(Debug, Deserialize)]
struct GitHubUser {
    login: String,
    #[serde(rename = "type")]
    user_type: String,
}

#[derive(Debug, Deserialize)]
struct GitHubComment {
    id: u64,
    body: String,
    user: GitHubUser,
}

fn find_existing_comment(
    repo: &str,
    pr_number: &str,
    token: &str,
    profiling_mode: &ProfilingMode,
    benchmark_id: Option<&str>,
) -> Result<Option<u64>> {
    let url = format!(
        "https://api.github.com/repos/{}/issues/{}/comments",
        repo, pr_number
    );

    let response = ureq::get(&url)
        .header("Authorization", &format!("token {}", token))
        .header("Accept", "application/vnd.github.v3+json")
        .header("User-Agent", "hotpath-utils-action")
        .call();

    match response {
        Ok(mut resp) => {
            let comments: Vec<GitHubComment> = resp.body_mut().read_json()?;

            let search_marker = match benchmark_id {
                Some(id) => format!("**Benchmark ID:** {}", id),
                None => format!("**Profiling Mode:** {}", profiling_mode),
            };

            for comment in comments {
                if comment.user.user_type == "Bot"
                    && comment.user.login == "github-actions[bot]"
                    && comment.body.contains(&search_marker)
                {
                    return Ok(Some(comment.id));
                }
            }

            Ok(None)
        }
        Err(e) => {
            println!("Warning: Failed to fetch existing comments: {}", e);
            Ok(None)
        }
    }
}

fn create_comment(repo: &str, pr_number: &str, token: &str, body: &str) -> Result<()> {
    let url = format!(
        "https://api.github.com/repos/{}/issues/{}/comments",
        repo, pr_number
    );

    let comment_body = json!({
        "body": body,
    });

    let response = ureq::post(&url)
        .header("Authorization", &format!("token {}", token))
        .header("Accept", "application/vnd.github.v3+json")
        .header("User-Agent", "hotpath-utils-action")
        .send_json(&comment_body)?;

    let status = response.status();
    if status.is_success() {
        println!("Successfully created new comment");
        Ok(())
    } else {
        let error_text = response.into_body().read_to_string()?;
        println!("Failed to create comment: {}", status);
        println!("Error details: {}", error_text);
        if status.as_u16() == 403 {
            println!("This is likely a permissions issue. Make sure the workflow has:");
            println!("permissions:");
            println!("  pull-requests: write");
            println!("  contents: read");
        }
        Err(eyre::eyre!("Failed to create comment"))
    }
}

fn update_comment(repo: &str, comment_id: u64, token: &str, body: &str) -> Result<()> {
    let url = format!(
        "https://api.github.com/repos/{}/issues/comments/{}",
        repo, comment_id
    );

    let comment_body = json!({
        "body": body,
    });

    let response = ureq::patch(&url)
        .header("Authorization", &format!("token {}", token))
        .header("Accept", "application/vnd.github.v3+json")
        .header("User-Agent", "hotpath-utils-action")
        .send_json(&comment_body)?;

    let status = response.status();
    if status.is_success() {
        println!("Successfully updated existing comment");
        Ok(())
    } else {
        let error_text = response.into_body().read_to_string()?;
        println!("Failed to update comment: {}", status);
        println!("Error details: {}", error_text);
        Err(eyre::eyre!("Failed to update comment"))
    }
}

pub fn upsert_pr_comment(
    repo: &str,
    pr_number: &str,
    token: &str,
    body: &str,
    profiling_mode: &ProfilingMode,
    benchmark_id: Option<&str>,
) -> Result<()> {
    match find_existing_comment(repo, pr_number, token, profiling_mode, benchmark_id) {
        Ok(Some(comment_id)) => {
            println!(
                "Found existing comment (id: {}) for profiling mode: {}",
                comment_id, profiling_mode
            );
            update_comment(repo, comment_id, token, body)
        }
        Ok(None) => {
            println!("No existing comment found, creating new comment");
            create_comment(repo, pr_number, token, body)
        }
        Err(e) => {
            println!("Error searching for existing comment: {}", e);
            println!("Falling back to creating new comment");
            create_comment(repo, pr_number, token, body)
        }
    }
}