Skip to content

823: replace timer() with Timed#838

Open
luoh00 wants to merge 4 commits intomainfrom
823
Open

823: replace timer() with Timed#838
luoh00 wants to merge 4 commits intomainfrom
823

Conversation

@luoh00
Copy link
Collaborator

@luoh00 luoh00 commented Mar 6, 2026

823

Description of changes

Removed timer() in LeetcodeClientImpl and LeetcodeAuthStealer and replaced with Spring Timed annotation

Checklist before review

  • I have done a thorough self-review of the PR
  • Copilot has reviewed my latest changes, and all comments have been fixed and/or closed.
  • If I have made database changes, I have made sure I followed all the db repo rules listed in the wiki here. (check if no db changes)
  • All tests have passed
  • I have successfully deployed this PR to staging
  • I have done manual QA in both dev (and staging if possible) and attached screenshots below.

Screenshots

Dev

image

Staging

@luoh00
Copy link
Collaborator Author

luoh00 commented Mar 6, 2026

/deploy

@github-actions
Copy link
Contributor

github-actions bot commented Mar 6, 2026

Available PR Commands

  • /ai - Triggers all AI review commands at once
  • /review - AI review of the PR changes
  • /describe - AI-powered description of the PR
  • /improve - AI-powered suggestions
  • /deploy - Deploy to staging

See: https://github.com/tahminator/codebloom/wiki/CI-Commands

@github-actions
Copy link
Contributor

github-actions bot commented Mar 6, 2026

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Metrics Verification

Ensure that the @Timed annotation correctly captures and reports metrics for all annotated methods, replicating the functionality of the previously removed manual timer().record() calls. Verify that the metric names and tags are consistent with the intended monitoring setup.

@Timed(value = TIMED_METRIC_NAME)
public LeetcodeQuestion findQuestionBySlug(final String slug) {
    String requestBody;
    try {
        requestBody = SelectProblemQuery.body(slug);
    } catch (Exception e) {
        throw new RuntimeException("Error building the request body");
    }

    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(requestBody))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException("API Returned status " + statusCode + ": " + body);
        }

        JsonNode node = mapper.readTree(body);

        int questionId =
                node.path("data").path("question").path("questionId").asInt();
        String questionTitle =
                node.path("data").path("question").path("title").asText();
        String titleSlug =
                node.path("data").path("question").path("titleSlug").asText();
        String link = "https://leetcode.com/problems/" + titleSlug;
        String difficulty =
                node.path("data").path("question").path("difficulty").asText();
        String question = node.path("data").path("question").path("content").asText();

        String statsJson = node.path("data").path("question").path("stats").asText();
        JsonNode stats = mapper.readTree(statsJson);
        String acRateString = stats.get("acRate").asText();
        float acRate = Float.parseFloat(acRateString.replace("%", "")) / 100f;

        JsonNode topicTagsNode = node.path("data").path("question").path("topicTags");

        List<LeetcodeTopicTag> tags = new ArrayList<>();

        for (JsonNode el : topicTagsNode) {
            tags.add(LeetcodeTopicTag.builder()
                    .name(el.get("name").asText())
                    .slug(el.get("slug").asText())
                    .build());
        }

        return LeetcodeQuestion.builder()
                .link(link)
                .questionId(questionId)
                .questionTitle(questionTitle)
                .titleSlug(titleSlug)
                .difficulty(difficulty)
                .question(question)
                .acceptanceRate(acRate)
                .topics(tags)
                .build();
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error fetching the API", e);
    }
}

@Override
public ArrayList<LeetcodeSubmission> findSubmissionsByUsername(final String username) {
    return findSubmissionsByUsername(username, 20);
}

@Override
@Timed(value = TIMED_METRIC_NAME)
public ArrayList<LeetcodeSubmission> findSubmissionsByUsername(final String username, final int limit) {
    ArrayList<LeetcodeSubmission> submissions = new ArrayList<>();

    String requestBody;
    try {
        requestBody = SelectAcceptedSubmisisonsQuery.body(username, limit);
    } catch (Exception e) {
        throw new RuntimeException("Error building the request body");
    }

    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(requestBody))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException("API Returned status " + statusCode + ": " + body);
        }

        JsonNode node = mapper.readTree(body);
        JsonNode submissionsNode = node.path("data").path("recentAcSubmissionList");

        if (submissionsNode.isArray()) {
            if (submissionsNode.isEmpty() || submissionsNode == null) {
                return submissions;
            }

            for (JsonNode submission : submissionsNode) {
                int id = submission.path("id").asInt();
                String title = submission.path("title").asText();
                String titleSlug = submission.path("titleSlug").asText();
                String timestampString = submission.path("timestamp").asText();
                long epochSeconds = Long.parseLong(timestampString);
                Instant instant = Instant.ofEpochSecond(epochSeconds);

                LocalDateTime timestamp = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
                String statusDisplay = submission.path("statusDisplay").asText();
                submissions.add(new LeetcodeSubmission(id, title, titleSlug, timestamp, statusDisplay));
            }
        }

        return submissions;
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error fetching the API", e);
    }
}

