Skip to contentSkip to conversation

per-thread exec-time stats

RFC PATCH In review Jan Kiszka 3 participants 24 messages Jul 6, 2006 – Jul 7, 2006

lore
Share & tools
lore

Jan Kiszka

author
#0 lore raw

RFC PATCH 1/1 per-thread exec-time stats

Hi,

I think to remember people crying for some execution time statistics for quite a while now. Here comes a straightforward approach to add this on a per-thread basis. The results are printed like this:

[email protected] :/root# cat /proc/xenomai/stat

CPU  PID    MSW        CSW        PF    STAT              TIME  NAME
  0  0      0          7508041    0     01400080     166038894  ROOT
  0  0      0          2          0     00000082            55  timsPipeReceiver
  0  1009   751        8800       0     00c00082        143026  display-1008
  0  1010   0          7514585    0     00c00084      54472477  sampling-1008

The new TIME column contains the accumulated execution time of each active thread in nanoseconds. It's now a job of some user-space tool (shell script?) to pick those numbers up and do, e.g., periodic evaluation (I'm thinking of some "xeno-top"). Any volunteer around to hack this? Would be very welcome!

Jan

PS: Could someone have a look that I didn't miss an accounting point and my acquisition is sound?

--

 include/nucleus/pod.h    |   28 ++++++++++++++++++++++++++++
 include/nucleus/thread.h |    1 +
 ksrc/nucleus/module.c    |   26 +++++++++++++++++++++-----
 ksrc/nucleus/pod.c       |    5 +++++
 ksrc/nucleus/thread.c    |    1 +
 5 files changed, 56 insertions(+), 5 deletions(-)
nucleus/thread.h +1 -0
Index: include/nucleus/thread.h
===================================================================
@@ -152,6 +152,7 @@ typedef struct xnthread {
 	unsigned long csw;	/* Context switches (includes
 				   secondary -> primary switches) */
 	unsigned long pf;	/* Number of page faults */
+	xnticks_t exec_time;	/* Accumulated execution time (ticks) */
     } stat;
 #endif /* CONFIG_XENO_OPT_STATS */
 
nucleus/pod.h +28 -0
Index: include/nucleus/pod.h
===================================================================
@@ -145,6 +145,10 @@ typedef struct xnsched {
 
     xnthread_t rootcb;          /*!< Root thread control block. */
 
+#ifdef CONFIG_XENO_OPT_STATS
+    xnticks_t last_csw;         /*!< Last context switch (ticks). */
+#endif /* CONFIG_XENO_OPT_STATS */
+
 } xnsched_t;
 
 #ifdef CONFIG_SMP
@@ -544,6 +548,30 @@ static inline void xnpod_delete_self (vo
     xnpod_delete_thread(xnpod_current_thread());
 }
 
+#ifdef CONFIG_XENO_OPT_STATS
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
+    xnticks_t now = xntimer_get_rawclock();
+    threadout->stat.exec_time += now - sched->last_csw;
+    sched->last_csw = now;
+}
+
+static inline void xnpod_update_csw_date(xnsched_t *sched)
+{
+    sched->last_csw = xntimer_get_rawclock();
+}
+#else /* !CONFIG_XENO_OPT_STATS */
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
+}
+
+static inline void xnpod_update_csw_date(xnsched_t *sched)
+{
+}
+#endif /* CONFIG_XENO_OPT_STATS */
+
 #ifdef __cplusplus
 }
 #endif
