gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1389Shell 137Markdown 80Rust 802602 Sutichaykuna

⎇ main docs/evidencia/tasas-kernel-2026-08-29/bench_connector.c C · 312 siq'ikuna · 12.2 KB Ñawpa willay Pi imata llamkarqan ch'uya

tasas: los 4 experimentos que §9 dejó nombrados, medidos con root 849c04144 · Sergio ·

1/* bench_connector.c — proc connector (netlink) y taskstats contra sondear /proc.
2 *
3 * Cierra el experimento 1 de SDD 25 §9, que quedo sin medir por falta de CAP_NET_ADMIN.
4 * Requiere root. Tres fases:
5 *   A. LATENCIA   fork+_exit+waitpid  vs  fork+_exit+esperar el evento del connector.
6 *   B. CEGUERA    procesos cortos vistos por el connector vs vistos sondeando /proc cada 100 ms.
7 *   C. TASKSTATS  una consulta binaria por genetlink vs parsear /proc/<pid>/stat.
8 *
9 * Se compila con: cc -O2 -o bench_connector bench_connector.c
10 */
11#define _GNU_SOURCE
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <unistd.h>
16#include <fcntl.h>
17#include <time.h>
18#include <errno.h>
19#include <dirent.h>
20#include <sys/socket.h>
21#include <sys/wait.h>
22#include <sys/resource.h>
23#include <poll.h>
24#include <linux/netlink.h>
25#include <linux/connector.h>
26#include <linux/cn_proc.h>
27#include <linux/genetlink.h>
28#include <linux/taskstats.h>
29
30static volatile long sink;
31static inline double now_ns(void){ struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return t.tv_sec*1e9+t.tv_nsec; }
32static int cmpd(const void*a,const void*b){ double x=*(const double*)a,y=*(const double*)b; return (x>y)-(x<y); }
33static void report(const char*n,double*r,int c,long it){ qsort(r,c,sizeof(double),cmpd);
34  printf("%-44s %10.1f %10.1f   (n=%ld)\n",n,r[0],r[c/2],it); fflush(stdout); }
35#define ROUNDS 7
36
37/* ---------------------------------------------------------------- proc connector */
38
39static int cn_open(void){
40  int s = socket(PF_NETLINK, SOCK_DGRAM|SOCK_CLOEXEC, NETLINK_CONNECTOR);
41  if(s<0) return -1;
42  struct sockaddr_nl a; memset(&a,0,sizeof a);
43  a.nl_family=AF_NETLINK; a.nl_groups=CN_IDX_PROC; a.nl_pid=getpid();
44  if(bind(s,(struct sockaddr*)&a,sizeof a)<0){ close(s); return -1; }
45
46  struct {
47    struct nlmsghdr nl;
48    struct cn_msg cn;
49    enum proc_cn_mcast_op op;
50  } __attribute__((packed)) m;
51  memset(&m,0,sizeof m);
52  m.nl.nlmsg_len=sizeof m; m.nl.nlmsg_pid=getpid(); m.nl.nlmsg_type=NLMSG_DONE;
53  m.cn.id.idx=CN_IDX_PROC; m.cn.id.val=CN_VAL_PROC; m.cn.len=sizeof(enum proc_cn_mcast_op);
54  m.op=PROC_CN_MCAST_LISTEN;
55  if(send(s,&m,sizeof m,0)<0){ close(s); return -1; }
56  /* El multicast del connector NO reintenta: si el buffer se llena, el evento se PIERDE (ENOBUFS)
57   * y el que espera ese pid se queda esperando para siempre. Se agranda el buffer y, aun asi, la
58   * espera lleva plazo (abajo). Es una propiedad del mecanismo, no un detalle de este programa. */
59  int rb=4*1024*1024; setsockopt(s,SOL_SOCKET,SO_RCVBUFFORCE,&rb,sizeof rb);
60  return s;
61}
62
63/* Lee un evento. Devuelve el proc_event, o NULL si el mensaje no lo era. */
64static struct proc_event *cn_read(int s, char *buf, size_t n){
65  ssize_t r = recv(s, buf, n, 0);
66  if(r<=0) return NULL;
67  struct nlmsghdr *nlh=(struct nlmsghdr*)buf;
68  if(!NLMSG_OK(nlh,(size_t)r)) return NULL;
69  struct cn_msg *cn=(struct cn_msg*)NLMSG_DATA(nlh);
70  return (struct proc_event*)cn->data;
71}
72
73/* Vacia lo que haya en el socket sin bloquear. */
74static void cn_drain(int s){
75  int fl=fcntl(s,F_GETFL,0); fcntl(s,F_SETFL,fl|O_NONBLOCK);
76  char b[4096]; while(recv(s,b,sizeof b,0)>0){}
77  fcntl(s,F_SETFL,fl);
78}
79
80static void fase_a(int s){
81  printf("\n### A. Latencia de notificacion: waitpid contra evento del connector\n");
82  printf("%-44s %10s %10s\n","BENCH (ns por proceso)","min","mediana");
83  const long N=200;
84
85  double r[ROUNDS];
86  for(int q=0;q<ROUNDS;q++){
87    double t0=now_ns();
88    for(long i=0;i<N;i++){ pid_t p=fork(); if(p==0) _exit(0); int st; waitpid(p,&st,0); }
89    r[q]=(now_ns()-t0)/(double)N;
90  }
91  report("fork+_exit+waitpid (SIGCHLD, la base)",r,ROUNDS,N);
92
93  char buf[8192];
94  long perdidos=0;
95  for(int q=0;q<ROUNDS;q++){
96    cn_drain(s);
97    double t0=now_ns();
98    for(long i=0;i<N;i++){
99      pid_t p=fork(); if(p==0) _exit(0);
100      double lim=now_ns()+2e8;              /* 200 ms de plazo: si no llega, se PERDIO */
101      for(;;){
102        if(now_ns()>lim){ perdidos++; break; }
103        struct pollfd pf={.fd=s,.events=POLLIN};
104        if(poll(&pf,1,50)<=0) continue;
105        struct proc_event *e=cn_read(s,buf,sizeof buf);
106        if(!e) continue;
107        if(e->what==PROC_EVENT_EXIT && e->event_data.exit.process_pid==p) break;
108      }
109      int st; waitpid(p,&st,0);
110    }
111    r[q]=(now_ns()-t0)/(double)N;
112  }
113  report("fork+_exit+esperar EXIT del connector",r,ROUNDS,N);
114  printf("eventos EXIT que no llegaron en 200 ms: %ld de %ld\n",perdidos,(long)N*ROUNDS);
115}
116
117/* ---------------------------------------------------------------- ceguera del sondeo */
118
119#define BURST 200        /* procesos cortos */
120#define SPACING_US 10000 /* uno cada 10 ms => 2 s de rafaga */
121#define POLL_MS 100      /* periodo del sondeo, generoso: un supervisor real no baja de aca */
122
123/* Lanza BURST hijos que viven lo minimo, y escribe sus pids por el pipe. */
124static void spawner(int wfd){
125  for(int i=0;i<BURST;i++){
126    pid_t p=fork();
127    if(p==0) _exit(0);
128    (void)!write(wfd,&p,sizeof p);
129    int st; waitpid(p,&st,0);
130    usleep(SPACING_US);
131  }
132  close(wfd);
133  _exit(0);
134}
135
136static int en_lista(pid_t *v,int n,pid_t p){ for(int i=0;i<n;i++) if(v[i]==p) return 1; return 0; }
137
138static double cpu_ms(void){
139  struct rusage u; getrusage(RUSAGE_SELF,&u);
140  return (u.ru_utime.tv_sec+u.ru_stime.tv_sec)*1e3
141       + (u.ru_utime.tv_usec+u.ru_stime.tv_usec)/1e3;
142}
143
144static void fase_b(int s){
145  printf("\n### B. Ceguera: %d procesos de vida minima, uno cada %d ms\n",BURST,SPACING_US/1000);
146
147  /* --- observador 1: sondeo de /proc cada POLL_MS --- */
148  int fd[2]; if(pipe(fd)) { perror("pipe"); return; }
149  pid_t sp=fork();
150  if(sp==0){ close(fd[0]); spawner(fd[1]); }
151  close(fd[1]);
152  static pid_t lanzados[BURST]; int nl=0;
153  static pid_t vistos[BURST*8]; int nv=0;
154  double c0=cpu_ms(), t0=now_ns(); int barridos=0;
155  while(now_ns()-t0 < (double)BURST*SPACING_US*1000.0 + 3e8){
156    DIR *d=opendir("/proc"); struct dirent *e;
157    while(d && (e=readdir(d))){
158      if(e->d_name[0]<'0'||e->d_name[0]>'9') continue;
159      pid_t p=(pid_t)atol(e->d_name);
160      if(nv<BURST*8 && !en_lista(vistos,nv,p)) vistos[nv++]=p;
161    }
162    if(d) closedir(d);
163    barridos++;
164    usleep(POLL_MS*1000);
165  }
166  double cpu_sondeo=cpu_ms()-c0;
167  { int st; waitpid(sp,&st,0); }
168  { pid_t p; while(read(fd[0],&p,sizeof p)==sizeof p) if(nl<BURST) lanzados[nl++]=p; }
169  close(fd[0]);
170  int cazados=0; for(int i=0;i<nl;i++) if(en_lista(vistos,nv,lanzados[i])) cazados++;
171  printf("sondeo /proc cada %d ms : %d de %d procesos vistos  (%d barridos, %.1f ms de CPU)\n",
172         POLL_MS,cazados,nl,barridos,cpu_sondeo);
173
174  /* --- observador 2: connector --- */
175  if(pipe(fd)){ perror("pipe"); return; }
176  cn_drain(s);
177  sp=fork();
178  if(sp==0){ close(fd[0]); spawner(fd[1]); }
179  close(fd[1]);
180  nl=0; nv=0;
181  c0=cpu_ms(); t0=now_ns();
182  int flags=fcntl(s,F_GETFL,0); fcntl(s,F_SETFL,flags|O_NONBLOCK);
183  char buf[8192];
184  while(now_ns()-t0 < (double)BURST*SPACING_US*1000.0 + 3e8){
185    struct proc_event *e=cn_read(s,buf,sizeof buf);
186    if(!e){ usleep(200); continue; }
187    if(e->what==PROC_EVENT_EXIT){
188      pid_t p=e->event_data.exit.process_pid;
189      if(nv<BURST*8 && !en_lista(vistos,nv,p)) vistos[nv++]=p;
190    }
191  }
192  fcntl(s,F_SETFL,flags);
193  double cpu_conn=cpu_ms()-c0;
194  { int st; waitpid(sp,&st,0); }
195  { pid_t p; while(read(fd[0],&p,sizeof p)==sizeof p) if(nl<BURST) lanzados[nl++]=p; }
196  close(fd[0]);
197  cazados=0; for(int i=0;i<nl;i++) if(en_lista(vistos,nv,lanzados[i])) cazados++;
198  printf("proc connector (eventos)  : %d de %d procesos vistos  (%.1f ms de CPU)\n",
199         cazados,nl,cpu_conn);
200  printf("NOTA: el connector se lee aca con espera activa de 200 us, que NO es el modo de un\n"
201         "      supervisor real (seria epoll) => su CPU es cota superior. Lo que cuenta: COBERTURA.\n");
202}
203
204/* ---------------------------------------------------------------- taskstats */
205
206static int genl_family(int s,const char *name){
207  struct { struct nlmsghdr n; struct genlmsghdr g; char buf[256]; } req;
208  memset(&req,0,sizeof req);
209  req.n.nlmsg_type=GENL_ID_CTRL; req.n.nlmsg_flags=NLM_F_REQUEST; req.n.nlmsg_seq=1; req.n.nlmsg_pid=getpid();
210  req.g.cmd=CTRL_CMD_GETFAMILY; req.g.version=1;
211  struct nlattr *a=(struct nlattr*)req.buf;
212  a->nla_type=CTRL_ATTR_FAMILY_NAME; a->nla_len=NLA_HDRLEN+strlen(name)+1;
213  strcpy((char*)a+NLA_HDRLEN,name);
214  req.n.nlmsg_len=NLMSG_LENGTH(GENL_HDRLEN)+NLA_ALIGN(a->nla_len);
215  if(send(s,&req,req.n.nlmsg_len,0)<0) return -1;
216
217  char rb[4096]; ssize_t r=recv(s,rb,sizeof rb,0);
218  if(r<0) return -1;
219  struct nlmsghdr *nh=(struct nlmsghdr*)rb;
220  if(nh->nlmsg_type==NLMSG_ERROR) return -1;
221  struct nlattr *na=(struct nlattr*)((char*)NLMSG_DATA(nh)+GENL_HDRLEN);
222  int len=NLMSG_PAYLOAD(nh,0)-GENL_HDRLEN;
223  while(len>0){
224    if(na->nla_type==CTRL_ATTR_FAMILY_ID) return *(__u16*)((char*)na+NLA_HDRLEN);
225    len-=NLA_ALIGN(na->nla_len);
226    na=(struct nlattr*)((char*)na+NLA_ALIGN(na->nla_len));
227  }
228  return -1;
229}
230
231static int ts_query(int s,int fam,pid_t pid,unsigned long long *cpu_ns){
232  struct { struct nlmsghdr n; struct genlmsghdr g; char buf[128]; } req;
233  memset(&req,0,sizeof req);
234  req.n.nlmsg_type=fam; req.n.nlmsg_flags=NLM_F_REQUEST; req.n.nlmsg_seq=2; req.n.nlmsg_pid=getpid();
235  req.g.cmd=TASKSTATS_CMD_GET; req.g.version=TASKSTATS_VERSION;
236  struct nlattr *a=(struct nlattr*)req.buf;
237  a->nla_type=TASKSTATS_CMD_ATTR_PID; a->nla_len=NLA_HDRLEN+sizeof(__u32);
238  *(__u32*)((char*)a+NLA_HDRLEN)=pid;
239  req.n.nlmsg_len=NLMSG_LENGTH(GENL_HDRLEN)+NLA_ALIGN(a->nla_len);
240  if(send(s,&req,req.n.nlmsg_len,0)<0) return -1;
241
242  char rb[8192]; ssize_t r=recv(s,rb,sizeof rb,0);
243  if(r<0) return -1;
244  struct nlmsghdr *nh=(struct nlmsghdr*)rb;
245  if(nh->nlmsg_type==NLMSG_ERROR) return -1;
246  struct nlattr *na=(struct nlattr*)((char*)NLMSG_DATA(nh)+GENL_HDRLEN);
247  int len=NLMSG_PAYLOAD(nh,0)-GENL_HDRLEN;
248  while(len>0){
249    if(na->nla_type==TASKSTATS_TYPE_AGGR_PID || na->nla_type==TASKSTATS_TYPE_AGGR_TGID){
250      struct nlattr *in=(struct nlattr*)((char*)na+NLA_HDRLEN);
251      int il=na->nla_len-NLA_HDRLEN;
252      while(il>0){
253        if(in->nla_type==TASKSTATS_TYPE_STATS){
254          struct taskstats *ts=(struct taskstats*)((char*)in+NLA_HDRLEN);
255          *cpu_ns=ts->ac_utime*1000ULL+ts->ac_stime*1000ULL;
256          return 0;
257        }
258        il-=NLA_ALIGN(in->nla_len);
259        in=(struct nlattr*)((char*)in+NLA_ALIGN(in->nla_len));
260      }
261    }
262    len-=NLA_ALIGN(na->nla_len);
263    na=(struct nlattr*)((char*)na+NLA_ALIGN(na->nla_len));
264  }
265  return -1;
266}
267
268static void fase_c(void){
269  printf("\n### C. Estadisticas de UNA tarea: taskstats binario contra /proc de texto\n");
270  int s=socket(PF_NETLINK,SOCK_DGRAM|SOCK_CLOEXEC,NETLINK_GENERIC);
271  if(s<0){ printf("taskstats: sin socket genetlink (%s)\n",strerror(errno)); return; }
272  struct sockaddr_nl a; memset(&a,0,sizeof a); a.nl_family=AF_NETLINK; a.nl_pid=getpid();
273  if(bind(s,(struct sockaddr*)&a,sizeof a)<0){ printf("taskstats: bind (%s)\n",strerror(errno)); close(s); return; }
274  int fam=genl_family(s,TASKSTATS_GENL_NAME);
275  if(fam<0){ printf("taskstats: la familia %s no esta (kernel sin TASKSTATS)\n",TASKSTATS_GENL_NAME); close(s); return; }
276  unsigned long long ns=0;
277  if(ts_query(s,fam,getpid(),&ns)<0){ printf("taskstats: la consulta fallo (%s)\n",strerror(errno)); close(s); return; }
278  printf("familia TASKSTATS = %d, cpu de este proceso = %llu ns\n",fam,ns);
279
280  printf("%-44s %10s %10s\n","BENCH (ns por consulta)","min","mediana");
281  const long N=2000;
282  double r[ROUNDS];
283  for(int q=0;q<ROUNDS;q++){
284    double t0=now_ns();
285    for(long i=0;i<N;i++){ unsigned long long v; sink=ts_query(s,fam,getpid(),&v); }
286    r[q]=(now_ns()-t0)/(double)N;
287  }
288  report("taskstats por genetlink (binario)",r,ROUNDS,N);
289
290  for(int q=0;q<ROUNDS;q++){
291    double t0=now_ns();
292    for(long i=0;i<N;i++){
293      int fd=open("/proc/self/stat",O_RDONLY); char b[2048];
294      ssize_t n=read(fd,b,sizeof b-1); close(fd);
295      if(n>0){ b[n]=0; char *p=strrchr(b,')'); for(int f=0;p&&f<12;f++) p=strchr(p+1,' ');
296               sink=p?atol(p+1):0; }
297    }
298    r[q]=(now_ns()-t0)/(double)N;
299  }
300  report("open+read+parse /proc/self/stat",r,ROUNDS,N);
301  close(s);
302}
303
304int main(void){
305  if(geteuid()!=0) fprintf(stderr,"aviso: sin root el connector no se suscribe (CAP_NET_ADMIN)\n");
306  printf("# bench_connector — SDD 25 seccion 9, experimento 1\n");
307  int s=cn_open();
308  if(s<0){ printf("proc connector: no disponible (%s)\n",strerror(errno)); }
309  else { fase_a(s); fase_b(s); close(s); }
310  fase_c();
311  return 0;
312}