Skip to content

Conversation

@huangdijia
Copy link
Contributor

@huangdijia huangdijia commented Nov 20, 2025

Summary

  • Add server.address and server.port attributes to Redis tracing data for better observability
  • Support cluster and sentinel configurations with proper address extraction
  • Add ConnectionContainer utility class using WeakMap for tracking connection configurations
  • Add RedisConnectionAspect for capturing connection configurations (currently commented out in ConfigProvider)

Test plan

  • Verify Redis tracing includes server address information
  • Test with standalone Redis configuration
  • Test with Redis cluster configuration
  • Test with Redis sentinel configuration

Summary by CodeRabbit

  • 新功能
    • Redis 连接追踪新增对 Redis 服务地址与端口的采集与上报。
  • 改进
    • 在连接建立阶段增强追踪上下文填充,提升 Redis 相关性能问题与网络故障的诊断能力。

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

Walkthrough

将 Redis 连接信息纳入分布式追踪:新增 RedisConnectionAspect 拦截 Redis 连接调用以写入追踪上下文;新增两个常量用于存储地址与端口;事件监听器在生成 Redis 追踪跨度时读取并附加 server.address 与 server.port 字段;并在配置提供者中注册该切面。

Changes

内聚体 / 文件(s) 变更摘要
配置提供者
src/sentry/src/ConfigProvider.php
Tracing\Aspect\RedisConnectionAspect::class 加入返回的活跃 aspects 列表以启用该切面
Redis连接切面(新增)
src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php
新增类 FriendsOfHyperf\Sentry\Tracing\Aspect\RedisConnectionAspect:拦截 Hyperf\Redis\RedisConnection::__call,在调用返回后提取 Redis 主机/端口并写入追踪 Context(支持 Redis 实例;RedisCluster 留有 TODO)
事件处理监听器
src/sentry/src/Tracing/Listener/EventHandleListener.php
在 Redis 追踪跨度数据中添加 server.addressserver.port 字段,分别从 Context 常量读取(默认 'localhost' 和 6379)
追踪常量
src/sentry/src/Constants.php
新增常量 TRACE_REDIS_SERVER_ADDRESSTRACE_REDIS_SERVER_PORT,键值分别为 'sentry.tracing.redis.server.address''sentry.tracing.redis.server.port'

Sequence Diagram(s)

sequenceDiagram
    participant App as 应用代码
    participant Hyperf as Hyperf Redis
    participant Aspect as RedisConnectionAspect
    participant Context as Trace Context
    participant Listener as EventHandleListener
    Note over App,Hyperf: Redis 调用发生
    App->>Hyperf: 调用 RedisConnection::__call(...)
    Hyperf->>Aspect: 切面拦截(Around)
    Aspect->>Hyperf: proceed()(执行原始调用)
    Hyperf-->>Aspect: 返回 Redis 实例
    Aspect->>Context: 写入 TRACE_REDIS_SERVER_ADDRESS / TRACE_REDIS_SERVER_PORT
    Note right of Context: 保存主机与端口信息
    Listener->>Context: 读取 TRACE_REDIS_SERVER_ADDRESS / TRACE_REDIS_SERVER_PORT
    Listener->>Listener: 将 server.address/server.port 附加到 Redis span 数据
    Listener-->>App: 追踪跨度包含地址与端口
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 分钟

  • 需要重点检查 RedisConnectionAspect 中对 Redis / RedisCluster 的类型判断与主机端口提取逻辑(边界情况、默认值)。
  • 验证 EventHandleListener 新增字段与 Constants 的对应性与默认值处理。
  • 确认 ConfigProvider 中将切面加入 aspects 列表的意图(是否应可配置或有环境依赖)。

Suggested reviewers

  • guandeng

诗歌

🐰 我绕着键盘轻轻蹦,
地址与端口悄悄藏,
切面拦住了那一瞬,
追踪足迹有章法,
小胡萝卜为变更欢唱 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确地总结了本变更的主要目的:为Redis连接添加服务器地址追踪功能。
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/redis-connection-tracing

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PHPStan (2.1.32)

At least one path must be specified to analyse.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@huangdijia huangdijia marked this pull request as draft November 20, 2025 06:10
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/sentry/src/Tracing/Listener/EventHandleListener.php (1)

505-505: 考虑过滤掉 null 值以避免污染 span 数据。