nucleus/thread.c +1 -0
Index: ksrc/nucleus/thread.c
===================================================================
@@ -90,6 +90,7 @@ int xnthread_init(xnthread_t *thread,
 	thread->stat.ssw = 0;
 	thread->stat.csw = 0;
 	thread->stat.pf = 0;
+	thread->stat.exec_time = 0;
 #endif /* CONFIG_XENO_OPT_STATS */
 
 	/* These will be filled by xnpod_start_thread() */
nucleus/pod.c +5 -0
Index: ksrc/nucleus/pod.c
===================================================================
@@ -669,6 +669,9 @@ static inline void xnpod_switch_zombie(x
 
 	xnthread_cleanup_tcb(threadout);
 
+	/* no need to update stats of dying thread */
+	xnpod_update_csw_date(sched);
+
 	xnarch_finalize_and_switch(xnthread_archtcb(threadout),
 				   xnthread_archtcb(threadin));
 
@@ -2431,6 +2434,7 @@ void xnpod_schedule(void)
 		xnarch_enter_root(xnthread_archtcb(threadin));
 	}
 
+	xnpod_acc_exec_time(sched, threadout);
 	xnthread_inc_csw(threadin);
 
 	xnarch_switch_to(xnthread_archtcb(threadout),
@@ -2602,6 +2606,7 @@ void xnpod_schedule_runnable(xnthread_t 
 		nkpod->schedhook(runthread, XNREADY);
 #endif /* __XENO_SIM__ */
 
+	xnpod_acc_exec_time(sched, runthread);
 	xnthread_inc_csw(threadin);
 
 	xnarch_switch_to(xnthread_archtcb(runthread),
nucleus/module.c +21 -5
Index: ksrc/nucleus/module.c
===================================================================
@@ -254,6 +254,7 @@ struct stat_seq_iterator {
 		unsigned long ssw;
 		unsigned long csw;
 		unsigned long pf;
+		xnticks_t exec_time;
 	} stat_info[1];
 };
 
@@ -294,13 +295,17 @@ static void stat_seq_stop(struct seq_fil
 static int stat_seq_show(struct seq_file *seq, void *v)
 {
 	if (v == SEQ_START_TOKEN)
-		seq_printf(seq, "%-3s  %-6s %-10s %-10s %-4s  %-8s  %s\n",
-			   "CPU", "PID", "MSW", "CSW", "PF", "STAT", "NAME");
+		seq_printf(seq, "%-3s  %-6s %-10s %-10s %-4s  %-8s  %12s"
+			   "  %s\n",
+			   "CPU", "PID", "MSW", "CSW", "PF", "STAT", "TIME",
+			   "NAME");
 	else {
 		struct stat_seq_info *p = (struct stat_seq_info *)v;
-		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %s\n",
+		unsigned long long exec_time = xnpod_ticks2ns(p->exec_time);
+		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %12llu"
+			   "  %s\n",
 			   p->cpu, p->pid, p->ssw, p->csw, p->pf, p->status,
-			   p->name);
+			   xnarch_ulldiv(exec_time, 1000, NULL), p->name);
 	}
 
 	return 0;
@@ -318,7 +323,7 @@ static int stat_seq_open(struct inode *i
 	struct stat_seq_iterator *iter;
 	struct seq_file *seq;
 	xnholder_t *holder;
-	int err, count;
+	int err, count, cpu;
 	spl_t s;
 
 	if (!nkpod)
@@ -341,6 +346,16 @@ static int stat_seq_open(struct inode *i
 
 	iter->nentries = 0;
 
+	/* update exec-time stats of currently running threads */
+	for_each_online_cpu(cpu) {
+		xnsched_t *sched;
+
+		xnlock_get_irqsave(&nklock, s);
+		sched = xnpod_sched_slot(cpu);
+		xnpod_acc_exec_time(sched, sched->runthread);
+		xnlock_put_irqrestore(&nklock, s);
+	}
+
 	/* Take a snapshot and release the nucleus lock immediately after,
 	   so that dumping /proc/xenomai/stat with lots of entries won't
 	   cause massive jittery. */
@@ -359,6 +374,7 @@ static int stat_seq_open(struct inode *i
 		iter->stat_info[n].ssw = thread->stat.ssw;
 		iter->stat_info[n].csw = thread->stat.csw;
 		iter->stat_info[n].pf = thread->stat.pf;
+		iter->stat_info[n].exec_time = thread->stat.exec_time;
 	}
 
 	xnlock_put_irqrestore(&nklock, s);
author
#1 lore raw
Quoted 7 lines · Jan Kiszka

Hi,

I think to remember people crying for some execution time statistics for quite a while now. Here comes a straightforward approach to add this on a per-thread basis. The results are printed like this:

[email protected] :/root# cat /proc/xenomai/stat

Quoted 8 lines
CPU  PID    MSW        CSW        PF    STAT              TIME  NAME
  0  0      0          7508041    0     01400080     166038894  ROOT
  0  0      0          2          0     00000082            55  timsPipeReceiver
  0  1009   751        8800       0     00c00082        143026  display-1008
  0  1010   0          7514585    0     00c00084      54472477  sampling-1008

The new TIME column contains the accumulated execution time of each active thread in nanoseconds. It's now a job of some user-space tool (shell script?) to pick those

I was thinking of the right unit, I swear:
s/nanoseconds/microseconds/

Quoted 8 lines

numbers up and do, e.g., periodic evaluation (I'm thinking of some "xeno-top"). Any volunteer around to hack this? Would be very welcome!

Jan

PS: Could someone have a look that I didn't miss an accounting point and my acquisition is sound?

Jan

#2 lore raw
In reply to Jan Kiszka

On Thu, 2006-07-06 at 13:37 +0200, Jan Kiszka wrote:

<snip>

Quoted 41 lines

PS: Could someone have a look that I didn't miss an accounting point and my acquisition is sound?

--

 include/nucleus/pod.h    |   28 ++++++++++++++++++++++++++++
 include/nucleus/thread.h |    1 +
 ksrc/nucleus/module.c    |   26 +++++++++++++++++++++-----
 ksrc/nucleus/pod.c       |    5 +++++
 ksrc/nucleus/thread.c    |    1 +
 5 files changed, 56 insertions(+), 5 deletions(-)

Index: include/nucleus/thread.h
===================================================================

nucleus/thread.h +1 -0
@@ -152,6 +152,7 @@ typedef struct xnthread {
 	unsigned long csw;	/* Context switches (includes
 				   secondary -> primary switches) */
 	unsigned long pf;	/* Number of page faults */
+	xnticks_t exec_time;	/* Accumulated execution time (ticks) */
     } stat;
 #endif /* CONFIG_XENO_OPT_STATS */
 

Index: include/nucleus/pod.h
===================================================================

nucleus/pod.h +4 -0
@@ -145,6 +145,10 @@ typedef struct xnsched {
 
     xnthread_t rootcb;          /*!< Root thread control block. */
 
+#ifdef CONFIG_XENO_OPT_STATS
+    xnticks_t last_csw;         /*!< Last context switch (ticks). */
+#endif /* CONFIG_XENO_OPT_STATS */
+
 } xnsched_t;
 
 #ifdef CONFIG_SMP
@@ -544,6 +548,30 @@ static inline void xnpod_delete_self (vo
     xnpod_delete_thread(xnpod_current_thread());

nucleus/pod.h static inline void xnpod_delete_self (vo · line 558

Quoted 10 lines
nucleus/pod.h +8 -0
 }
 
+#ifdef CONFIG_XENO_OPT_STATS
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
+    xnticks_t now = xntimer_get_rawclock();
+    threadout->stat.exec_time += now - sched->last_csw;
+    sched->last_csw = now;
+}

It would be better to only pass the thread pointer, then use the thread->sched member. This would clearly explain the relationship between both, and prevent any bugous attempt at mixing things.

Quoted 108 lines
nucleus/pod.h +16 -0
+
+static inline void xnpod_update_csw_date(xnsched_t *sched)
+{
+    sched->last_csw = xntimer_get_rawclock();
+}
+#else /* !CONFIG_XENO_OPT_STATS */
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
+}
+
+static inline void xnpod_update_csw_date(xnsched_t *sched)
+{
+}
+#endif /* CONFIG_XENO_OPT_STATS */
+
 #ifdef __cplusplus
 }
 #endif

Index: ksrc/nucleus/thread.c
===================================================================

nucleus/thread.c +1 -0
@@ -90,6 +90,7 @@ int xnthread_init(xnthread_t *thread,
 	thread->stat.ssw = 0;
 	thread->stat.csw = 0;
 	thread->stat.pf = 0;
+	thread->stat.exec_time = 0;
 #endif /* CONFIG_XENO_OPT_STATS */
 
 	/* These will be filled by xnpod_start_thread() */

Index: ksrc/nucleus/pod.c
===================================================================

nucleus/pod.c +5 -0
@@ -669,6 +669,9 @@ static inline void xnpod_switch_zombie(x
 
 	xnthread_cleanup_tcb(threadout);
 
+	/* no need to update stats of dying thread */
+	xnpod_update_csw_date(sched);
+
 	xnarch_finalize_and_switch(xnthread_archtcb(threadout),
 				   xnthread_archtcb(threadin));
 
@@ -2431,6 +2434,7 @@ void xnpod_schedule(void)
 		xnarch_enter_root(xnthread_archtcb(threadin));
 	}
 
+	xnpod_acc_exec_time(sched, threadout);
 	xnthread_inc_csw(threadin);
 
 	xnarch_switch_to(xnthread_archtcb(threadout),
@@ -2602,6 +2606,7 @@ void xnpod_schedule_runnable(xnthread_t 
 		nkpod->schedhook(runthread, XNREADY);
 #endif /* __XENO_SIM__ */
 
+	xnpod_acc_exec_time(sched, runthread);
 	xnthread_inc_csw(threadin);
 
 	xnarch_switch_to(xnthread_archtcb(runthread),

Index: ksrc/nucleus/module.c
===================================================================

nucleus/module.c +10 -5
@@ -254,6 +254,7 @@ struct stat_seq_iterator {
 		unsigned long ssw;
 		unsigned long csw;
 		unsigned long pf;
+		xnticks_t exec_time;
 	} stat_info[1];
 };
 
@@ -294,13 +295,17 @@ static void stat_seq_stop(struct seq_fil
 static int stat_seq_show(struct seq_file *seq, void *v)
 {
 	if (v == SEQ_START_TOKEN)
-		seq_printf(seq, "%-3s  %-6s %-10s %-10s %-4s  %-8s  %s\n",
-			   "CPU", "PID", "MSW", "CSW", "PF", "STAT", "NAME");
+		seq_printf(seq, "%-3s  %-6s %-10s %-10s %-4s  %-8s  %12s"
+			   "  %s\n",
+			   "CPU", "PID", "MSW", "CSW", "PF", "STAT", "TIME",
+			   "NAME");
 	else {
 		struct stat_seq_info *p = (struct stat_seq_info *)v;
-		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %s\n",
+		unsigned long long exec_time = xnpod_ticks2ns(p->exec_time);
+		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %12llu"
+			   "  %s\n",
 			   p->cpu, p->pid, p->ssw, p->csw, p->pf, p->status,
-			   p->name);
+			   xnarch_ulldiv(exec_time, 1000, NULL), p->name);
 	}
 
 	return 0;
@@ -318,7 +323,7 @@ static int stat_seq_open(struct inode *i
 	struct stat_seq_iterator *iter;
 	struct seq_file *seq;
 	xnholder_t *holder;
-	int err, count;
+	int err, count, cpu;
 	spl_t s;
 
 	if (!nkpod)
@@ -341,6 +346,16 @@ static int stat_seq_open(struct inode *i
 
 	iter->nentries = 0;
 

nucleus/module.c static int stat_seq_open(struct inode *i · line 358

Quoted 10 lines
nucleus/module.c +10 -0
+	/* update exec-time stats of currently running threads */
+	for_each_online_cpu(cpu) {
+		xnsched_t *sched;
+
+		xnlock_get_irqsave(&nklock, s);
+		sched = xnpod_sched_slot(cpu);
+		xnpod_acc_exec_time(sched, sched->runthread);
+		xnlock_put_irqrestore(&nklock, s);
+	}
+

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

Quoted 18 lines
nucleus/module.c +1 -0
 	/* Take a snapshot and release the nucleus lock immediately after,
 	   so that dumping /proc/xenomai/stat with lots of entries won't
 	   cause massive jittery. */
@@ -359,6 +374,7 @@ static int stat_seq_open(struct inode *i
 		iter->stat_info[n].ssw = thread->stat.ssw;
 		iter->stat_info[n].csw = thread->stat.csw;
 		iter->stat_info[n].pf = thread->stat.pf;
+		iter->stat_info[n].exec_time = thread->stat.exec_time;
 	}
 
 	xnlock_put_irqrestore(&nklock, s);

_______________________________________________
Xenomai-core mailing list
[email protected]
https://mail.gna.org/listinfo/xenomai-core

Signature · 1 line
Philippe.
#22 lore raw
In reply to Jan Kiszka

nucleus/module.c static void stat_seq_stop(struct seq_fil · line 308

Quoted 6 lines · Jan Kiszka

c_time);

nucleus/module.c +3 -1
+		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %12llu"
+			   "  %s\n",
 			   p->cpu, p->pid, p->ssw, p->csw, p->pf, p->status,
-			   p->name);
+			   xnarch_ulldiv(exec_time, 1000, NULL), p->name);

xntimer_get_rawclock() returns TSC or jiffies, depending on the current aperiodic/periodic mode; what we need is something like xnarch_ulldiv(xnpod_ticks2ns(exec_time), ...)

Signature · 1 line
Philippe.
#23 lore raw
Quoted 12 lines · Philippe Gerum

nucleus/module.c static void stat_seq_stop(struct seq_fil · line 308

Quoted 6 lines · Jan Kiszka

c_time);

nucleus/module.c +3 -1
+		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %12llu"
+			   "  %s\n",
 			   p->cpu, p->pid, p->ssw, p->csw, p->pf, p->status,
-			   p->name);
+			   xnarch_ulldiv(exec_time, 1000, NULL), p->name);

xntimer_get_rawclock() returns TSC or jiffies, depending on the current aperiodic/periodic mode; what we need is something like xnarch_ulldiv(xnpod_ticks2ns(exec_time), ...)

Yeah, right. Forget about this one. -ENOBRAIN returned on my side.

Signature · 1 line
Philippe.
author
#3 lore raw
In reply to Philippe Gerum
Quoted 47 lines · Philippe Gerum

On Thu, 2006-07-06 at 13:37 +0200, Jan Kiszka wrote:

<snip>

Quoted 43 lines

PS: Could someone have a look that I didn't miss an accounting point and my acquisition is sound?

--

 include/nucleus/pod.h    |   28 ++++++++++++++++++++++++++++
 include/nucleus/thread.h |    1 +
 ksrc/nucleus/module.c    |   26 +++++++++++++++++++++-----
 ksrc/nucleus/pod.c       |    5 +++++
 ksrc/nucleus/thread.c    |    1 +
 5 files changed, 56 insertions(+), 5 deletions(-)

Index: include/nucleus/thread.h
===================================================================

nucleus/thread.h +1 -0
@@ -152,6 +152,7 @@ typedef struct xnthread {
 	unsigned long csw;	/* Context switches (includes
 				   secondary -> primary switches) */
 	unsigned long pf;	/* Number of page faults */
+	xnticks_t exec_time;	/* Accumulated execution time (ticks) */
     } stat;
 #endif /* CONFIG_XENO_OPT_STATS */
 

Index: include/nucleus/pod.h
===================================================================

nucleus/pod.h +4 -0
@@ -145,6 +145,10 @@ typedef struct xnsched {
 
     xnthread_t rootcb;          /*!< Root thread control block. */
 
+#ifdef CONFIG_XENO_OPT_STATS
+    xnticks_t last_csw;         /*!< Last context switch (ticks). */
+#endif /* CONFIG_XENO_OPT_STATS */
+
 } xnsched_t;
 
 #ifdef CONFIG_SMP
@@ -544,6 +548,30 @@ static inline void xnpod_delete_self (vo
     xnpod_delete_thread(xnpod_current_thread());
 }
 
Quoted 12 lines

nucleus/pod.h static inline void xnpod_delete_self (vo · line 558

Quoted 8 lines
nucleus/pod.h +8 -0
+#ifdef CONFIG_XENO_OPT_STATS
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
+    xnticks_t now = xntimer_get_rawclock();
+    threadout->stat.exec_time += now - sched->last_csw;
+    sched->last_csw = now;
+}

It would be better to only pass the thread pointer, then use the thread->sched member. This would clearly explain the relationship between both, and prevent any bugous attempt at mixing things.

True, will rework.

Quoted 109 lines
Quoted 109 lines
nucleus/pod.h +16 -0
+
+static inline void xnpod_update_csw_date(xnsched_t *sched)
+{
+    sched->last_csw = xntimer_get_rawclock();
+}
+#else /* !CONFIG_XENO_OPT_STATS */
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
+}
+
+static inline void xnpod_update_csw_date(xnsched_t *sched)
+{
+}
+#endif /* CONFIG_XENO_OPT_STATS */
+
 #ifdef __cplusplus
 }
 #endif

Index: ksrc/nucleus/thread.c
===================================================================

nucleus/thread.c +1 -0
@@ -90,6 +90,7 @@ int xnthread_init(xnthread_t *thread,
 	thread->stat.ssw = 0;
 	thread->stat.csw = 0;
 	thread->stat.pf = 0;
+	thread->stat.exec_time = 0;
 #endif /* CONFIG_XENO_OPT_STATS */
 
 	/* These will be filled by xnpod_start_thread() */

Index: ksrc/nucleus/pod.c
===================================================================

nucleus/pod.c +5 -0
@@ -669,6 +669,9 @@ static inline void xnpod_switch_zombie(x
 
 	xnthread_cleanup_tcb(threadout);
 
+	/* no need to update stats of dying thread */
+	xnpod_update_csw_date(sched);
+
 	xnarch_finalize_and_switch(xnthread_archtcb(threadout),
 				   xnthread_archtcb(threadin));
 
@@ -2431,6 +2434,7 @@ void xnpod_schedule(void)
 		xnarch_enter_root(xnthread_archtcb(threadin));
 	}
 
+	xnpod_acc_exec_time(sched, threadout);
 	xnthread_inc_csw(threadin);
 
 	xnarch_switch_to(xnthread_archtcb(threadout),
@@ -2602,6 +2606,7 @@ void xnpod_schedule_runnable(xnthread_t 
 		nkpod->schedhook(runthread, XNREADY);
 #endif /* __XENO_SIM__ */
 
+	xnpod_acc_exec_time(sched, runthread);
 	xnthread_inc_csw(threadin);
 
 	xnarch_switch_to(xnthread_archtcb(runthread),

Index: ksrc/nucleus/module.c
===================================================================

nucleus/module.c +10 -5
@@ -254,6 +254,7 @@ struct stat_seq_iterator {
 		unsigned long ssw;
 		unsigned long csw;
 		unsigned long pf;
+		xnticks_t exec_time;
 	} stat_info[1];
 };
 
@@ -294,13 +295,17 @@ static void stat_seq_stop(struct seq_fil
 static int stat_seq_show(struct seq_file *seq, void *v)
 {
 	if (v == SEQ_START_TOKEN)
-		seq_printf(seq, "%-3s  %-6s %-10s %-10s %-4s  %-8s  %s\n",
-			   "CPU", "PID", "MSW", "CSW", "PF", "STAT", "NAME");
+		seq_printf(seq, "%-3s  %-6s %-10s %-10s %-4s  %-8s  %12s"
+			   "  %s\n",
+			   "CPU", "PID", "MSW", "CSW", "PF", "STAT", "TIME",
+			   "NAME");
 	else {
 		struct stat_seq_info *p = (struct stat_seq_info *)v;
-		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %s\n",
+		unsigned long long exec_time = xnpod_ticks2ns(p->exec_time);
+		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %12llu"
+			   "  %s\n",
 			   p->cpu, p->pid, p->ssw, p->csw, p->pf, p->status,
-			   p->name);
+			   xnarch_ulldiv(exec_time, 1000, NULL), p->name);
 	}
 
 	return 0;
@@ -318,7 +323,7 @@ static int stat_seq_open(struct inode *i
 	struct stat_seq_iterator *iter;
 	struct seq_file *seq;
 	xnholder_t *holder;
-	int err, count;
+	int err, count, cpu;
 	spl_t s;
 
 	if (!nkpod)
@@ -341,6 +346,16 @@ static int stat_seq_open(struct inode *i
 
 	iter->nentries = 0;
 
Quoted 13 lines

nucleus/module.c static int stat_seq_open(struct inode *i · line 358

Quoted 10 lines
nucleus/module.c +10 -0
+	/* update exec-time stats of currently running threads */
+	for_each_online_cpu(cpu) {
+		xnsched_t *sched;
+
+		xnlock_get_irqsave(&nklock, s);
+		sched = xnpod_sched_slot(cpu);
+		xnpod_acc_exec_time(sched, sched->runthread);
+		xnlock_put_irqrestore(&nklock, s);
+	}
+

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

The idea is avoid adding even further load to the nklock-protected loop. And we only update the current thread, not each and every.

Quoted 19 lines
Quoted 19 lines
nucleus/module.c +1 -0
 	/* Take a snapshot and release the nucleus lock immediately after,
 	   so that dumping /proc/xenomai/stat with lots of entries won't
 	   cause massive jittery. */
@@ -359,6 +374,7 @@ static int stat_seq_open(struct inode *i
 		iter->stat_info[n].ssw = thread->stat.ssw;
 		iter->stat_info[n].csw = thread->stat.csw;
 		iter->stat_info[n].pf = thread->stat.pf;
+		iter->stat_info[n].exec_time = thread->stat.exec_time;
 	}
 
 	xnlock_put_irqrestore(&nklock, s);

_______________________________________________
Xenomai-core mailing list
[email protected]
https://mail.gna.org/listinfo/xenomai-core

#4 lore raw
Quoted 6 lines · Jan Kiszka
Quoted 2 lines

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

The idea is avoid adding even further load to the nklock-protected loop. And we only update the current thread, not each and every.

Yes, but why? I mean, accumulated time so far remains significant even for non-running threads.

Quoted 21 lines
Quoted 19 lines
Quoted 19 lines
nucleus/module.c +1 -0
 	/* Take a snapshot and release the nucleus lock immediately after,
 	   so that dumping /proc/xenomai/stat with lots of entries won't
 	   cause massive jittery. */
@@ -359,6 +374,7 @@ static int stat_seq_open(struct inode *i
 		iter->stat_info[n].ssw = thread->stat.ssw;
 		iter->stat_info[n].csw = thread->stat.csw;
 		iter->stat_info[n].pf = thread->stat.pf;
+		iter->stat_info[n].exec_time = thread->stat.exec_time;
 	}
 
 	xnlock_put_irqrestore(&nklock, s);

_______________________________________________
Xenomai-core mailing list
[email protected]
https://mail.gna.org/listinfo/xenomai-core

Signature · 1 line
Philippe.
author
#5 lore raw
Quoted 9 lines · Philippe Gerum
Quoted 5 lines · Jan Kiszka
Quoted 2 lines

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

The idea is avoid adding even further load to the nklock-protected loop. And we only update the current thread, not each and every.

Yes, but why? I mean, accumulated time so far remains significant even for non-running threads.

Update must only happen for the currently running thread on each cpu, otherwise you skew the stats up.

But looking at the whole code in stat_seq_open() again, I now wonder if that whole routine isn't

a) fragile on task removal and
b) still poorly scaling.

The thread name is only copied by reference, a disappearing instance my cause troubles on printing it. And, instead of holding the lock all the time, shouldn't we better

1. pick some element from the queue and mark it somehow in-use under lock
2. print or copy the entry
3. reacquire the lock to proceed to the next one - after checking if that element happened to be removed from the queue meanwhile (in that case we could abort the output or try to resync)

I'm worrying about a potential nice xeno-top tool polling the stats at high frequency and causing unexpected jitters if there are a significant number tasks registered (think of passive shadows we may create automatically in the future!).

Jan

author
#6 lore raw
Quoted 24 lines · Jan Kiszka
Quoted 8 lines · Philippe Gerum
Quoted 5 lines · Jan Kiszka
Quoted 2 lines

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

The idea is avoid adding even further load to the nklock-protected loop. And we only update the current thread, not each and every.

Yes, but why? I mean, accumulated time so far remains significant even for non-running threads.

Update must only happen for the currently running thread on each cpu, otherwise you skew the stats up.

But looking at the whole code in stat_seq_open() again, I now wonder if that whole routine isn't

a) fragile on task removal and
b) still poorly scaling.

The thread name is only copied by reference, a disappearing instance my cause troubles on printing it. And, instead of holding the lock all the time, shouldn't we better

Quoted 6 lines

1. pick some element from the queue and mark it somehow in-use under lock
2. print or copy the entry
3. reacquire the lock to proceed to the next one - after checking if that element happened to be removed from the queue meanwhile (in that case we could abort the output or try to resync)

Not feasible (threads may not always be prevented from being deleted), but what about this:

- maintain a modification counter for nkpod->threadq
- in our /proc-loops (sched and latency e.g.):
1. take nklock
2. get current counter
3. compare with previous, restart whole loop if not matching
4. copy current element (including the name...)
5. forward element pointer
6. release nklock
7. goto 1 if not end-of-list

As modifications on threadq should be fairly rare, I don't see a risk of looping endlessly here.

Jan

#14 lore raw
In reply to Jan Kiszka
Quoted 8 lines · Jan Kiszka
Quoted 7 lines · Philippe Gerum
Quoted 5 lines · Jan Kiszka
Quoted 2 lines

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

The idea is avoid adding even further load to the nklock-protected loop. And we only update the current thread, not each and every.

Quoted 6 lines
Quoted 2 lines

Yes, but why? I mean, accumulated time so far remains significant even for non-running threads.

Update must only happen for the currently running thread on each cpu, otherwise you skew the stats up.

The point is that you don't want to update there, just peek at the current value for all threads, and indeed update for the current one before that. So we should keep the initial loop, but only read the accumulator in the second one.

Quoted 9 lines

But looking at the whole code in stat_seq_open() again, I now wonder if that whole routine isn't

a) fragile on task removal and
b) still poorly scaling.

The thread name is only copied by reference, a disappearing instance my cause troubles on printing it.

Yes, this will admittedly print garbage if the day really goes rainy (the kernel memory we point at would still be mapped, but occupied by other data). The other option would be to copy the name, but that's unacceptable latency-wise, since we would need to do that under nklock protection.

Quoted 8 lines

And, instead of holding the lock all the
time, shouldn't we better

1. pick some element from the queue and mark it somehow in-use under lock
2. print or copy the entry
3. reacquire the lock to proceed to the next one - after checking if that element happened to be removed from the queue meanwhile

Unfortunately, you can't since the associated memory might be utterly trashed.

Quoted 3 lines

(in that
case we could abort the output or try to resync)

It's the "try to resync" that bothers me. We currently have no sane way to give any guarantee about the consistency of the output sequence here, unless maybe using an array representation for indexing the threads. But I suspect ore issues ahead.

Quoted 5 lines

I'm worrying about a potential nice xeno-top tool polling the stats at high frequency and causing unexpected jitters if there are a significant number tasks registered (think of passive shadows we may create automatically in the future!).

I agree, that's a potential issue. But we should also find an approach that does not cause half-baked or mis-resynced data to be emitted, and the current seqfile interface is not providing an easy way to do this.

Quoted 2 lines

Jan

Signature · 1 line
Philippe.
author
#7 lore raw
In reply to Jan Kiszka
Quoted 51 lines · Jan Kiszka
Quoted 28 lines · Jan Kiszka
Quoted 5 lines · Philippe Gerum
Quoted 3 lines · Jan Kiszka
Quoted 3 lines

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

Quoted 7 lines
Quoted 4 lines

The idea is avoid adding even further load to the nklock-protected loop. And we only update the current thread, not each and every.

Yes, but why? I mean, accumulated time so far remains significant even for non-running threads.

Update must only happen for the currently running thread on each cpu, otherwise you skew the stats up.

But looking at the whole code in stat_seq_open() again, I now wonder if that whole routine isn't

a) fragile on task removal and
b) still poorly scaling.

The thread name is only copied by reference, a disappearing instance my cause troubles on printing it. And, instead of holding the lock all the time, shouldn't we better

Quoted 7 lines

1. pick some element from the queue and mark it somehow in-use under lock
2. print or copy the entry
3. reacquire the lock to proceed to the next one - after checking if that element happened to be removed from the queue meanwhile (in that case we could abort the output or try to resync)

Not feasible (threads may not always be prevented from being deleted), but what about this:

- maintain a modification counter for nkpod->threadq
- in our /proc-loops (sched and latency e.g.):
1. take nklock
2. get current counter
3. compare with previous, restart whole loop if not matching
4. copy current element (including the name...)
5. forward element pointer
6. release nklock
7. goto 1 if not end-of-list

Quoted 2 lines

As modifications on threadq should be fairly rare, I don't see a risk of looping endlessly here.

Here is such a patch + a reworked version of the exec-time stats. Compiles and boots, doesn't cause obvious troubles when running in a loop while threads are created and destroyed in parallel. Nevertheless, a quick hack which may contain bugs.

Jan

---
 include/nucleus/pod.h |    1 
 ksrc/nucleus/module.c |   94 +++++++++++++++++++++++++++++++++-----------------
 ksrc/nucleus/pod.c    |    2 +
 3 files changed, 66 insertions(+), 31 deletions(-)
include/nucleus/pod.h +1 -0
Index: xenomai/include/nucleus/pod.h
===================================================================
@@ -195,6 +195,7 @@ struct xnpod {
     xnsched_t sched[XNARCH_NR_CPUS]; /*!< Per-cpu scheduler slots. */
 
     xnqueue_t threadq;          /*!< All existing threads. */
+    int threadq_rev;            /*!< Modification counter of threadq. */
 
     volatile u_long schedlck;	/*!< Scheduler lock count. */
 
ksrc/nucleus/module.c +63 -31

169 lines not on this page · raw message

ksrc/nucleus/pod.c +2 -0
Index: xenomai/ksrc/nucleus/pod.c
===================================================================
@@ -815,6 +815,7 @@ int xnpod_init_thread(xnthread_t *thread
 	xnlock_get_irqsave(&nklock, s);
 	thread->sched = xnpod_current_sched();
 	appendq(&nkpod->threadq, &thread->glink);
+	nkpod->threadq_rev++;
 	xnpod_suspend_thread(thread, XNDORMANT | (flags & XNSUSP), XN_INFINITE,
 			     NULL);
 	xnlock_put_irqrestore(&nklock, s);
@@ -1225,6 +1226,7 @@ void xnpod_delete_thread(xnthread_t *thr
 	sched = thread->sched;
 
 	removeq(&nkpod->threadq, &thread->glink);
+	nkpod->threadq_rev++;
 
 	if (!testbits(thread->status, XNTHREAD_BLOCK_BITS)) {
 		if (testbits(thread->status, XNREADY)) {
---
 include/nucleus/pod.h    |   28 ++++++++++++++++++++++++++++
 include/nucleus/thread.h |    1 +
 ksrc/nucleus/module.c    |   26 +++++++++++++++++++++-----
 ksrc/nucleus/pod.c       |    5 +++++
 ksrc/nucleus/thread.c    |    1 +
 5 files changed, 56 insertions(+), 5 deletions(-)
nucleus/thread.h renamed from nucleus/thread.h.orig +1 -0
Index: include/nucleus/thread.h
===================================================================
@@ -152,6 +152,7 @@ typedef struct xnthread {
 	unsigned long csw;	/* Context switches (includes
 				   secondary -> primary switches) */
 	unsigned long pf;	/* Number of page faults */
+	xnticks_t exec_time;	/* Accumulated execution time (ticks) */
     } stat;
 #endif /* CONFIG_XENO_OPT_STATS */
 
nucleus/pod.h renamed from nucleus/pod.h.orig +28 -0
Index: include/nucleus/pod.h
===================================================================
@@ -145,6 +145,10 @@ typedef struct xnsched {
 
     xnthread_t rootcb;          /*!< Root thread control block. */
 
+#ifdef CONFIG_XENO_OPT_STATS
+    xnticks_t last_csw;         /*!< Last context switch (ticks). */
+#endif /* CONFIG_XENO_OPT_STATS */
+
 } xnsched_t;
 
 #ifdef CONFIG_SMP
@@ -545,6 +549,30 @@ static inline void xnpod_delete_self (vo
     xnpod_delete_thread(xnpod_current_thread());
 }
 
+#ifdef CONFIG_XENO_OPT_STATS
+static inline void xnpod_acc_exec_time(xnthread_t *thread)
+{
+    xnsched_t *sched = thread->sched;
+    xnticks_t now = xntimer_get_rawclock();
+
+    thread->stat.exec_time += now - sched->last_csw;
+    sched->last_csw = now;
+}
+
+static inline void xnpod_update_csw_date(xnsched_t *sched)
+{
+    sched->last_csw = xntimer_get_rawclock();
+}
+#else /* !CONFIG_XENO_OPT_STATS */
+static inline void xnpod_acc_exec_time(xnthread_t *thread)
+{
+}
+
+static inline void xnpod_update_csw_date(xnsched_t *sched)
+{
+}
+#endif /* CONFIG_XENO_OPT_STATS */
+
 #ifdef __cplusplus
 }
 #endif
nucleus/thread.c renamed from nucleus/thread.c.orig +1 -0
Index: ksrc/nucleus/thread.c
===================================================================
@@ -90,6 +90,7 @@ int xnthread_init(xnthread_t *thread,
 	thread->stat.ssw = 0;
 	thread->stat.csw = 0;
 	thread->stat.pf = 0;
+	thread->stat.exec_time = 0;
 #endif /* CONFIG_XENO_OPT_STATS */
 
 	/* These will be filled by xnpod_start_thread() */
nucleus/pod.c renamed from nucleus/pod.c.orig +5 -0
Index: ksrc/nucleus/pod.c
===================================================================
@@ -669,6 +669,9 @@ static inline void xnpod_switch_zombie(x
 
 	xnthread_cleanup_tcb(threadout);
 
+	/* no need to update stats of dying thread */
+	xnpod_update_csw_date(sched);
+
 	xnarch_finalize_and_switch(xnthread_archtcb(threadout),
 				   xnthread_archtcb(threadin));
 
@@ -2433,6 +2436,7 @@ void xnpod_schedule(void)
 		xnarch_enter_root(xnthread_archtcb(threadin));
 	}
 
+	xnpod_acc_exec_time(threadout);
 	xnthread_inc_csw(threadin);
 
 	xnarch_switch_to(xnthread_archtcb(threadout),
@@ -2604,6 +2608,7 @@ void xnpod_schedule_runnable(xnthread_t 
 		nkpod->schedhook(runthread, XNREADY);
 #endif /* __XENO_SIM__ */
 
+	xnpod_acc_exec_time(runthread);
 	xnthread_inc_csw(threadin);
 
 	xnarch_switch_to(xnthread_archtcb(runthread),
nucleus/module.c renamed from nucleus/module.c.orig +21 -5
Index: ksrc/nucleus/module.c
===================================================================
@@ -269,6 +269,7 @@ struct stat_seq_iterator {
 		unsigned long ssw;
 		unsigned long csw;
 		unsigned long pf;
+		xnticks_t exec_time;
 	} stat_info[1];
 };
 
@@ -309,13 +310,17 @@ static void stat_seq_stop(struct seq_fil
 static int stat_seq_show(struct seq_file *seq, void *v)
 {
 	if (v == SEQ_START_TOKEN)
-		seq_printf(seq, "%-3s  %-6s %-10s %-10s %-4s  %-8s  %s\n",
-			   "CPU", "PID", "MSW", "CSW", "PF", "STAT", "NAME");
+		seq_printf(seq, "%-3s  %-6s %-10s %-10s %-4s  %-8s  %12s"
+			   "  %s\n",
+			   "CPU", "PID", "MSW", "CSW", "PF", "STAT", "TIME",
+			   "NAME");
 	else {
 		struct stat_seq_info *p = (struct stat_seq_info *)v;
-		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %s\n",
+		unsigned long long exec_time = xnpod_ticks2ns(p->exec_time);
+		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %12llu"
+			   "  %s\n",
 			   p->cpu, p->pid, p->ssw, p->csw, p->pf, p->status,
-			   p->name);
+			   xnarch_ulldiv(exec_time, 1000, NULL), p->name);
 	}
 
 	return 0;
@@ -333,7 +338,7 @@ static int stat_seq_open(struct inode *i
 	struct stat_seq_iterator *iter = NULL;
 	struct seq_file *seq;
 	xnholder_t *holder;
-	int err, count, rev;
+	int err, count, rev, cpu;
 	spl_t s;
 
 	if (!nkpod)
@@ -365,6 +370,16 @@ static int stat_seq_open(struct inode *i
 
 	iter->nentries = 0;
 
+	/* update exec-time stats of currently running threads */
+	for_each_online_cpu(cpu) {
+		xnsched_t *sched;
+
+		xnlock_get_irqsave(&nklock, s);
+		sched = xnpod_sched_slot(cpu);
+		xnpod_acc_exec_time(sched->runthread);
+		xnlock_put_irqrestore(&nklock, s);
+	}
+
 	/* Take a snapshot element-wise, restart if something changes
 	   underneath us. */
 
@@ -389,6 +404,7 @@ static int stat_seq_open(struct inode *i
 		iter->stat_info[n].ssw = thread->stat.ssw;
 		iter->stat_info[n].csw = thread->stat.csw;
 		iter->stat_info[n].pf = thread->stat.pf;
+		iter->stat_info[n].exec_time = thread->stat.exec_time;
 
 		holder = nextq(&nkpod->threadq, holder);
 
#15 lore raw
In reply to Philippe Gerum
Quoted 4 lines · Philippe Gerum
Quoted 4 lines
nucleus/pod.h +4 -0
+#ifdef CONFIG_XENO_OPT_STATS
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
Quoted 8 lines

nucleus/pod.h static inline void xnpod_delete_self (vo · line 558

Quoted 4 lines
nucleus/pod.h +4 -0
+    xnticks_t now = xntimer_get_rawclock();
+    threadout->stat.exec_time += now - sched->last_csw;
+    sched->last_csw = now;
+}

It would be better to only pass the thread pointer, then use the thread->sched member. This would clearly explain the relationship between both, and prevent any bugous attempt at mixing things.

Beware, when xnpod_schedule is called from within xnpod_migrate_thread(), the sched pointer of the switched out thread is the one of the destination cpu. So, passing the sched and threadout pointers to xnpod_acc_exec_time is safer.

Signature · 1 line
					    Gilles Chanteperdrix.
#16 lore raw
Quoted 14 lines · Gilles Chanteperdrix
Quoted 4 lines · Philippe Gerum
Quoted 4 lines
nucleus/pod.h +4 -0
+#ifdef CONFIG_XENO_OPT_STATS
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
Quoted 8 lines

nucleus/pod.h static inline void xnpod_delete_self (vo · line 558

Quoted 4 lines
nucleus/pod.h +4 -0
+    xnticks_t now = xntimer_get_rawclock();
+    threadout->stat.exec_time += now - sched->last_csw;
+    sched->last_csw = now;
+}

It would be better to only pass the thread pointer, then use the thread->sched member. This would clearly explain the relationship between both, and prevent any bugous attempt at mixing things.

Quoted 4 lines

Beware, when xnpod_schedule is called from within xnpod_migrate_thread(), the sched pointer of the switched out thread is the one of the destination cpu. So, passing the sched and threadout pointers to xnpod_acc_exec_time is safer.

Nope, the nklock is held by the migrating thread all along until the switch has actually occured.

Quoted 2 lines
Signature · 1 line
Philippe.
#9 lore raw
In reply to Jan Kiszka
Quoted 45 lines · Jan Kiszka
Quoted 24 lines · Jan Kiszka
Quoted 8 lines · Philippe Gerum
Quoted 5 lines · Jan Kiszka
Quoted 2 lines

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

The idea is avoid adding even further load to the nklock-protected loop. And we only update the current thread, not each and every.

Yes, but why? I mean, accumulated time so far remains significant even for non-running threads.

Update must only happen for the currently running thread on each cpu, otherwise you skew the stats up.

But looking at the whole code in stat_seq_open() again, I now wonder if that whole routine isn't

a) fragile on task removal and
b) still poorly scaling.

The thread name is only copied by reference, a disappearing instance my cause troubles on printing it. And, instead of holding the lock all the time, shouldn't we better

Quoted 6 lines

1. pick some element from the queue and mark it somehow in-use under lock
2. print or copy the entry
3. reacquire the lock to proceed to the next one - after checking if that element happened to be removed from the queue meanwhile (in that case we could abort the output or try to resync)

Not feasible (threads may not always be prevented from being deleted), but what about this:

- maintain a modification counter for nkpod->threadq
- in our /proc-loops (sched and latency e.g.):
1. take nklock
2. get current counter
3. compare with previous, restart whole loop if not matching
4. copy current element (including the name...)
5. forward element pointer
6. release nklock
7. goto 1 if not end-of-list

Quoted 3 lines

As modifications on threadq should be fairly rare, I don't see a risk of looping endlessly here.

The more I think of it, the more I'm convinced that we are trying to tweak /proc/xenomai/stats for the wrong purpose. Actually, some xeno_top tool would rather need the equivalent of individual /proc/<pid> files, thus reducing the contention on access, and the need for weird grepping on the output. e.g. /proc/xenomai/threads/<pid> could emit per-thread data in some easily greppable form by a user-space tool.

Quoted 2 lines

Jan

Signature · 1 line
Philippe.
author
#10 lore raw
Quoted 48 lines · Philippe Gerum
Quoted 43 lines · Jan Kiszka
Quoted 23 lines · Jan Kiszka
Quoted 8 lines · Philippe Gerum
Quoted 5 lines · Jan Kiszka
Quoted 2 lines

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

The idea is avoid adding even further load to the nklock-protected loop. And we only update the current thread, not each and every.

Yes, but why? I mean, accumulated time so far remains significant even for non-running threads.

Update must only happen for the currently running thread on each cpu, otherwise you skew the stats up.

But looking at the whole code in stat_seq_open() again, I now wonder if that whole routine isn't

a) fragile on task removal and
b) still poorly scaling.

The thread name is only copied by reference, a disappearing instance my cause troubles on printing it. And, instead of holding the lock all the time, shouldn't we better

Quoted 6 lines

1. pick some element from the queue and mark it somehow in-use under lock
2. print or copy the entry
3. reacquire the lock to proceed to the next one - after checking if that element happened to be removed from the queue meanwhile (in that case we could abort the output or try to resync)

Not feasible (threads may not always be prevented from being deleted), but what about this:

- maintain a modification counter for nkpod->threadq
- in our /proc-loops (sched and latency e.g.):
1. take nklock
2. get current counter
3. compare with previous, restart whole loop if not matching
4. copy current element (including the name...)
5. forward element pointer
6. release nklock
7. goto 1 if not end-of-list

Quoted 3 lines

As modifications on threadq should be fairly rare, I don't see a risk of looping endlessly here.

Quoted 6 lines

The more I think of it, the more I'm convinced that we are trying to tweak /proc/xenomai/stats for the wrong purpose. Actually, some xeno_top tool would rather need the equivalent of individual /proc/<pid> files, thus reducing the contention on access, and the need for weird grepping on the output. e.g. /proc/xenomai/threads/<pid> could emit per-thread data in some easily greppable form by a user-space tool.

Far too complex in my eyes for this simple purpose (what's the pid of kernel-only threads?) - and we need to solve the latency problem of /sched and /stat anyway.

Jan

author
#8 lore raw
In reply to Jan Kiszka
Quoted 3 lines · Jan Kiszka

Here is such a patch + a reworked version of the exec-time stats. Compiles and boots, doesn't cause obvious troubles when running in a loop while threads are created and destroyed in parallel. Nevertheless, a quick hack which may contain bugs.

As expected, there were bugs. This version of the exec-time patch always uses TSC for timestamping and the related correct conversion function. And it doesn't try to update the exec-time across CPUs as the TSCs may not be in sync. Thus, when there is no scheduling activity on some CPU, we may not see progress in /stat. But this is the least invasive way, and we typically DO have activity anyway.

Jan

---
 include/nucleus/pod.h    |   28 ++++++++++++++++++++++++++++
 include/nucleus/thread.h |    1 +
 ksrc/nucleus/module.c    |   20 ++++++++++++++++----
 ksrc/nucleus/pod.c       |    5 +++++
 ksrc/nucleus/thread.c    |    1 +
 5 files changed, 51 insertions(+), 4 deletions(-)
nucleus/thread.h renamed from nucleus/thread.h.orig +1 -0
Index: include/nucleus/thread.h
===================================================================
@@ -152,6 +152,7 @@ typedef struct xnthread {
 	unsigned long csw;	/* Context switches (includes
 				   secondary -> primary switches) */
 	unsigned long pf;	/* Number of page faults */
+	xnticks_t exec_time;	/* Accumulated execution time (ticks) */
     } stat;
 #endif /* CONFIG_XENO_OPT_STATS */
 
nucleus/pod.h renamed from nucleus/pod.h.orig +28 -0
Index: include/nucleus/pod.h
===================================================================
@@ -145,6 +145,10 @@ typedef struct xnsched {
 
     xnthread_t rootcb;          /*!< Root thread control block. */
 
+#ifdef CONFIG_XENO_OPT_STATS
+    xnticks_t last_csw;         /*!< Last context switch (ticks). */
+#endif /* CONFIG_XENO_OPT_STATS */
+
 } xnsched_t;
 
 #ifdef CONFIG_SMP
@@ -545,6 +549,30 @@ static inline void xnpod_delete_self (vo
     xnpod_delete_thread(xnpod_current_thread());
 }
 
+#ifdef CONFIG_XENO_OPT_STATS
+static inline void xnpod_acc_exec_time(xnthread_t *thread)
+{
+    xnsched_t *sched = thread->sched;
+    xnticks_t now = xnarch_get_cpu_tsc();
+
+    thread->stat.exec_time += now - sched->last_csw;
+    sched->last_csw = now;
+}
+
+static inline void xnpod_update_csw_date(xnsched_t *sched)
+{
+    sched->last_csw = xnarch_get_cpu_tsc();
+}
+#else /* !CONFIG_XENO_OPT_STATS */
+static inline void xnpod_acc_exec_time(xnthread_t *thread)
+{
+}
+
+static inline void xnpod_update_csw_date(xnsched_t *sched)
+{
+}
+#endif /* CONFIG_XENO_OPT_STATS */
+
 #ifdef __cplusplus
 }
 #endif
nucleus/thread.c renamed from nucleus/thread.c.orig +1 -0
Index: ksrc/nucleus/thread.c
===================================================================
@@ -90,6 +90,7 @@ int xnthread_init(xnthread_t *thread,
 	thread->stat.ssw = 0;
 	thread->stat.csw = 0;
 	thread->stat.pf = 0;
+	thread->stat.exec_time = 0;
 #endif /* CONFIG_XENO_OPT_STATS */
 
 	/* These will be filled by xnpod_start_thread() */
nucleus/pod.c renamed from nucleus/pod.c.orig +5 -0
Index: ksrc/nucleus/pod.c
===================================================================
@@ -669,6 +669,9 @@ static inline void xnpod_switch_zombie(x
 
 	xnthread_cleanup_tcb(threadout);
 
+	/* no need to update stats of dying thread */
+	xnpod_update_csw_date(sched);
+
 	xnarch_finalize_and_switch(xnthread_archtcb(threadout),
 				   xnthread_archtcb(threadin));
 
@@ -2433,6 +2436,7 @@ void xnpod_schedule(void)
 		xnarch_enter_root(xnthread_archtcb(threadin));
 	}
 
+	xnpod_acc_exec_time(threadout);
 	xnthread_inc_csw(threadin);
 
 	xnarch_switch_to(xnthread_archtcb(threadout),
@@ -2604,6 +2608,7 @@ void xnpod_schedule_runnable(xnthread_t 
 		nkpod->schedhook(runthread, XNREADY);
 #endif /* __XENO_SIM__ */
 
+	xnpod_acc_exec_time(runthread);
 	xnthread_inc_csw(threadin);
 
 	xnarch_switch_to(xnthread_archtcb(runthread),
nucleus/module.c renamed from nucleus/module.c.orig +16 -4
Index: ksrc/nucleus/module.c
===================================================================
@@ -269,6 +269,7 @@ struct stat_seq_iterator {
 		unsigned long ssw;
 		unsigned long csw;
 		unsigned long pf;
+		xnticks_t exec_time;
 	} stat_info[1];
 };
 
@@ -309,13 +310,17 @@ static void stat_seq_stop(struct seq_fil
 static int stat_seq_show(struct seq_file *seq, void *v)
 {
 	if (v == SEQ_START_TOKEN)
-		seq_printf(seq, "%-3s  %-6s %-10s %-10s %-4s  %-8s  %s\n",
-			   "CPU", "PID", "MSW", "CSW", "PF", "STAT", "NAME");
+		seq_printf(seq, "%-3s  %-6s %-10s %-10s %-4s  %-8s  %12s"
+			   "  %s\n",
+			   "CPU", "PID", "MSW", "CSW", "PF", "STAT", "TIME",
+			   "NAME");
 	else {
 		struct stat_seq_info *p = (struct stat_seq_info *)v;
-		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %s\n",
+		unsigned long long exec_time = xnarch_tsc_to_ns(p->exec_time);
+		seq_printf(seq, "%3u  %-6d %-10lu %-10lu %-4lu  %.8lx  %12llu"
+			   "  %s\n",
 			   p->cpu, p->pid, p->ssw, p->csw, p->pf, p->status,
-			   p->name);
+			   xnarch_ulldiv(exec_time, 1000, NULL), p->name);
 	}
 
 	return 0;
@@ -365,6 +370,12 @@ static int stat_seq_open(struct inode *i
 
 	iter->nentries = 0;
 
+	/* Update exec-time stats at least on current CPU, unsync'ed TSCs
+	   prevent cross-CPU update yet. */
+	xnlock_get_irqsave(&nklock, s);
+	xnpod_acc_exec_time(xnpod_current_thread());
+	xnlock_put_irqrestore(&nklock, s);
+
 	/* Take a snapshot element-wise, restart if something changes
 	   underneath us. */
 
@@ -389,6 +400,7 @@ static int stat_seq_open(struct inode *i
 		iter->stat_info[n].ssw = thread->stat.ssw;
 		iter->stat_info[n].csw = thread->stat.csw;
 		iter->stat_info[n].pf = thread->stat.pf;
+		iter->stat_info[n].exec_time = thread->stat.exec_time;
 
 		holder = nextq(&nkpod->threadq, holder);
 
#11 lore raw
In reply to Jan Kiszka
Quoted 56 lines · Jan Kiszka
Quoted 48 lines · Philippe Gerum
Quoted 43 lines · Jan Kiszka
Quoted 23 lines · Jan Kiszka
Quoted 8 lines · Philippe Gerum
Quoted 5 lines · Jan Kiszka
Quoted 2 lines

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

The idea is avoid adding even further load to the nklock-protected loop. And we only update the current thread, not each and every.

Yes, but why? I mean, accumulated time so far remains significant even for non-running threads.

Update must only happen for the currently running thread on each cpu, otherwise you skew the stats up.

But looking at the whole code in stat_seq_open() again, I now wonder if that whole routine isn't

a) fragile on task removal and
b) still poorly scaling.

The thread name is only copied by reference, a disappearing instance my cause troubles on printing it. And, instead of holding the lock all the time, shouldn't we better

Quoted 6 lines

1. pick some element from the queue and mark it somehow in-use under lock
2. print or copy the entry
3. reacquire the lock to proceed to the next one - after checking if that element happened to be removed from the queue meanwhile (in that case we could abort the output or try to resync)

Not feasible (threads may not always be prevented from being deleted), but what about this:

- maintain a modification counter for nkpod->threadq
- in our /proc-loops (sched and latency e.g.):
1. take nklock
2. get current counter
3. compare with previous, restart whole loop if not matching
4. copy current element (including the name...)
5. forward element pointer
6. release nklock
7. goto 1 if not end-of-list

Quoted 3 lines

As modifications on threadq should be fairly rare, I don't see a risk of looping endlessly here.

Quoted 6 lines

The more I think of it, the more I'm convinced that we are trying to tweak /proc/xenomai/stats for the wrong purpose. Actually, some xeno_top tool would rather need the equivalent of individual /proc/<pid> files, thus reducing the contention on access, and the need for weird grepping on the output. e.g. /proc/xenomai/threads/<pid> could emit per-thread data in some easily greppable form by a user-space tool.

Quoted 2 lines

Far too complex in my eyes for this simple purpose (what's the pid of kernel-only threads?) -

It's symbolic name. It's not a matter of simplicity, your patch for that purpose is rather complex already.

Quoted 2 lines

and we need to solve the latency problem of
/sched and /stat anyway.

That's separate issues. Solving the scalability issue
of /proc/xenomai/stats and friends does not improve their usability in the context of user-space tools monitoring thread activity. E.g. how are we going to extend the reporting about such activity if need be, adding yet another column to /stats and /sched?

Quoted 2 lines

Jan

Signature · 1 line
Philippe.
author
#12 lore raw
Quoted 55 lines · Philippe Gerum
Quoted 54 lines · Jan Kiszka
Quoted 53 lines · Philippe Gerum
Quoted 43 lines · Jan Kiszka
Quoted 23 lines · Jan Kiszka
Quoted 8 lines · Philippe Gerum
Quoted 5 lines · Jan Kiszka
Quoted 2 lines

We could do that from the current loop below, given that the accumulation routine is changed to use thread->sched implicitely.

The idea is avoid adding even further load to the nklock-protected loop. And we only update the current thread, not each and every.

Yes, but why? I mean, accumulated time so far remains significant even for non-running threads.

Update must only happen for the currently running thread on each cpu, otherwise you skew the stats up.

But looking at the whole code in stat_seq_open() again, I now wonder if that whole routine isn't

a) fragile on task removal and
b) still poorly scaling.

The thread name is only copied by reference, a disappearing instance my cause troubles on printing it. And, instead of holding the lock all the time, shouldn't we better

Quoted 6 lines

1. pick some element from the queue and mark it somehow in-use under lock
2. print or copy the entry
3. reacquire the lock to proceed to the next one - after checking if that element happened to be removed from the queue meanwhile (in that case we could abort the output or try to resync)

Not feasible (threads may not always be prevented from being deleted), but what about this:

- maintain a modification counter for nkpod->threadq
- in our /proc-loops (sched and latency e.g.):
1. take nklock
2. get current counter
3. compare with previous, restart whole loop if not matching
4. copy current element (including the name...)
5. forward element pointer
6. release nklock
7. goto 1 if not end-of-list

Quoted 3 lines

As modifications on threadq should be fairly rare, I don't see a risk of looping endlessly here.

The more I think of it, the more I'm convinced that we are trying to tweak /proc/xenomai/stats for the wrong purpose. Actually, some xeno_top tool would rather need the equivalent of individual /proc/<pid> files, thus reducing the contention on access, and the need for weird grepping on the output. e.g. /proc/xenomai/threads/<pid> could emit per-thread data in some easily greppable form by a user-space tool.

Quoted 5 lines
Quoted 2 lines

Far too complex in my eyes for this simple purpose (what's the pid of kernel-only threads?) -

It's symbolic name. It's not a matter of simplicity, your patch for that purpose is rather complex already.

Haven't measured, but the amount of code added for collecting the data and printing just another column should be marginal as yet. Introducing a new infrastructure for more /proc subdirs with probably multiple entries would certainly cost more.

Quoted 9 lines
Quoted 3 lines

and we need to solve the latency problem of
/sched and /stat anyway.

That's separate issues. Solving the scalability issue
of /proc/xenomai/stats and friends does not improve their usability in the context of user-space tools monitoring thread activity. E.g. how are we going to extend the reporting about such activity if need be, adding yet another column to /stats and /sched?

As long as there are no stand-alone tools relying on the layout, just only our own ones - fairly simple. But before speculating more, I will have a look now how simple such tool/script may actually be.

Jan

#17 lore raw
In reply to Philippe Gerum
Quoted 15 lines · Philippe Gerum
Quoted 14 lines · Gilles Chanteperdrix
Quoted 12 lines · Philippe Gerum

nucleus/pod.h static inline void xnpod_delete_self (vo · line 558

Quoted 8 lines
nucleus/pod.h +8 -0
+#ifdef CONFIG_XENO_OPT_STATS
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
+    xnticks_t now = xntimer_get_rawclock();
+    threadout->stat.exec_time += now - sched->last_csw;
+    sched->last_csw = now;
+}

It would be better to only pass the thread pointer, then use the thread->sched member. This would clearly explain the relationship between both, and prevent any bugous attempt at mixing things.

Quoted 7 lines
Quoted 4 lines

Beware, when xnpod_schedule is called from within xnpod_migrate_thread(), the sched pointer of the switched out thread is the one of the destination cpu. So, passing the sched and threadout pointers to xnpod_acc_exec_time is safer.

Nope, the nklock is held by the migrating thread all along until the switch has actually occured.

xnpod_acc_exec_time() is called from within xnpod_schedule() that is called from within xnpod_migrate_thread() at a time where threadout->sched is the wrong pointer.

Signature · 1 line
					    Gilles Chanteperdrix.
author
#18 lore raw
Quoted 21 lines · Gilles Chanteperdrix
Quoted 20 lines · Philippe Gerum
Quoted 18 lines · Gilles Chanteperdrix
Quoted 4 lines · Philippe Gerum
Quoted 4 lines
nucleus/pod.h +4 -0
+#ifdef CONFIG_XENO_OPT_STATS
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
Quoted 8 lines

nucleus/pod.h static inline void xnpod_delete_self (vo · line 558

Quoted 4 lines
nucleus/pod.h +4 -0
+    xnticks_t now = xntimer_get_rawclock();
+    threadout->stat.exec_time += now - sched->last_csw;
+    sched->last_csw = now;
+}

It would be better to only pass the thread pointer, then use the thread->sched member. This would clearly explain the relationship between both, and prevent any bugous attempt at mixing things.

Beware, when xnpod_schedule is called from within xnpod_migrate_thread(), the sched pointer of the switched out thread is the one of the destination cpu. So, passing the sched and threadout pointers to xnpod_acc_exec_time is safer.

Quoted 7 lines
Quoted 2 lines

Nope, the nklock is held by the migrating thread all along until the switch has actually occured.

xnpod_acc_exec_time() is called from within xnpod_schedule() that is called from within xnpod_migrate_thread() at a time where threadout->sched is the wrong pointer.

True, will revert this.

Thanks,
Jan

#13 lore raw
In reply to Jan Kiszka
Quoted 11 lines · Jan Kiszka
Quoted 5 lines
Quoted 2 lines

Far too complex in my eyes for this simple purpose (what's the pid of kernel-only threads?) -

It's symbolic name. It's not a matter of simplicity, your patch for that purpose is rather complex already.

Haven't measured, but the amount of code added for collecting the data and printing just another column should be marginal as yet. Introducing a new infrastructure for more /proc subdirs with probably multiple entries would certainly cost more.

This has no cost on the real-time side of things. In contrast, scanning the threadq within /proc handlers like /stats and /sched are doing now, does have some noticeable cost. By having separate per-thread entries, we provide the necessary infrastructure for exporting more raw statistical data to user-space tools, in a format which would be script-friendly.

Quoted 16 lines
Quoted 9 lines
Quoted 3 lines

and we need to solve the latency problem of
/sched and /stat anyway.

That's separate issues. Solving the scalability issue
of /proc/xenomai/stats and friends does not improve their usability in the context of user-space tools monitoring thread activity. E.g. how are we going to extend the reporting about such activity if need be, adding yet another column to /stats and /sched?

As long as there are no stand-alone tools relying on the layout, just only our own ones - fairly simple. But before speculating more, I will have a look now how simple such tool/script may actually be.

Jan

Signature · 1 line
Philippe.
#19 lore raw
In reply to Gilles Chanteperdrix
Quoted 21 lines · Gilles Chanteperdrix
Quoted 20 lines · Philippe Gerum
Quoted 18 lines · Gilles Chanteperdrix
Quoted 4 lines · Philippe Gerum
Quoted 4 lines
nucleus/pod.h +4 -0
+#ifdef CONFIG_XENO_OPT_STATS
+static inline void xnpod_acc_exec_time(xnsched_t *sched,
+                                       xnthread_t *threadout)
+{
Quoted 8 lines

nucleus/pod.h static inline void xnpod_delete_self (vo · line 558

Quoted 4 lines
nucleus/pod.h +4 -0
+    xnticks_t now = xntimer_get_rawclock();
+    threadout->stat.exec_time += now - sched->last_csw;
+    sched->last_csw = now;
+}

It would be better to only pass the thread pointer, then use the thread->sched member. This would clearly explain the relationship between both, and prevent any bugous attempt at mixing things.

Beware, when xnpod_schedule is called from within xnpod_migrate_thread(), the sched pointer of the switched out thread is the one of the destination cpu. So, passing the sched and threadout pointers to xnpod_acc_exec_time is safer.

Quoted 6 lines
Quoted 2 lines

Nope, the nklock is held by the migrating thread all along until the switch has actually occured.

xnpod_acc_exec_time() is called from within xnpod_schedule() that is called from within xnpod_migrate_thread() at a time where threadout->sched is the wrong pointer.

Gasp. Unfortunately, we are trapped by an exception case, and I don't see any better approach.

Quoted 1 line
Signature · 1 line
Philippe.
#20 lore raw
Quoted 6 lines · Philippe Gerum
Quoted 3 lines

xnpod_acc_exec_time() is called from within xnpod_schedule() that is called from within xnpod_migrate_thread() at a time where threadout->sched is the wrong pointer.

Gasp. Unfortunately, we are trapped by an exception case, and I don't see any better approach.

We could reimplement an xnpod_migrate_thread() which suspend the calling thread and wake up a server thread on the destination CPU, this server thread migrating the suspended thread by changing its sched pointer outside of xnpod_schedule().

Or we can get rid of xnpod_migrate_thread(), it is currently not used by any skin.

Signature · 1 line
					    Gilles Chanteperdrix.
#21 lore raw
Quoted 5 lines · Gilles Chanteperdrix
Quoted 4 lines · Philippe Gerum
Quoted 3 lines

xnpod_acc_exec_time() is called from within xnpod_schedule() that is called from within xnpod_migrate_thread() at a time where threadout->sched is the wrong pointer.

Quoted 8 lines
Quoted 2 lines

Gasp. Unfortunately, we are trapped by an exception case, and I don't see any better approach.

We could reimplement an xnpod_migrate_thread() which suspend the calling thread and wake up a server thread on the destination CPU, this server thread migrating the suspended thread by changing its sched pointer outside of xnpod_schedule().

No, really. A small hack to get per-thread accounting should not trigger a storm of fundamental changes like this.

Quoted 3 lines

Or we can get rid of xnpod_migrate_thread(), it is currently not used by any skin.

It's a fundamental feature for placing SMP jobs, and kernel-based Xenomai threads could not rely on sched_setscheduler() to do it. Let's keep this service, and simply pass the sched pointer to the accumulation routine; I was wrong initially suggesting the opposite. IOW, let's avoid smashing a squadron of flies with nukes...

Signature · 1 line
Philippe.