@Override
@Timed(value = TIMED_METRIC_NAME)
public LeetcodeDetailedQuestion findSubmissionDetailBySubmissionId(final int submissionId) {
    String requestBody;
    try {
        requestBody = GetSubmissionDetails.body(submissionId);
    } catch (Exception e) {
        throw new RuntimeException("Error building the request body");
    }

    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(requestBody))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException("API Returned status " + statusCode + ": " + body);
        }

        JsonNode node = mapper.readTree(body);
        JsonNode baseNode = node.path("data").path("submissionDetails");

        int runtime = baseNode.path("runtime").asInt();
        String runtimeDisplay = baseNode.path("runtimeDisplay").asText();
        float runtimePercentile = (float) baseNode.path("runtimePercentile").asDouble();
        int memory = baseNode.path("memory").asInt();
        String memoryDisplay = baseNode.path("memoryDisplay").asText();
        float memoryPercentile = (float) baseNode.path("memoryPercentile").asDouble();
        String code = baseNode.path("code").asText();
        String langName = baseNode.path("lang").path("name").asText();
        String langVerboseName = baseNode.path("lang").path("verboseName").asText();
        Lang lang = (Strings.isNullOrEmpty(langName) || Strings.isNullOrEmpty(langVerboseName))
                ? null
                : new Lang(langName, langVerboseName);

        // if any of these are empty, then extremely likely that we're throttled.
        if (Strings.isNullOrEmpty(runtimeDisplay) || Strings.isNullOrEmpty(memoryDisplay)) {
            leetcodeAuthStealer.reloadCookie();
        }

        LeetcodeDetailedQuestion question = new LeetcodeDetailedQuestion(
                runtime, runtimeDisplay, runtimePercentile, memory, memoryDisplay, memoryPercentile, code, lang);

        return question;
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error fetching the API", e);
    }
}

@Timed(value = TIMED_METRIC_NAME)
public POTD getPotd() {
    String requestBody;
    try {
        requestBody = GetPotd.body();
    } catch (Exception e) {
        throw new RuntimeException("Error building the request body");
    }

    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(requestBody))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException("API Returned status " + statusCode + ": " + body);
        }

        JsonNode node = mapper.readTree(body);
        JsonNode baseNode =
                node.path("data").path("activeDailyCodingChallengeQuestion").path("question");

        String titleSlug = baseNode.path("titleSlug").asText();
        String title = baseNode.path("title").asText();
        var difficulty =
                QuestionDifficulty.valueOf(baseNode.path("difficulty").asText());

        return new POTD(title, titleSlug, difficulty);
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error fetching the API", e);
    }
}

@Override
@Timed(value = TIMED_METRIC_NAME)
public UserProfile getUserProfile(final String username) {
    String requestBody;
    try {
        requestBody = GetUserProfile.body(username);
    } catch (Exception e) {
        throw new RuntimeException("Error building the request body", e);
    }

    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(requestBody))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException("API Returned status " + statusCode + ": " + body);
        }

        JsonNode node = mapper.readTree(body);
        JsonNode baseNode = node.path("data").path("matchedUser");

        var returnedUsername = baseNode.path("username").asText();
        var ranking = baseNode.path("profile").path("ranking").asText();
        var userAvatar = baseNode.path("profile").path("userAvatar").asText();
        var realName = baseNode.path("profile").path("realName").asText();
        var aboutMe = baseNode.path("profile").path("aboutMe").asText().trim();

        return new UserProfile(returnedUsername, ranking, userAvatar, realName, aboutMe);
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error fetching the API", e);
    }
}

@Override
@Timed(value = TIMED_METRIC_NAME)
public Set<LeetcodeTopicTag> getAllTopicTags() {
    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(GetTopics.body()))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException(
                    "Non-successful response getting topics from Leetcode API. Status code: " + statusCode);
        }

        JsonNode json = mapper.readTree(body);
        JsonNode edges = json.path("data").path("questionTopicTags").path("edges");

        if (!edges.isArray()) {
            throw new RuntimeException("The expected shape of getting topics did not match the received body");
        }

        Set<LeetcodeTopicTag> result = new HashSet<>();

        for (JsonNode edge : edges) {
            JsonNode node = edge.path("node");
            result.add(LeetcodeTopicTag.builder()
                    .name(node.get("name").asText())
                    .slug(node.get("slug").asText())
                    .build());
        }

        return result;
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error getting topics from Leetcode API", e);
    }
}