当配置键缺失时,$serverData 可能包含 null 值(例如 ['server.address' => null, 'server.port' => null])。将这些 null 值传播到 Sentry span 数据中可能导致:

  1. 数据噪音(无意义的 null 属性)
  2. 潜在的 Sentry SDK 处理问题

建议在展开前过滤 null 值:

+                    ...array_filter($serverData, fn($value) => $value !== null),
-                    ...$serverData,

或者在构建 serverData 时使用条件逻辑:

-$serverData = match (true) {
-    $config['cluster']['enable'] ?? false => ['server.address' => $config['cluster']['nodes'] ?? null],
-    $config['sentinel']['enable'] ?? false => ['server.address' => $config['sentinel']['nodes'] ?? null],
-    default => ['server.address' => $config['host'] ?? null, 'server.port' => $config['port'] ?? null],
-};
+$serverData = [];
+if ($config['cluster']['enable'] ?? false) {
+    if (isset($config['cluster']['nodes'])) {
+        $serverData['server.address'] = $config['cluster']['nodes'];
+    }
+} elseif ($config['sentinel']['enable'] ?? false) {
+    if (isset($config['sentinel']['nodes'])) {
+        $serverData['server.address'] = $config['sentinel']['nodes'];
+    }
+} else {
+    if (isset($config['host'])) {
+        $serverData['server.address'] = $config['host'];
+    }
+    if (isset($config['port'])) {
+        $serverData['server.port'] = $config['port'];
+    }
+}
src/sentry/src/Util/ConnectionContainer.php (1)

20-48: LGTM - WeakMap 实现正确且高效。

ConnectionContainer 的实现很好地利用了 WeakMap 的特性:

优点:

  1. 使用 WeakMap 防止内存泄漏:连接对象销毁时自动清理映射
  2. 延迟初始化 $mapping,避免不必要的内存分配
  3. PHPDoc 泛型提供类型安全
  4. API 简洁明了

可选的增强建议:

可以考虑添加 has() 方法以便检查键是否存在(区分"值为 null"和"键不存在"):

/**
 * @param TKey $object
 */
public static function has(object $object): bool
{
    if (self::$mapping === null) {
        return false;
    }

    return isset(self::$mapping[$object]);
}

这在调试时可能有用,但对当前用例不是必需的。

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41ce93b and 31049e6.

📒 Files selected for processing (4)
  • src/sentry/src/ConfigProvider.php (1 hunks)
  • src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (1 hunks)
  • src/sentry/src/Tracing/Listener/EventHandleListener.php (2 hunks)
  • src/sentry/src/Util/ConnectionContainer.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (1)
src/sentry/src/Util/ConnectionContainer.php (2)
  • ConnectionContainer (20-49)
  • set (28-35)
src/sentry/src/Util/ConnectionContainer.php (1)
src/support/src/Once.php (1)
  • value (54-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: Test on PHP 8.2 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.1 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.1 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.2 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.3 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.3 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.1 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.2 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.1 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.3 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.2 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.3 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
🔇 Additional comments (3)
src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (2)

22-24: LGTM - 切面目标定义正确。

使用通配符模式 create* 可以有效拦截所有 Redis 连接创建方法(createRedis、createRedisCluster、createRedisSentinel)。


28-30: 私有属性访问模式虽然必要,但需妥善防护

第 30 行通过闭包绑定访问 RedisConnection 的私有 $config 属性。验证确认 Hyperf\Redis\RedisConnection 不提供公共方法获取连接配置,因此这种访问方式是必要的。

但是,该模式仍存在风险:依赖 Hyperf 的私有实现细节,如果未来版本中 Hyperf 重构或重命名该属性,此代码将失效。

建议采取以下防护措施:

  1. 添加清晰注释说明这是必要的 hack 及其原因
  2. 监控 Hyperf 版本更新,及时跟进可能的 API 变化
+        // 访问私有 $config 属性来获取连接配置
+        // 必要原因:Hyperf\Redis\RedisConnection 不提供公共 API
+        // 风险:如果 Hyperf 在未来版本中移除或重命名该属性,此代码需要调整
         $config = (fn () => $this->config ?? [])->call($instance);
src/sentry/src/Tracing/Listener/EventHandleListener.php (1)

467-473: 原始审查意见基于对代码架构的误解,建议的改动实际上无法实现。

原始审查建议使用 ConnectionContainer 从事件处理器中获取配置,但这在技术上存在根本问题:

  1. 事件只提供连接名称字符串CommandExecuted 事件的 $event->connectionName 是字符串,不是连接对象实例
  2. ConnectionContainer 使用对象作为 WeakMap 的键:在 RedisConnectionAspect 中,ConnectionContainer::set() 存储的是连接对象实例,而不是字符串
  3. 无法通过字符串查询ConnectionContainer::get() 期望接收原始对象实例作为键,而不是连接名称字符串。整个代码库中从未成功调用过 ConnectionContainer::get() 来检索值
  4. 当前实现是正确的:直接从配置中按连接名称读取(第 468 行)是唯一可行的方法

ConnectionContainer 的设计目的是在连接创建期间存储每个连接实例的配置,而不是在事件处理时通过连接名称进行检索。

Likely an incorrect or invalid review comment.

- Add server.address and server.port to Redis tracing data
- Support cluster and sentinel configurations
- Add ConnectionContainer utility for WeakMap-based connection tracking
- Add RedisConnectionAspect for connection configuration capture (commented out in config)
@huangdijia huangdijia force-pushed the feat/redis-connection-tracing branch from 02d62e2 to 238fa35 Compare November 21, 2025 03:35
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (2)

36-39: 考虑上下文设置的时机优化。

如果此切面在每次 Redis 命令执行时触发,则会在每次操作时重复设置相同的服务器地址和端口上下文信息。这可能导致不必要的开销。

建议评估是否可以仅在连接建立时设置一次上下文,或者使用连接对象本身作为键来存储配置信息,避免重复设置。


41-43: RedisCluster 支持待实现。

当前 RedisCluster 的处理逻辑为空。根据 PR 目标,需要添加对集群和哨兵配置的支持,包括适当的地址提取逻辑。

对于 RedisCluster,可能需要提取多个节点的地址信息或主节点信息。

如需帮助实现 RedisCluster 的地址提取逻辑,我可以协助生成相关代码。是否需要我创建一个新的 issue 来跟踪此任务?

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 238fa35 and db8c96e.

📒 Files selected for processing (3)
  • src/sentry/src/Constants.php (1 hunks)
  • src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (1 hunks)
  • src/sentry/src/Tracing/Listener/EventHandleListener.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/sentry/src/Tracing/Listener/EventHandleListener.php
🧰 Additional context used
🧬 Code graph analysis (1)
src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (1)
src/sentry/src/Constants.php (1)
  • Constants (14-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.2 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.1 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.3 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.1 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.2 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.3 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.1 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.1 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.2 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.3 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.2 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.3 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
🔇 Additional comments (4)
src/sentry/src/Constants.php (1)

18-21: 常量定义符合预期。

新增的 Redis 服务器地址和端口追踪常量与现有的 RPC 追踪常量保持一致,命名清晰,遵循了项目的命名约定。

src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (3)

23-46: 确认切面的激活计划。

根据 PR 说明,此切面当前在 ConfigProvider 中被注释掉。在完成开发和测试后,请记得在配置中启用此切面以使其生效。


33-34: RedisConnection 属性验证已确认——代码正确。

验证结果表明,RedisConnection 类确实包含这两个属性,且它们被声明为 受保护的属性 而非私有属性:

  • 第 48 行:protected null|Redis|RedisCluster $connection = null;
  • 第 51 行:protected array $config = [...]

通过闭包绑定访问受保护属性是 PHP 中的有效模式,代码使用 (fn () => $this->config ?? [])->call($redisConnection) 正确地绑定闭包上下文以访问这些属性。实现方式正确,属性确实存在。


25-27: 验证拦截点设计是否最优,并注意切面当前处于禁用状态。

切面拦截 RedisConnection::__call 是有问题的。该魔术方法会在每次 Redis 命令执行时触发(如 get、set 等),导致 process() 方法中设置的上下文信息在每个命令上都冗余执行。这会产生性能开销。

如果目标是在连接建立时一次性捕获配置信息,应改为拦截 RedisFactory::make()RedisPool::get() 等连接工厂方法。

注意: 此切面当前在 ConfigProvider.php 第 44 行被注释掉,活跃的是 RedisAspect 类。若后续计划启用此切面,请先验证上述设计问题。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (2)

36-37: 建议添加错误处理。

调用 $connection->getHost()$connection->getPort() 时没有检查返回值的有效性。虽然这些方法通常不会失败,但添加基本的验证可以提高代码的健壮性,避免将无效值写入追踪上下文。

考虑添加简单的非空检查:

 if ($connection instanceof Redis) { // Redis or RedisSentinel
-    Context::set(Constants::TRACE_REDIS_SERVER_ADDRESS, $connection->getHost());
-    Context::set(Constants::TRACE_REDIS_SERVER_PORT, $connection->getPort());
+    $host = $connection->getHost();
+    $port = $connection->getPort();
+    if ($host && $port) {
+        Context::set(Constants::TRACE_REDIS_SERVER_ADDRESS, $host);
+        Context::set(Constants::TRACE_REDIS_SERVER_PORT, $port);
+    }
 }

40-42: RedisCluster 支持标记为 TODO 是合理的。

在草稿 PR 中保留 RedisCluster 的 TODO 是可以接受的。不过,在合并到主分支之前,建议要么实现 RedisCluster 支持,要么在文档中明确说明当前仅支持单机和 sentinel 模式。

需要帮助生成 RedisCluster 的实现代码吗?

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db8c96e and 0b31cb9.

📒 Files selected for processing (2)
  • src/sentry/src/ConfigProvider.php (1 hunks)
  • src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (2)
src/sentry/src/Constants.php (1)
  • Constants (14-37)
src/sentry/src/Tracing/Listener/EventHandleListener.php (1)
  • process (123-168)
src/sentry/src/ConfigProvider.php (1)
src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (1)
  • RedisConnectionAspect (23-45)
🪛 PHPMD (2.15.0)
src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php

31-31: Avoid unused parameters such as '$result'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.1 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.3 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.1 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.2 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.2 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.3 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.3 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.1 with Swoole 6.1.2
  • GitHub Check: Test on PHP 8.2 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.1 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.3 with Swoole 5.1.8
  • GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
  • GitHub Check: Test on PHP 8.2 with Swoole 6.1.2
🔇 Additional comments (3)
src/sentry/src/ConfigProvider.php (1)

44-44: 已正确启用 Redis 连接追踪切面。

该变更将 RedisConnectionAspect 添加到切面列表中,使其能够拦截 Redis 连接调用并捕获服务器地址信息用于分布式追踪。这与 PR 目标一致。

src/sentry/src/Tracing/Aspect/RedisConnectionAspect.php (2)

31-31: 静态分析标记的未使用参数是可接受的。

静态分析工具标记 $result 参数未使用,这在技术上是正确的。然而,这是 tap() 函数的惯用法——当你只需要执行副作用并返回原始值时,闭包参数可以不使用。当前实现是符合习惯的,无需修改。


33-33: 属性访问方式的准确性澄清。

原始评论关于属性类型的描述有误:代码访问的 $connectionprotected 属性而非私有属性,这是 PHP 中访问受保护属性的有效模式。该属性在 Hyperf RedisConnection 类的第38行已明确定义为 protected null|Redis|RedisCluster $connection = null,具有完整的类型声明。

代码不会因属性名改变而"静默失败"——如果访问 $this->connection 时属性不存在或为 null,闭包会返回 null,这是预期的安全行为,而非隐蔽的失败。由于属性在框架源代码中已明确声明且有类型声明,其稳定性有保障,删除此属性需要框架的重大版本变更。

@huangdijia huangdijia marked this pull request as ready for review November 21, 2025 04:16
@huangdijia huangdijia merged commit 8d86e78 into main Nov 21, 2025
22 checks passed
@huangdijia huangdijia deleted the feat/redis-connection-tracing branch November 21, 2025 04:16
huangdijia added a commit that referenced this pull request Nov 21, 2025
* feat: add server address tracing for Redis connections

- Add server.address and server.port to Redis tracing data
- Support cluster and sentinel configurations
- Add ConnectionContainer utility for WeakMap-based connection tracking
- Add RedisConnectionAspect for connection configuration capture (commented out in config)

* feat: improve server address handling for Redis connections

* feat: 添加 Redis 服务器地址和端口追踪常量,并更新相关逻辑

* feat: 更新 Redis 连接追踪逻辑以直接获取服务器地址和端口

* feat: 更新 Redis 连接追踪逻辑,添加注释以区分 Redis 和 RedisCluster

* fix: 修正 RedisConnectionAspect 中的变量命名以提高代码可读性

* feat: 启用 RedisConnectionAspect 以支持 Redis 连接追踪

---------

Co-authored-by: Deeka Wong <8337659+huangdijia@users.noreply.github.com>
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.

2 participants