aboutsummaryrefslogtreecommitdiffstats
path: root/history.html
blob: d55ce867b1913fe7ac52633298ea0a2d88278f7e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rev="made" href="mailto:esr@snark.thyrsus.com" />
<meta name="description"
content="Fetchmail participation statistics" />
<meta name="keywords" content="fetchmail, growth, analysis" />
<title>Trends in the fetchmail project's growth</title>
<style type="text/css">
/*<![CDATA[*/
 span.c6 {color: brown}
 span.c5 {color: red}
 span.c4 {color: lime}
 span.c3 {color: blue}
 div.c2 {text-align: center}
 h1.c1 {text-align: center}
/*]]>*/
</style>
</head>
<body>
<table width="100%" cellpadding="0" summary="Canned page header">
<tr>
<td width="30%">Back to <a href="/~esr">Eric's Home Page</a></td>
<td width="30%" align="center">Up to <a href="/~esr/sitemap.html">Site Map</a></td>
<td width="30%" align="right">$Date: 2002/09/09 07:24:37 $</td>
</tr>
</table>

<hr />
<h1 class="c1">Trends in the fetchmail project's growth</h1>

<p>The scattergram below was made with Gnuplot 3.7 from data pulled
directly out of the project NEWS file using two custom
shellscripts, <a href="timeseries">timeseries</a> and <a
href="growthplot">growthplot</a>. If you see a broken-image icon,
upgrade to a <a
href="http://www.libpng.org/pub/png/pngapbr.html">browser that can
view PNGs</a>.</p>

<div class="c2"><img src="growth.png"
alt="Fetchmail trends graph" /></div>

<p>The graph shows the population growth of the fetchmail project.
The horizontal scale is days since baseline, which is when I
started collecting statistics in October 1996 at version 1.9.0.
Left vertical scale is number of participants. There is one data
point for each release; therefore, the changes in density of marks
indicate release frequency.</p>

