Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tash command for power management monitoring #4185

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

hasw7569
Copy link
Contributor

@hasw7569 hasw7569 commented Feb 5, 2020

I added "pminfo" tash command for power management monitoring.
It displays wakeup reason and stay time of each pm state and activities of registered driver in each pm state.
also pminfo command displays accumulated stay time for each pm state.

@seinfra
Copy link

seinfra commented Feb 5, 2020

Target : [b6ae987ddaef13dc3784419cc93a312f553c2d7f] - Code Rule Check OK.

@seinfra
Copy link

seinfra commented Feb 5, 2020

Target : [b6ae987ddaef13dc3784419cc93a312f553c2d7f] - Code Rule Check (C++) OK.

@seinfra
Copy link

seinfra commented Feb 5, 2020

Target : [fd9b077d19c776bbc45c6efe9e71993701470018] - Code Rule Check OK.

@seinfra
Copy link

seinfra commented Feb 5, 2020

Target : [fd9b077d19c776bbc45c6efe9e71993701470018] - Code Rule Check (C++) OK.

@seinfra
Copy link

seinfra commented Feb 5, 2020

Target : [00399aa8413e34b7d4a06e383f3f3385a6f410a0] - Code Rule Check (C++) OK.

@seinfra

This comment has been minimized.

@seinfra
Copy link

seinfra commented Feb 5, 2020

Target : [bf5f0cbc6d082bb337c1dceeef0a65b3af153154] - Code Rule Check (C++) OK.

@seinfra

This comment has been minimized.

@@ -66,6 +66,7 @@ endif
ifeq ($(CONFIG_ARCH_BOARD_SIDK_S5JT200),y)
CFLAGS+=-I$(TOPDIR)/../apps/include/netutils/wifi
endif
CFLAGS+=-I$(TOPDIR)/pm
Copy link
Contributor

Choose a reason for hiding this comment

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

If pm is config, it would be better to add it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pm is directory name.


#ifdef CONFIG_SCHED_CPULOAD
#include <tinyara/clock.h>
#endif

#include <arch/irq.h>
#include <pm.h>
Copy link
Contributor

Choose a reason for hiding this comment

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

How about this change?
#if defined(CONFIG_PM) && defined(CONFIG_PM_METRICS) #include <pm.h> #endif
and line 84

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree.
I will update soon.