@Timed(value = TIMED_METRIC_NAME)
public List<LeetcodeQuestion> getAllProblems() {
    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(GetAllProblems.body()))
                .build();
        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();
        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException(
                    "Non-successful response getting all questions from Leetcode API. Status code: " + statusCode);
        }

        JsonNode json = mapper.readTree(body);
        JsonNode allQuestions =
                json.path("data").path("problemsetQuestionListV2").path("questions");

        if (!allQuestions.isArray()) {
            throw new RuntimeException("The expected shape of getting topics did not match the received body");
        }

        List<LeetcodeQuestion> result = new ArrayList<>();
        for (JsonNode question : allQuestions) {
            JsonNode topicTags = question.get("topicTags");

            List<LeetcodeTopicTag> tags = new ArrayList<>();
            for (JsonNode tag : topicTags) {
                tags.add(LeetcodeTopicTag.builder()
                        .name(tag.get("name").asText())
                        .slug(tag.get("slug").asText())
                        .build());
            }

            result.add(LeetcodeQuestion.builder()
                    .link("https://leetcode.com/problems/"
                            + question.get("titleSlug").asText())
                    .questionId(question.get("questionFrontendId").asInt())
                    .questionTitle(question.get("title").asText())
                    .titleSlug(question.get("titleSlug").asText())
                    .difficulty(question.get("difficulty").asText())
                    .acceptanceRate((float) question.get("acRate").asDouble())
                    .topics(tags)
                    .build());
        }
        return result;
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error getting all problems from Leetcode API", e);
    }
Metrics Verification

Confirm that the @Timed annotation on methods within LeetcodeAuthStealer accurately records execution times, replacing the previous manual timing mechanism. Pay attention to the stealAuthCookie() method, which is a scheduled task, to ensure its timing is correctly captured.

@Timed(value = METRIC_NAME)
public void stealAuthCookie() {
    boolean acquired = LOCK.writeLock().tryLock();
    if (!acquired) {
        log.info("Lock failed to be acquired, bouncing...");
        return;
    }

    try {
        Auth mostRecentAuth = authRepository.getMostRecentAuth();

        // The auth token should be refreshed every 4 hours.
        if (mostRecentAuth != null
                && mostRecentAuth
                        .getCreatedAt()
                        .isAfter(StandardizedOffsetDateTime.now().minus(4, ChronoUnit.HOURS))) {
            log.info("Auth token already exists, using token from database.");
            cookie = mostRecentAuth.getToken();
            csrf = mostRecentAuth.getCsrf();
            return;
        }

        log.info("falling back to checking redis client...");
        Optional<String> authToken = redisClient.getAuth();

        log.info("auth token in redis = {}", authToken.isPresent());

        if (authToken.isPresent()) {
            log.info("auth token found in redis client");
            cookie = authToken.get();
            csrf = null; // don't care in ci.
            return;
        }

        log.info("auth token not found in redis client");
        log.info("Auth token is missing/expired. Attempting to receive token...");

        stealCookieImpl();
    } finally {
        LOCK.writeLock().unlock();
    }
}

/**
 * There are some cases where leetcode.com may not respect the token anymore. If that is the case, it is best to try
 * to steal a new cookie and replace the current one.
 *
 * <p>You may await the `CompletableFuture` and receive the brand new token, or call-and-forget.
 */
@Async
@Timed(value = METRIC_NAME)
public CompletableFuture<Optional<String>> reloadCookie() {
    boolean acquired = LOCK.writeLock().tryLock();
    if (!acquired) {
        log.info("Lock failed to be acquired, bouncing...");
        return CompletableFuture.completedFuture(Optional.empty());
    }

    try {
        return CompletableFuture.completedFuture(Optional.ofNullable(stealCookieImpl()));
    } finally {
        LOCK.writeLock().unlock();
    }
}

@Timed(value = METRIC_NAME)
public String getCookie() {
    LOCK.readLock().lock();
    try {
        return cookie;
    } finally {
        LOCK.readLock().unlock();
    }
}

/**
 * It's fine if this is null for some requests; it isn't a requirement to fetch data from the GraphQL layer of
 * leetcode.com
 */
@Timed(value = METRIC_NAME)
public String getCsrf() {
    if (csrf == null && !reported) {
        reported = true;
        reporter.log(
                "getCsrf",
                Report.builder()
                        .environments(env.getActiveProfiles())
                        .location(Location.BACKEND)
                        .data(
                                "CSRF token is missing inside of LeetcodeAuthStealer. This may be something to look into.")
                        .build());
    }

    return csrf;
}

@Timed(value = METRIC_NAME)
String stealCookieImpl() {
    Optional<Auth> auth = playwrightClient.getLeetcodeCookie(githubUsername, githubPassword);
    if (auth.isPresent()) {
        var a = auth.get();
        this.csrf = a.getCsrf();
        this.cookie = a.getToken();
        redisClient.setAuth(a.getToken(), 4, ChronoUnit.HOURS);
        log.info("auth token stored in redis");
        this.authRepository.createAuth(Auth.builder()
                .csrf(a.getCsrf())
                .token(a.getToken())
                .createdAt(StandardizedOffsetDateTime.now())
                .build());
        return cookie;
    }
    return null;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant