aboutsummaryrefslogtreecommitdiffstats
path: root/history.html
blob: 4395107cb40e6ea77f933f84fa3abef26fc3219a (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
<?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%" align="right">$Date$</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>

<hr />
<table width="100%" cellpadding="0" summary="Canned page header">
<tr>
<td width="30%" align="right">$Date$</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>
} /* Zero the structure, so that it is ready to use. */ memset (a, 0, sizeof(*a)); /* Return the new pointers. */ *newentry = a; *list = l; return; } /* Parse FILE as a .netrc file (as described in ftp(1)), and return a list of entries. NULL is returned if the file could not be parsed. */ netrc_entry * parse_netrc (file) char *file; { FILE *fp; char buf[POPBUFSIZE+1], *p, *tok; const char *premature_token; netrc_entry *current, *retval; int ln; /* The latest token we've seen in the file. */ enum { tok_nothing, tok_account, tok_login, tok_macdef, tok_machine, tok_password } last_token = tok_nothing; current = retval = NULL; fp = fopen (file, "r"); if (!fp) { /* Just return NULL if we can't open the file. */ return NULL; } /* Initialize the file data. */ ln = 0; premature_token = NULL; /* While there are lines in the file... */ while (fgets(buf, sizeof(buf) - 1, fp)) { ln++; /* Strip trailing CRLF */ for (p = buf + strlen(buf) - 1; (p >= buf) && isspace((unsigned char)*p); p--) *p = '\0'; /* Parse the line. */ p = buf; /* If the line is empty... */ if (!*p) { if (last_token == tok_macdef) /* end of macro */ last_token = tok_nothing; else continue; /* otherwise ignore it */ } /* If we are defining macros, then skip parsing the line. */ while (*p && last_token != tok_macdef) { char quote_char = 0; char *pp; /* Skip any whitespace. */ while (*p && isspace ((unsigned char)*p)) p++; /* Discard end-of-line comments. */ if (*p == '#') break; tok = pp = p; /* Find the end of the token. */ while (*p && (quote_char || !isspace ((unsigned char)*p))) { if (quote_char) { if (quote_char == *p) { quote_char = 0; p ++; } else { *pp = *p; p ++; pp ++; } } else { if (*p == '"' || *p == '\'') quote_char = *p; else { *pp = *p; pp ++; } p ++; } } /* Null-terminate the token, if it isn't already. */ if (*p) *p ++ = '\0'; *pp = 0; switch (last_token) { case tok_login: if (current) current->login = (char *) xstrdup (tok); else premature_token = "login"; break; case tok_machine: /* Start a new machine entry. */ maybe_add_to_list (&current, &retval); current->host = (char *) xstrdup (tok); break; case tok_password: if (current) current->password = (char *) xstrdup (tok); else premature_token = "password"; break; /* We handle most of tok_macdef above. */ case tok_macdef: if (!current) premature_token = "macdef"; break; /* We don't handle the account keyword at all. */ case tok_account: if (!current) premature_token = "account"; break; /* We handle tok_nothing below this switch. */ case tok_nothing: break; } if (premature_token) { fprintf (stderr, GT_("%s:%d: warning: found \"%s\" before any host names\n"), file, ln, premature_token); premature_token = NULL; } if (last_token != tok_nothing) /* We got a value, so reset the token state. */ last_token = tok_nothing; else { /* Fetch the next token. */ if (!strcmp (tok, "default")) { maybe_add_to_list (&current, &retval); } else if (!strcmp (tok, "login")) last_token = tok_login; else if (!strcmp (tok, "user")) last_token = tok_login; else if (!strcmp (tok, "macdef")) last_token = tok_macdef; else if (!strcmp (tok, "machine")) last_token = tok_machine; else if (!strcmp (tok, "password")) last_token = tok_password; else if (!strcmp (tok, "passwd")) last_token = tok_password; else if (!strcmp (tok, "account")) last_token = tok_account; else { fprintf (stderr, GT_("%s:%d: warning: unknown token \"%s\"\n"), file, ln, tok); } } } } fclose (fp); /* Finalize the last machine entry we found. */ maybe_add_to_list (&current, &retval); free (current); /* Reverse the order of the list so that it appears in file order. */ current = retval; retval = NULL; while (current) { netrc_entry *saved_reference; /* Change the direction of the pointers. */ saved_reference = current->next; current->next = retval; /* Advance to the next node. */ retval = current; current = saved_reference; } return retval; } /* Return the netrc entry from LIST corresponding to HOST. NULL is returned if no such entry exists. */ netrc_entry * search_netrc (list, host, login) netrc_entry *list; char *host, *login; { /* Look for the HOST in LIST. */ while (list) { if (list->host && !strcmp(list->host, host)) if (!list->login || !strcmp(list->login, login)) /* We found a matching entry. */ break; list = list->next; } /* Return the matching entry, or NULL. */ return list; } #ifdef STANDALONE #include <sys/types.h> #include <sys/stat.h> #include <errno.h> int main (int argc, char **argv) { struct stat sb; char *program_name, *file, *host, *login; netrc_entry *head, *a; program_name = argv[0]; file = argv[1]; host = argv[2]; login = argv[3]; switch (argc) { case 2: case 4: break; default: fprintf (stderr, "Usage: %s <file> [<host> <login>]\n", argv[0]); exit(EXIT_FAILURE); } if (stat (file, &sb)) { fprintf (stderr, "%s: cannot stat %s: %s\n", argv[0], file, strerror (errno)); exit (1); } head = parse_netrc (file); if (!head) { fprintf (stderr, "%s: no entries found in %s\n", argv[0], file); exit (1); } if (host && login) { int status; status = EXIT_SUCCESS; printf("Host: %s, Login: %s\n", host, login); a = search_netrc (head, host, login); if (a) { /* Print out the password (if any). */ if (a->password) { printf("Password: %s\n", a->password); } } else status = EXIT_FAILURE; fputc ('\n', stdout); exit (status); } /* Print out the entire contents of the netrc. */ a = head; while (a) { /* Print the host name. */ if (a->host) fputs (a->host, stdout); else fputs ("DEFAULT", stdout); fputc (' ', stdout); /* Print the login name. */ fputs (a->login, stdout); if (a->password) { /* Print the password, if there is any. */ fputc (' ', stdout); fputs (a->password, stdout); } fputc ('\n', stdout); a = a->next; } exit (0); } #endif /* STANDALONE */