const char* local = buf;
int ret = ERROR;
while(*local) {
if(*local == ' ') {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add space after 'if'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

modified.

{
const char* local = buf;
int ret = ERROR;
while(*local) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add space after while

@@ -256,6 +266,12 @@ static const struct proc_node_s g_groupfd = {
"group/fd", "fd", (uint8_t)PROC_GROUP_FD, DTYPE_FILE /* Group file descriptors */
};

#if defined(CONFIG_PM) && defined(CONFIG_PM_METRICS)
static const struct proc_node_s g_pminfo = {
"pminfo", "pminfo", (uint8_t)PROC_PMINFO, DTYPE_FILE /* Group file descriptors */
Copy link
Contributor

Choose a reason for hiding this comment

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

Please change the description. This is not for group file descriptors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

modified.

FAR sq_entry_t *node = NULL;
int flag = ERROR;
for (node = sq_peek(&(pdom->accum_history)); node; node = node->flink) {
if (!strcmp(((struct pm_accumchange_s*)node)->name, callbacks->name)) {
Copy link
Contributor

@jeongchanKim jeongchanKim Feb 6, 2020

Choose a reason for hiding this comment

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

  1. You'd better use strncmp instead of strcmp.
  2. callbacks can be dereferencing. Please check that first.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

modified.

@@ -150,6 +170,11 @@ void pm_activity(int domain, int priority)
tmp = pdom->accum;
pdom->stime = now;
pdom->accum = 0;
#ifdef CONFIG_PM_METRICS
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove tabs before #ifdef.

* language governing permissions and limitations under the License.
*
****************************************************************************/
/****************************************************************************
Copy link
Contributor

Choose a reason for hiding this comment

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

This is your own code. You don't need to add Nuttx license.


node = (struct pm_statechange_s *)pm_alloc(1, sizeof(struct pm_statechange_s));

node->state = state;
Copy link
Contributor

Choose a reason for hiding this comment

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

node can be NULL, then it can be dereferencing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

modified

@@ -296,7 +296,9 @@ int pm_changestate(int domain_indx, enum pm_state_e newstate)
pm_changeall(domain_indx, newstate);
if (newstate != PM_RESTORE) {
g_pmglobals.domain[domain_indx].state = newstate;

#ifdef CONFIG_PM_METRICS
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove tabs before #ifdef.

@@ -145,6 +147,32 @@ enum pm_state_e pm_checkstate(int domain)
*/

(void)pm_update(domain, accum);
#ifdef CONFIG_PM_METRICS
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove tabs before #ifdef.

FAR sq_entry_t *dest = NULL;

do
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove enter between do and {.

} while (dest);

for (node = sq_peek(&(pdom->accum_history)); node; node = node->flink) {
struct pm_accumchange_s *insert_node = (struct pm_accumchange_s *)pm_alloc(1, sizeof(struct pm_accumchange_s));
Copy link
Contributor

Choose a reason for hiding this comment

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

Please check insert_node is NULL or not.

struct pm_accumchange_s {
sq_entry_t entry;
int16_t accum;
#ifdef CONFIG_PM
Copy link
Contributor

@jeongchanKim jeongchanKim Feb 6, 2020

Choose a reason for hiding this comment

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

Why only name should have condition for CONFIG_PM?
And if you want to use CONFIG_XX, you'd better include <tinyara/config.h>


initnode = (struct pm_accumchange_s *)pm_alloc(1, sizeof(struct pm_accumchange_s));

initnode->accum = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Check dereferencing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

modified

@@ -94,6 +95,16 @@ int pm_unregister(FAR struct pm_callback_s *callbacks)
ret = pm_lock();
if (ret == OK) {
dq_rem(&callbacks->entry, &g_pmglobals.registry);
#ifdef CONFIG_PM_METRICS
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove unnecessary tabs.

FAR sq_entry_t *node = NULL;

for(node = sq_peek(&(g_pmglobals.domain[callbacks->domain].accum_history)); node; node = node->flink) {
if(!strcmp(((struct pm_accumchange_s*)node)->name, callbacks->name)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use strncmp instead of strcmp.

* language governing permissions and limitations under the License.
*
****************************************************************************/
/****************************************************************************
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need to add Nuttx License in this file.


static void pminfo_print_values(char *buf)
{
int i, j;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please separate the declaration.


for (i = 0; pm_info[i]; i = i + 3 + driver_cnt) {

if(!strcmp(pm_info[i], "END")) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use strncmp instead of strcmp.

struct pm_domain_s *pdom = &g_pmglobals.domain[callbacks->domain];
struct pm_accumchange_s *node = NULL;

for (node = (struct pm_accumchange_s*)sq_peek(&(pdom->each_accum)); node; node = (struct pm_accumchange_s*)(node->entry.flink)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add space before '*'.

@@ -150,6 +179,11 @@ void pm_activity(int domain, int priority)
tmp = pdom->accum;
pdom->stime = now;
pdom->accum = 0;
#ifdef CONFIG_PM_METRICS
for (node = (struct pm_accumchange_s*)sq_peek(&(pdom->each_accum)); node; node = (struct pm_accumchange_s*)(node->entry.flink)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add space before '*'.

@@ -42,6 +42,9 @@ config PM_METRICS_DURATION
int "History Duration (secs)"
default 60

config PM_HISTORY_COUNT
int "History Count"
default 5
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add ---help--- for explanation about this config?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

modified

} else {
for (node = (struct pm_accumchange_s*)sq_peek(&(pdom->each_accum)); node; node = (struct pm_accumchange_s*)(node->entry.flink)) {
if (!strncmp(node->name, name, strlen(name) + 1)) {
uint32_t next_accum = node->accum + priority;
Copy link
Contributor

Choose a reason for hiding this comment

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

next_accum is unnecessary, actually.

@@ -166,6 +167,10 @@ static const struct procfs_entry_s g_procfsentries[] = {
{"ereport/*", &ereport_operations},
#endif

#if defined(CONFIG_PM) && defined(CONFIG_PM_METRICS)
Copy link
Contributor

Choose a reason for hiding this comment

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

Like above cases, CONFIG_FS_PROCFS_EXCLUDE_PM may needed.

pminfo_close, /* close */
pminfo_read, /* read */
NULL, /* write */
NULL, /* dup */
Copy link
Contributor

Choose a reason for hiding this comment

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

This is one of file operation, also working with fd, so dup & stat are necessary.

/* Create an initial state change node with NORMAL state and bootup time */

node = (struct pm_statechange_s *)pm_alloc(1, sizeof(struct pm_statechange_s));
if (node == NULL) {
Copy link
Contributor

Choose a reason for hiding this comment

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

minor one you don't have to modify but below is more good habit for future work.
if (a != b) {
return; //ENOMEM for your case.
}

do something

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your comment : )

tail = (struct pm_statechange_s*)dq_tail(&(pdom->history));
if (tail != NULL) {
pdom->state_stay[tail->state] += node->timestamp - tail->timestamp;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

In my opinion, if tail is null, then we don't have to push it to history(below progress)...isn't it?

Copy link
Contributor Author

@hasw7569 hasw7569 Feb 20, 2020

Choose a reason for hiding this comment

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

Thank you for your comment : )
but I don't understand what you mean.
Do you mean we don't have to push "tail" into history?

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean below, if tail contains most recent history...

if (tail == NULL) {
   return;
}
pdom->state_stay.....

Ignore this If I am wrong.

*
****************************************************************************/
#ifdef CONFIG_PM_METRICS
void pm_addhistory(int domain, int state)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is one of kernel feature, you had better modify type to int, instead of void.
Then you can return error values which is defined in errno.h

Comment on lines 157 to 161
dest = (struct pm_accumchange_s*)sq_remfirst(&(history->accum_history));
while (dest) {
free(dest);
dest = (struct pm_accumchange_s*)sq_remfirst(&(history->accum_history));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

To clean code, how about do-while?

@seinfra
Copy link

seinfra commented Feb 20, 2020

Target : [17e8ea1e9a4416f95b32b4145ea3ebedbaf334f8] - Code Rule Check (C++) OK.

@seinfra
Copy link

seinfra commented Feb 20, 2020

Target : [17e8ea1e9a4416f95b32b4145ea3ebedbaf334f8] - Code Rule Check OK.

@@ -153,6 +153,10 @@ ifeq ($(CONFIG_ENABLE_UPTIME),y)
CSRCS += utils_uptime.c
endif

ifeq ($(CONFIG_PM)$(CONFIG_PM_METRICS),yy)
Copy link
Contributor

Choose a reason for hiding this comment

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

PM_METRICS can be enabled when PM is enabled so that you could remove the PM conditional. It's redundant.

#define PM_INFO_MAX 100

static char* pm_state[5] = {
"NORMAL",
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's remove a tab. It has two tabs.

for (i = 0; i < loop; i++) {
printf("----------------------");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the purpose of this function?

Copy link
Contributor Author

@hasw7569 hasw7569 Feb 24, 2020

Choose a reason for hiding this comment

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

This function print line to separate entries.
I think this function improves readability for user.

@@ -769,7 +770,7 @@ static struct
bool serial_suspended;
} g_serialpm =
{
.pm_cb.name = "serial",
.pm_cb.name = SERIAL_NAME,
Copy link
Contributor

Choose a reason for hiding this comment

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

This new definition is used only here. Do we need to use this definition only for here?

@@ -182,6 +182,7 @@
#endif
#if defined(CONFIG_PM)
# define PM_IDLE_DOMAIN 0 /* Revisit */
#define SERIAL_NAME "serial"
Copy link
Contributor

Choose a reason for hiding this comment

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

serial name "serial" ?

@@ -166,6 +167,10 @@ static const struct procfs_entry_s g_procfsentries[] = {
{"ereport/*", &ereport_operations},
#endif

#if defined(CONFIG_PM_METRICS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_PMINFO)
Copy link
Contributor

Choose a reason for hiding this comment

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

CONFIG_FS_PROCFS_EXCLUDE_PMINFO is enough. Because FS_PROCFS_EXCLUDE_PMINFO checks PM_METRICS.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Even if CONFIG_PM_METRICS is disabled, the default value of CONFIG_FS_PROCFS_EXCLUDE_PMINFO is "n".
So we should check CONFIG_PM_METRICS, otherwise "pminfo_operations" is not defined.
"pminfo_operations" in "fs_procfspminfo.c" can be defined when CONFIG_PM_METRICS is enabled.

* Included Files
****************************************************************************/

#include <tinyara/config.h>
Copy link
Contributor

Choose a reason for hiding this comment

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

This file does not include CONFIG_XX. Unnecessary.


static int pminfo_open(FAR struct file *filep, FAR const char *relpath, int oflags, mode_t mode);
static int pminfo_close(FAR struct file *filep);
static ssize_t pminfo_read(FAR struct file *filep, FAR char *buffer, size_t buflen);
Copy link
Contributor

Choose a reason for hiding this comment

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

header for ssize_t

Copy link
Contributor Author

Choose a reason for hiding this comment

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

modified

@seinfra
Copy link

seinfra commented Feb 24, 2020

Target : [8dba31b] - Code Rule Check (C++) OK.

@seinfra
Copy link

seinfra commented Feb 24, 2020

Target : [8dba31b] - Code Rule Check OK.

@seinfra
Copy link

seinfra commented Mar 6, 2020

Target : [3f144b7] - Code Rule Check (C++) OK.

@seinfra
Copy link

seinfra commented Mar 6, 2020

Target : [3f144b7] - Code Rule Check OK.

hasw7569 added 4 commits March 6, 2020 14:58
I added accum_history variable into pm_domain_s to record activity of each driver

When the power management state changes,
It records the activity of each driver and current power management state and timestamp into history variable.

so, user can check stay time of each power management state.
user can also check the activity of registerd drivers in each power management state.
I added pminfo tash command for power management monitoring.
pminfo command displays wakeup reason and stay time of each power management state and activity of each registered driver into pm system.
also pminfo command displays accumulated stay time for each state.

ex)
TASH>>pminfo

 STATE  | STAY(SEC) |
---------------------
 NORMAL |        21 |
---------------------
   IDLE |        17 |
---------------------
STANDBY |        15 |
---------------------
  SLEEP |        46 |
---------------------

 STATE  | STAY(SEC) | WAKEUP REASON | drv_name , activity |
-----------------------------------------------------------
 NORMAL |        10 |          None |   serial ,        0 |
-----------------------------------------------------------
   IDLE |         5 |          None |   serial ,        0 |
-----------------------------------------------------------
STANDBY |         7 |          None |   serial ,        0 |
-----------------------------------------------------------
  SLEEP |         5 |        serial |   serial ,       20 |
-----------------------------------------------------------
update pm_activity() call on some boards
I added fs_procfspminfo.c file to handle pminfo file operations.

The pminfo_read function writes power management monitoring informations to the buffer.
pm_data pm_info[PM_INFO_MAX];

pm_info[0] = buf;
for (i = 0; strncmp(pm_info[i], "eof", strlen("eof") + 1) != 0; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you'd better to define "eof"

}
printf("\n---------------------\n\n\n");

for (i = 5; strncmp(pm_info[i], "eof", strlen("eof") + 1) != 0; i += j + 4) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you need to calculate count instead of hardcode

pminfo_open, /* open */
pminfo_close, /* close */
pminfo_read, /* read */
NULL, /* write */
Copy link
Contributor

Choose a reason for hiding this comment

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

please change alignment

Copy link
Contributor

@jeongarmy jeongarmy left a comment

Choose a reason for hiding this comment

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

please find comments

@@ -450,6 +452,7 @@ int pm_unregister(FAR struct pm_callback_s *callbacks);
* priority - Activity priority, range 0-9. Larger values correspond to
* higher priorities. Higher priority activity can prevent the system
* from entering reduced power states for a longer period of time.
* name - name of the registered driver or application
Copy link
Contributor

Choose a reason for hiding this comment

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

Application can register it?

* Name: pm_addhistory
*
* Description:
* This function add history into pm structure.
Copy link
Contributor

Choose a reason for hiding this comment

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

adds

@@ -58,12 +58,15 @@

#include <stdint.h>
#include <assert.h>
#include <queue.h>
Copy link
Contributor

Choose a reason for hiding this comment

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

#ifdef CONFIG_PM_METRICS

@@ -150,6 +179,14 @@ void pm_activity(int domain, int priority)
tmp = pdom->accum;
pdom->stime = now;
pdom->accum = 0;
#ifdef CONFIG_PM_METRICS
for (node = (struct pm_accumchange_s *)sq_peek(&(pdom->each_accum)); node; node = (struct pm_accumchange_s *)(node->entry.flink)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

sq_next

@tizen-build
Copy link

Target : [3f144b7] - Code Rule Check OK.

@@ -306,6 +307,7 @@ struct pm_callback_s {
struct dq_entry_s entry; /* Supports a doubly linked list */

char name[MAX_PM_CALLBACK_NAME]; /* Name of driver which register callback */
Copy link
Contributor

Choose a reason for hiding this comment

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

maintain same tab space for comments

@@ -306,6 +307,7 @@ struct pm_callback_s {
struct dq_entry_s entry; /* Supports a doubly linked list */

char name[MAX_PM_CALLBACK_NAME]; /* Name of driver which register callback */
int domain; /* Number of domain */
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

@@ -631,12 +651,13 @@ enum pm_state_e pm_querystate(int domain);
#define pm_initialize()
#define pm_register(cb) (0)
#define pm_unregister(cb) (0)
Copy link
Contributor

Choose a reason for hiding this comment

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

try to maintain the same tab space here also

#define pm_relax(domain,state)
#define pm_activity(domain, prio, name)
#define pm_stay(domain, state)
#define pm_relax(domain, state)
#define pm_checkstate(domain) (0)
#define pm_changestate(domain, state) (0)
#define pm_querystate(domain) (0)
Copy link
Contributor

@deepaksrma deepaksrma May 15, 2020

Choose a reason for hiding this comment

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

same as above

return -ENOMEM;
}

/* The copy the file attributes from the old attributes to the new */
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we please remove "The" or may be we can modify the comment line ?

@@ -0,0 +1,146 @@
/****************************************************************************
*
* Copyright 2020 Samsung Electronics All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we change the year to 2021?

@tizen-build
Copy link

Target : [3f144b7] - Code Rule Check OK.

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.

10 participants