<p>The peak in the earliest part of the graph (before the note "Bad
addresses dropped") seems to be an artifact; I was not regularly
dropping addresses that became invalid at the time. Turnover on the
list seems to be about 5% per month (but that's just my estimate, I
don't have numbers on this).</p>

<p>The <span class="c3">blue scatter of squares</span> is total
participants. The <span class="c4">green scatter of crosses</span>
is the count of people on fetchmail-friends after I split the list.
The <span class="c5">cyan scatter of diamonds</span> is the
population of fetchmail-announce after the split.</p>

<p>The <span class="c6">brown scatter of diamonds</span> tracks
project size in lines of code (right vertical axis). The scale
relationship between this scatter and the other three is
arbitrary.</p>

<p>This graph is quite revealing. Several trends stand out:</p>

<ul>
<li>
<p>Over time, the project population displays rather consistent
linear growth.</p>
</li>

<li>
<p>The key event in the project's lifetime was release 4.3.0 in
October 1997, when I declared the code to be out of development and
in maintainance mode, and split the fetchmail list.</p>
</li>

<li>
<p>The run-up to 4.3.0 saw the most intensive spate of releases in
the project's history (the gap in that run happened when I took a
two-week vacation). It was followed by a significant slowdown.</p>
</li>

<li>
<p>After 4.3.0, the developer population remained fairly stable
around an average of about 250 participants.</p>
</li>

<li>
<p>Essentially all population growth after 4.3.0 happened on the
announce list, among people using fetchmail but not active
co-developers.</p>
</li>

<li>
<p>The growth trend in code size looks sublinear, perhaps
logarithmic.</p>
</li>
</ul>

<p>The linear growth trend in population is particularly
interesting; a priori we might expect geometric or logistic growth,
given that the project spreads by word of mouth.</p>

<p>It has been suggested that the linear growth rate is the result
of a situation in which both number of projects and the population
of eligible programmers are rising on trend curves of the same
(probably exponential) rate.</p>

<p>There are some other pages doing similar things:</p>

<ul>
<li>
<p><a href="http://kitenet.net/programs/debhelper/stats/">Here</a>
are growth statistics on the debhelper packaging utility.</p>
</li>

<li>
<p><a href="http://durak.org:81/sean/pubs/kfc/">Here</a> is a page
on the vocabulary of the Linux kernel.</p>
</li>
</ul>

<hr />
<table width="100%" cellpadding="0" summary="Canned page header">
<tr>
<td width="30%">Back to <a href="/~esr">Eric's Home Page</a></td>
<td width="30%" align="center">Up to <a href="/~esr/sitemap.html">Site Map</a></td>
<td width="30%" align="right">$Date: 2002/09/09 07:24:37 $</td>
</tr>
</table>

<br clear="left" />
<address>Eric S. Raymond <a
href="mailto:esr@thyrsus.com">&lt;esr@thyrsus.com&gt;</a></address>
</body>
</html>
se debugging */ /* daemon mode control */ int poll_interval; /* poll interval in seconds */ char *logfile; /* log file for daemon mode */ int quitmode; /* if --quit was set */ /* miscellaneous global controls */ char *rcfile; /* path name of rc file */ int versioninfo; /* emit only version info */ /********************************************************************* function: main description: main driver routine arguments: argc argument count as passed by runtime startup code. argv argument strings as passed by runtime startup code. return value: an exit status code for the shell -- see the PS_.* constants defined above. calls: parsecmdline, setdefaults, openuserfolder, doPOP2. globals: none. *********************************************************************/ static void termhook(); static char *lockfile; static int popstatus; static struct hostrec *hostp; main (argc,argv) int argc; char **argv; { int mboxfd, st; struct hostrec def_opts; int parsestatus, implicitmode; char *servername, *user, *home, *tmpdir, tmpbuf[BUFSIZ]; FILE *lockfp; pid_t pid; memset(&def_opts, '\0', sizeof(struct hostrec)); if ((user = getenv("USER")) == (char *)NULL) user = getenv("LOGNAME"); if ((user == (char *)NULL) || (home = getenv("HOME")) == (char *)NULL) { struct passwd *pw; if ((pw = getpwuid(getuid())) != NULL) { user = pw->pw_name; home = pw->pw_dir; } else { fprintf(stderr,"I can't find your name and home directory!\n"); exit(PS_UNDEFINED); } } def_opts.protocol = P_AUTO; strcpy(def_opts.remotename, user); strcpy(def_opts.smtphost, "localhost"); /* * Backward-compatibility hack. If we're called by the name of the * ancestral popclient, look for .poprc. This will actually work * for popclient files that don't use the removed keywords. */ if (strcmp("popclient", argv[0]) == 0) tmpdir = ".poprc"; else tmpdir = ".fetchmailrc"; rcfile = (char *) xmalloc(strlen(home)+strlen(tmpdir)+2); strcpy(rcfile, home); strcat(rcfile, "/"); strcat(rcfile, tmpdir); outlevel = O_NORMAL; if ((parsestatus = parsecmdline(argc,argv,&cmd_opts)) < 0) exit(PS_SYNTAX); if (versioninfo) showversioninfo(); /* this builds the host list */ if (prc_parse_file(rcfile) != 0) exit(PS_SYNTAX); if (implicitmode = (optind >= argc)) { for (hostp = hostlist; hostp; hostp = hostp->next) hostp->active = TRUE; } else for (; optind < argc; optind++) { /* * If hostname corresponds to a host known from the rc file, * simply declare it active. Otherwise synthesize a host * record from command line and defaults */ for (hostp = hostlist; hostp; hostp = hostp->next) if (strcmp(hostp->servername, argv[optind]) == 0) goto foundit; hostp = hostalloc(&cmd_opts); strcpy(hostp->servername, argv[optind]); foundit: hostp->active = TRUE; } /* if there's a defaults record, merge it and lose it */ if (hostlist && strcmp(hostlist->servername, "defaults") == 0) { for (hostp = hostlist; hostp; hostp = hostp->next) optmerge(hostp, hostlist); hostlist = hostlist->next; } /* don't allow a defaults record after the first */ for (hostp = hostlist; hostp; hostp = hostp->next) if (strcmp(hostp->servername, "defaults") == 0) exit(PS_SYNTAX); /* merge in wired defaults, do sanity checks and prepare internal fields */ for (hostp = hostlist; hostp; hostp = hostp->next) if (hostp->active && !(implicitmode && hostp->skip)) { /* merge in defaults */ optmerge(hostp, &def_opts); /* if rc file didn't supply a localname, default appropriately */ if (!hostp->localname[0]) strcpy(hostp->localname, hostp->remotename); /* sanity checks */ if (hostp->port < 0) { (void) fprintf(stderr, "%s configuration invalid, port number cannot be negative", hostp->servername); exit(PS_SYNTAX); } /* expand MDA commands */ if (hostp->mda[0]) { int argi; char *argp; /* expand the %s escape if any before parsing */ sprintf(hostp->mdabuf, hostp->mda, hostp->localname); /* now punch nulls into the delimiting whitespace in the args */ for (argp = hostp->mdabuf, argi = 1; *argp != '\0'; argi++) { hostp->mda_argv[argi] = argp; while (!(*argp == '\0' || isspace(*argp))) argp++; if (*argp != '\0') *(argp++) = '\0'; } hostp->mda_argv[argi] = (char *)NULL; hostp->mda_argv[0] = hostp->mda_argv[1]; if ((argp = strrchr(hostp->mda_argv[1], '/')) != (char *)NULL) hostp->mda_argv[1] = argp + 1 ; } } /* set up to do lock protocol */ if ((tmpdir = getenv("TMPDIR")) == (char *)NULL) tmpdir = "/tmp"; strcpy(tmpbuf, tmpdir); strcat(tmpbuf, "/fetchmail-"); strcat(tmpbuf, user); /* perhaps we just want to check options? */ if (versioninfo) { printf("Taking options from command line"); if (access(rcfile, 0)) printf("\n"); else printf(" and %s\n", rcfile); if (outlevel == O_VERBOSE) printf("Lockfile at %s\n", tmpbuf); for (hostp = hostlist; hostp; hostp = hostp->next) { if (hostp->active && !(implicitmode && hostp->skip)) dump_params(hostp); } if (hostlist == NULL) (void) fprintf(stderr, "No mailservers set up -- perhaps %s is missing?\n", rcfile); exit(0); } else if (hostlist == NULL) { (void) fputs("fetchmail: no mailservers have been specified.\n", stderr); exit(PS_SYNTAX); } /* check for another fetchmail running concurrently */ pid = -1; if ((lockfile = (char *) malloc(strlen(tmpbuf) + 1)) == NULL) { fprintf(stderr,"fetchmail: cannot allocate memory for lock name.\n"); exit(PS_EXCLUDE); } else (void) strcpy(lockfile, tmpbuf); if ((lockfp = fopen(lockfile, "r")) != NULL ) { fscanf(lockfp,"%d",&pid); if (kill(pid, 0) == -1) { fprintf(stderr,"fetchmail: removing stale lockfile\n"); remove(lockfile); } fclose(lockfp); } /* perhaps user asked us to kill the other fetchmail */ if (quitmode) { if (pid == -1) { fprintf(stderr,"fetchmail: no other fetchmail is running\n"); exit(PS_EXCLUDE); } else if (kill(pid, SIGTERM) < 0) { fprintf(stderr,"fetchmail: error killing fetchmail at %d.\n",pid); exit(PS_EXCLUDE); } else { fprintf(stderr,"fetchmail: fetchmail at %d killed.\n", pid); remove(lockfile); exit(0); } } /* otherwise die if another fetchmail is running */ if (pid != -1) { fprintf(stderr, "fetchmail: another fetchmail is running at pid %d.\n", pid); return(PS_EXCLUDE); } /* pick up interactively any passwords we need but don't have */ for (hostp = hostlist; hostp; hostp = hostp->next) if (hostp->active && !(implicitmode && hostp->skip) && !hostp->password[0]) { (void) sprintf(tmpbuf, "Enter password for %s@%s: ", hostp->remotename, hostp->servername); (void) strncpy(hostp->password, (char *)getpassword(tmpbuf),PASSWORDLEN-1); } /* * Maybe time to go to demon mode... */ if (poll_interval) daemonize(logfile, termhook); /* beyond here we don't want more than one fetchmail running per user */ umask(0077); signal(SIGABRT, termhook); signal(SIGINT, termhook); signal(SIGTERM, termhook); signal(SIGALRM, termhook); signal(SIGHUP, termhook); signal(SIGPIPE, termhook); signal(SIGQUIT, termhook); if ( (lockfp = fopen(lockfile,"w")) != NULL ) { fprintf(lockfp,"%d",getpid()); fclose(lockfp); } /* * Query all hosts. If there's only one, the error return will * reflect the status of that transaction. */ do { for (hostp = hostlist; hostp; hostp = hostp->next) { if (hostp->active && !(implicitmode && hostp->skip)) popstatus = query_host(hostp); } sleep(poll_interval); } while (poll_interval); if (outlevel == O_VERBOSE) fprintf(stderr, "normal termination, status %d\n", popstatus); termhook(0); exit(popstatus); } void termhook(int sig) /* to be executed on normal or signal-induced termination */ { if (sig != 0) fprintf(stderr, "terminated with signal %d\n", sig); unlink(lockfile); exit(popstatus); } /********************************************************************* function: showproto description: protocol index to name mapping arguments: proto protocol index return value: string name of protocol calls: none. globals: none. *********************************************************************/ static char *showproto(proto) int proto; { switch (proto) { case P_AUTO: return("auto"); break; case P_POP2: return("POP2"); break; case P_POP3: return("POP3"); break; case P_IMAP: return("IMAP"); break; case P_APOP: return("APOP"); break; default: return("unknown?!?"); break; } } /* * Sequence of protocols to try when autoprobing, most capable to least. */ static const int autoprobe[] = {P_IMAP, P_POP3, P_POP2}; static int query_host(queryctl) /* perform fetch transaction with single host */ struct hostrec *queryctl; { int i, st; if (outlevel == O_VERBOSE) { time_t now; time(&now); fprintf(stderr, "Querying %s (protocol %s) at %s", queryctl->servername, showproto(queryctl->protocol), ctime(&now)); } switch (queryctl->protocol) { case P_AUTO: for (i = 0; i < sizeof(autoprobe)/sizeof(autoprobe[0]); i++) { queryctl->protocol = autoprobe[i]; if ((st = query_host(queryctl)) == PS_SUCCESS || st == PS_NOMAIL || st == PS_AUTHFAIL) break; } queryctl->protocol = P_AUTO; return(st); break; case P_POP2: return(doPOP2(queryctl)); break; case P_POP3: case P_APOP: return(doPOP3(queryctl)); break; case P_IMAP: return(doIMAP(queryctl)); break; default: fprintf(stderr,"fetchmail: unsupported protocol selected.\n"); return(PS_PROTOCOL); } } /********************************************************************* function: showversioninfo description: display program release arguments: none. return value: none. calls: none. globals: none. *********************************************************************/ static int showversioninfo() { printf("This is fetchmail release %s\n",RELEASE_ID); } /********************************************************************* function: dump_params description: display program options in English arguments: queryctl merged options return value: none. calls: none. globals: outlimit. *********************************************************************/ int dump_params (queryctl) struct hostrec *queryctl; { printf("Options for %s retrieving from %s:\n", hostp->localname, hostp->servername); if (queryctl->skip || outlevel == O_VERBOSE) printf(" This host will%s be queried when no host is specified.\n", queryctl->skip ? " not" : ""); printf(" Username = '%s'\n", queryctl->remotename); if (queryctl->password[0] == '\0') printf(" Password will be prompted for.\n"); else if (outlevel == O_VERBOSE) if (queryctl->protocol == P_APOP) printf(" APOP secret = '%s'\n", queryctl->password); else printf(" Password = '%s'\n", queryctl->password); printf(" Protocol is %s", showproto(queryctl->protocol)); if (queryctl->port) printf(" (using port %d)", queryctl->port); else if (outlevel == O_VERBOSE) printf(" (using default port)"); putchar('\n'); printf(" Fetched messages will%s be kept on the server (--keep %s).\n", queryctl->keep ? "" : " not", queryctl->keep ? "on" : "off"); printf(" %s messages will be retrieved (--all %s).\n", queryctl->fetchall ? "All" : "Only new", queryctl->fetchall ? "on" : "off"); printf(" Old messages will%s be flushed before message retrieval (--flush %s).\n", queryctl->flush ? "" : " not", queryctl->flush ? "on" : "off"); printf(" Rewrite of server-local addresses is %sabled (--norewrite %s)\n", queryctl->norewrite ? "dis" : "en", queryctl->norewrite ? "on" : "off"); if (queryctl->mda[0]) { char **cp; printf(" Messages will be delivered with %s, args:", queryctl->mda_argv[0]); for (cp = queryctl->mda_argv+1; *cp; cp++) printf(" %s", *cp); putchar('\n'); } else printf(" Messages will be SMTP-forwarded to '%s'\n", queryctl->smtphost); } /********************************************************************* function: openmailpipe description: open a one-way pipe to the mail delivery agent. arguments: queryctl fully-determined options (i.e. parsed, defaults invoked, etc). return value: open file descriptor for the pipe or -1. calls: none. *********************************************************************/ int openmailpipe (queryctl) struct hostrec *queryctl; { int pipefd [2]; int childpid; if (pipe(pipefd) < 0) { perror("fetchmail: openmailpipe: pipe"); return(-1); } if ((childpid = fork()) < 0) { perror("fetchmail: openmailpipe: fork"); return(-1); } else if (childpid == 0) { /* in child process space */ close(pipefd[1]); /* close the 'write' end of the pipe */ close(0); /* get rid of inherited stdin */ if (dup(pipefd[0]) != 0) { fputs("fetchmail: openmailpipe: dup() failed\n",stderr); exit(1); } execv(queryctl->mda_argv[0], queryctl->mda_argv + 1); /* if we got here, an error occurred */ perror("fetchmail: openmailpipe: exec"); _exit(PS_SYNTAX); } /* in the parent process space */ close(pipefd[0]); /* close the 'read' end of the pipe */ return(pipefd[1]); } /********************************************************************* function: closemailpipe description: close pipe to the mail delivery agent. arguments: queryctl fully-determined options record fd pipe descriptor. return value: 0 if success, else -1. calls: none. globals: none. *********************************************************************/ int closemailpipe (fd) int fd; { int err, status; int childpid; if (outlevel == O_VERBOSE) fprintf(stderr, "about to close pipe %d\n", fd); if ((err = close(fd)) != 0) perror("fetchmail: closemailpipe: close failed"); childpid = wait(&status); #if defined(WIFEXITED) && defined(WEXITSTATUS) /* * Try to pass up an error if the MDA returned nonzero status, * on the assumption that this means it was reporting failure. */ if (WIFEXITED(status) == 0 || WEXITSTATUS(status) != 0) { perror("fetchmail: MDA exited abnormally or returned nonzero status"); err = -1; } #endif if (outlevel == O_VERBOSE) fprintf(stderr, "closed pipe %d\n", fd); return(err); }