typedef struct _RcsWalker RcsWalker; typedef struct _RcsFile RcsFile; typedef struct _RcsVersion RcsVersion; typedef struct _RcsStats RcsStats; typedef struct _IntStat IntStat; typedef struct _DblStat DblStat; typedef struct _BinCounter BinCounter; typedef struct _ConfigOption ConfigOption; struct _RcsWalker { void* (* initialize) (void); int (* finalize) (RcsStats* stats, void* data); int (* onefile) (RcsFile* rcs, RcsStats* stats, void* data); int (* dateorder) (RcsFile* rcs, RcsVersion* v, void* data); int (* delta_orig) (RcsFile* rcs, RcsVersion* from, RcsVersion *to, void* data); int (* delta_date) (RcsFile* rcs, RcsVersion* from, RcsVersion *to, void* data); int min_versions; int max_versions; gboolean write_files; }; struct _RcsVersion { RcsFile *rcs; time_t date; int dateseq; int chain_length; char *vname; off_t size; int cc; guint8* segment; char *filename; RcsVersion *parent; GSList *children; guint on_trunk : 1; }; struct _RcsFile { char *filename; char *copyname; char *headname; int version_count; int forward_count; int reverse_count; int branch_count; RcsVersion *versions; RcsVersion **versions_date; RcsVersion *head_version; RcsVersion *root_version; off_t total_size; guint atflag : 1; }; struct _RcsStats { BinCounter *avg_version_size; IntStat* version_stat; IntStat* forward_stat; IntStat* reverse_stat; IntStat* branch_stat; IntStat* unencoded_stat; IntStat* literal_stat; }; struct _IntStat { const char* name; int count; long long sum; long long min; long long max; GArray *values; }; struct _DblStat { const char* name; int count; double sum; double min; double max; GArray *values; }; struct _BinCounter { const char *name; GPtrArray *bins; }; enum _ConfigArgument { CO_Required, CO_Optional, CO_None }; typedef enum _ConfigArgument ConfigArgument; enum _ConfigOptionType { CD_Bool, CD_Int32, CD_Double, CD_String }; typedef enum _ConfigOptionType ConfigOptionType; enum _ConfigStyle { CS_Ignore, CS_UseAsFile, CS_Use }; typedef enum _ConfigStyle ConfigStyle; struct _ConfigOption { const char *name; const char *abbrev; ConfigStyle style; ConfigArgument arg; ConfigOptionType type; void *value; gboolean found; }; /* RCS inspection stuff */ void rcswalk_init (void); int rcswalk (RcsWalker *walker, const char* copy_base); void rcswalk_report (RcsStats* stats); IntStat* stat_int_new (const char* name); void stat_int_add_item (IntStat* stat, long long v); void stat_int_report (IntStat* stat); DblStat* stat_dbl_new (const char* name); void stat_dbl_add_item (DblStat* stat, double v); void stat_dbl_report (DblStat* stat); BinCounter* stat_bincount_new (const char* name); void stat_bincount_add_item (BinCounter* bc, int bin, double val); void stat_bincount_report (BinCounter* bc); /* Experiment configuration stuff */ void config_register (ConfigOption *opts, int nopts); int config_parse (const char* config_file); int config_done (void); void config_help (void); void config_set_string (const char* var, const char* val); int config_clear_dir (const char* dir); int config_create_dir (const char* dir); FILE* config_output (const char* fmt, ...); #ifdef __cplusplus } #endif #endif #include "rcswalk.h" #include "edsio.h" #include #include #include #include #include #include #include #include #include #include #include #undef BUFSIZE #define BUFSIZE (1<<14) char *tmp_file_1; gboolean tmp_file_1_free = TRUE; char *tmp_file_2; gboolean tmp_file_2_free = TRUE; int skip_count; int small_count; int large_count; int process_count; extern time_t str2time (char const *, time_t, long); static guint8 readbuf[BUFSIZE]; static const char* rcswalk_input_dir = NULL; static const char* config_output_base = NULL; static const char* config_output_dir = NULL; static const char* rcswalk_experiment = NULL; static ConfigOption rcswalk_options[] = { { "rcswalk_experiment", "ex", CS_Use, CO_Required, CD_String, & rcswalk_experiment }, { "rcs_input_dir", "id", CS_UseAsFile, CO_Required, CD_String, & rcswalk_input_dir } }; static ConfigOption config_options[] = { { "config_output_base", "ob", CS_Ignore, CO_Required, CD_String, & config_output_base } }; void rcswalk_free_segment (RcsVersion *v) { if (v->segment) g_free (v->segment); if (v->filename == tmp_file_1) tmp_file_1_free = TRUE; else if (v->filename == tmp_file_2) tmp_file_2_free = TRUE; else if (v->filename) g_free (v->filename); v->segment = NULL; v->filename = NULL; } int rcswalk_checkout (RcsFile* rcs, RcsWalker* walker, RcsVersion *v) { FILE* out; char cmdbuf[1024]; int nread; int alloc = BUFSIZE; int pos = 0; sprintf (cmdbuf, "co -ko -p%s %s 2>/dev/null\n", v->vname, rcs->filename); g_assert (! v->segment); v->segment = g_malloc (alloc); if (! (out = popen (cmdbuf, "r"))) { g_warning ("popen failed: %s: %s", cmdbuf, g_strerror (errno)); return errno; } for (;;) { nread = fread (readbuf, 1, BUFSIZE, out); if (nread == 0) break; if (nread < 0) { g_warning ("fread failed: %s", g_strerror (errno)); return errno; } if (pos + nread > alloc) { alloc *= 2; v->segment = g_realloc (v->segment, alloc); } memcpy (v->segment + pos, readbuf, nread); pos += nread; } if (pclose (out) < 0) { g_warning ("pclose failed"); return errno; } v->size = pos; if (walker->write_files) { char* file = NULL; if (! file && tmp_file_1_free) { file = tmp_file_1; tmp_file_1_free = FALSE; } if (! file && tmp_file_2_free) { file = tmp_file_2; tmp_file_2_free = FALSE; } g_assert (file); v->filename = file; if (! (out = fopen (file, "w"))) { g_warning ("fopen failed: %s\n", file); return errno; } if (fwrite (v->segment, v->size, 1, out) != 1) { g_warning ("fwrite failed: %s\n", file); return errno; } if (fclose (out) < 0) { g_warning ("fclose failed: %s\n", file); return errno; } } return 0; } int rcswalk_delta_date (RcsFile* rcs, RcsWalker* walker, void* data) { int i; int ret; RcsVersion *vf = NULL; RcsVersion *vt = NULL; for (i = 0; i < (rcs->version_count-1); i += 1) { vf = rcs->versions_date[i+1]; vt = rcs->versions_date[i]; if (! vt->segment && (ret = rcswalk_checkout (rcs, walker, vt))) { return ret; } if ((ret = rcswalk_checkout (rcs, walker, vf))) { return ret; } if ((ret = walker->delta_date (rcs, vf, vt, data))) { return ret; } rcswalk_free_segment (vt); } if (vf) rcswalk_free_segment (vf); if (vt) rcswalk_free_segment (vt); return 0; } int rcswalk_delta_orig (RcsFile* rcs, RcsWalker* walker, RcsVersion* version, int *count, void* data) { int ret; GSList *c; RcsVersion *child; for (c = version->children; c; c = c->next) { gboolean reverse; child = c->data; if (! version->segment) { if ((ret = rcswalk_checkout (rcs, walker, version))) { return ret; } } if ((ret = rcswalk_checkout (rcs, walker, child))) { return ret; } reverse = version->on_trunk && child->on_trunk; (* count) += 1; if ((ret = walker->delta_orig (rcs, reverse ? child : version, reverse ? version : child, data))) { return ret; } rcswalk_free_segment (version); if ((ret = rcswalk_delta_orig (rcs, walker, child, count, data))) { return ret; } } rcswalk_free_segment (version); return 0; } int rcswalk_dateorder (RcsFile* rcs, RcsWalker *walker, RcsStats *stats, void* data) { int i, ret; for (i = 0; i < rcs->version_count; i += 1) { RcsVersion *v = rcs->versions_date[i]; if ((ret = rcswalk_checkout (rcs, walker, v))) { return ret; } stat_bincount_add_item (stats->avg_version_size, i, v->size); if ((ret = walker->dateorder (rcs, v, data))) { return ret; } rcswalk_free_segment (v); } return 0; } gboolean rcswalk_match (char** line_p, char* str) { int len = strlen (str); if (strncmp (*line_p, str, len) == 0) { (*line_p) += len; return TRUE; } return FALSE; } void rcswalk_find_parent (RcsFile *rcs, GHashTable* hash, RcsVersion *v) { char *lastdot; char mbuf[1024]; int lastn; RcsVersion *p; strcpy (mbuf, v->vname); if (! (lastdot = strchr (mbuf, '.'))) abort (); if (! (lastdot = strchr (lastdot+1, '.'))) v->on_trunk = TRUE; lastdot = strrchr (mbuf, '.'); lastn = atoi (lastdot + 1); do { if (lastn == 1) { (*lastdot) = 0; if (strcmp (mbuf, "1") == 0) { /* Assuming the first version is always "1.1". */ rcs->root_version = v; return; } else if (! (lastdot = strrchr (mbuf, '.'))) { int i = 1; int br = atoi (mbuf) - 1; RcsVersion *p2 = NULL; /* Now we have something like "2.1" and need to * search for the highest "1.x" version. */ do { sprintf (mbuf, "%d.%d", br, i++); p = p2; } while ((p2 = g_hash_table_lookup (hash, mbuf))); if (p == NULL) { rcs->root_version = v; return; } break; } else { /* 1.2.3.1 => 1.2 */ (*lastdot) = 0; lastdot = strrchr (mbuf, '.'); lastn = atoi (lastdot + 1); } } else { lastn -= 1; sprintf (lastdot, ".%d", lastn); } } while (! (p = g_hash_table_lookup (hash, mbuf))); g_assert (p); v->parent = p; p->children = g_slist_prepend (p->children, v); } int rcswalk_traverse_graph (RcsFile* rcs, RcsVersion* version, RcsVersion *parent) { GSList *c; int distance = -1; version->cc = g_slist_length (version->children); if (version->cc > 1) rcs->branch_count += (version->cc - 1); if (parent) { /* Insure that there is proper date ordering. */ if (version->date <= parent->date) version->date = parent->date + 1; if (parent->on_trunk && version->on_trunk) rcs->reverse_count += 1; else rcs->forward_count += 1; } for (c = version->children; c; c = c->next) { int c_dist = rcswalk_traverse_graph (rcs, c->data, version); distance = MAX (distance, c_dist); } if (version == rcs->head_version) distance = 0; if (distance >= 0) { version->chain_length = distance; return distance + 1; } return -1; } void rcswalk_compute_chain_length (RcsFile* rcs, RcsVersion* version, RcsVersion *parent) { GSList *c; if (! parent) { g_assert (version->chain_length >= 0); } else if (version->chain_length < 0) { version->chain_length = parent->chain_length + 1; } for (c = version->children; c; c = c->next) { rcswalk_compute_chain_length (rcs, c->data, version); } } int rcswalk_date_compare (const void* a, const void* b) { RcsVersion **ra = (void*) a; RcsVersion **rb = (void*) b; return (*ra)->date - (*rb)->date; } int rcswalk_build_graph (RcsFile* rcs) { GHashTable* hash = g_hash_table_new (g_str_hash, g_str_equal); int i; for (i = 0; i < rcs->version_count; i += 1) g_hash_table_insert (hash, rcs->versions[i].vname, rcs->versions + i); for (i = 0; i < rcs->version_count; i += 1) { RcsVersion *v = rcs->versions + i; v->chain_length = -1; v->rcs = rcs; rcswalk_find_parent (rcs, hash, v); } rcs->head_version = g_hash_table_lookup (hash, rcs->headname); rcswalk_traverse_graph (rcs, rcs->root_version, NULL); rcswalk_compute_chain_length (rcs, rcs->root_version, NULL); for (i = 0; i < rcs->version_count; i += 1) rcs->versions_date[i] = rcs->versions + i; qsort (rcs->versions_date, rcs->version_count, sizeof (RcsVersion*), & rcswalk_date_compare); for (i = 0; i < rcs->version_count; i += 1) { RcsVersion *v = rcs->versions_date[i]; v->dateseq = i; } g_hash_table_destroy (hash); return 0; } #define HEAD_STATE 0 #define BAR_STATE 1 #define REV_STATE 2 #define DATE_STATE 3 int rcswalk_load (RcsFile *rcs, gboolean *skip) { FILE* rlog; char cmdbuf[1024]; char oneline[1024], *oneline_p; char rbuf[1024]; int version_i = 0, ret; int read_state = HEAD_STATE; sprintf (cmdbuf, "rlog %s", rcs->filename); if (! (rlog = popen (cmdbuf, "r"))) { g_warning ("popen failed: %s", cmdbuf); return errno; } rcs->headname = NULL; while (fgets (oneline, 1024, rlog)) { oneline_p = oneline; if (read_state == HEAD_STATE && rcswalk_match (& oneline_p, "total revisions: ")) { if (sscanf (oneline_p, "%d", & rcs->version_count) != 1) goto badscan; rcs->versions = g_new0 (RcsVersion, rcs->version_count); rcs->versions_date = g_new (RcsVersion*, rcs->version_count); read_state = BAR_STATE; } else if (read_state == HEAD_STATE && rcswalk_match (& oneline_p, "head: ")) { if (sscanf (oneline_p, "%s", rbuf) != 1) goto badscan; rcs->headname = g_strdup (rbuf); read_state = HEAD_STATE; /* no change */ } else if (read_state == BAR_STATE && rcswalk_match (& oneline_p, "----------------------------")) { read_state = REV_STATE; } else if (read_state == REV_STATE && rcswalk_match (& oneline_p, "revision ")) { if (version_i >= rcs->version_count) { /* jkh likes to insert the rlog of one RCS file into the log * message of another, and this can confuse things. Why, oh why, * doesn't rlog have an option to not print the log? */ fprintf (stderr, "rcswalk: too many versions: skipping file %s\n", rcs->filename); *skip = TRUE; skip_count += 1; pclose (rlog); return 0; } if (sscanf (oneline_p, "%s", rbuf) != 1) goto badscan; rcs->versions[version_i].vname = g_strdup (rbuf); read_state = DATE_STATE; g_assert (rcs->versions[version_i].vname); } else if (read_state == DATE_STATE && rcswalk_match (& oneline_p, "date: ")) { char* semi = strchr (oneline_p, ';'); if (! semi) goto badscan; strncpy (rbuf, oneline_p, semi - oneline_p); rbuf[semi - oneline_p] = 0; rcs->versions[version_i].date = str2time (rbuf, 0, 0); version_i += 1; read_state = BAR_STATE; } } if (! rcs->headname) { fprintf (stderr, "rcswalk: no head version: skipping file %s\n", rcs->filename); *skip = TRUE; skip_count += 1; pclose (rlog); return 0; } if (pclose (rlog) < 0) { g_warning ("pclose failed: %s", cmdbuf); return errno; } if ((ret = rcswalk_build_graph (rcs))) { return ret; } return 0; badscan: pclose (rlog); g_warning ("rlog syntax error"); return -1; } void rcswalk_free (RcsFile* rcs) { int i; for (i = 0; i < rcs->version_count; i += 1) { g_free (rcs->versions[i].vname); g_slist_free (rcs->versions[i].children); } g_free (rcs->filename); g_free (rcs->headname); g_free (rcs->versions); g_free (rcs->versions_date); g_free (rcs); } int rcswalk_one (char* rcsfile, char* copyfile, RcsWalker* walker, RcsStats* stats, void* data) { RcsFile* rcs; int i, ret; long long maxsize = 0; gboolean skip = FALSE; rcs = g_new0 (RcsFile, 1); rcs->filename = g_strdup (rcsfile); rcs->copyname = copyfile; if ((ret = rcswalk_load (rcs, & skip))) { return ret; } if (walker->min_versions > rcs->version_count) { small_count += 1; skip = TRUE; } if (walker->max_versions < rcs->version_count) { large_count += 1; skip = TRUE; } if (! skip) { process_count += 1; if (walker->dateorder && (ret = rcswalk_dateorder (rcs, walker, stats, data))) { return ret; } if (walker->delta_orig) { int count = 0; if ((ret = rcswalk_delta_orig (rcs, walker, rcs->root_version, & count, data))) { return ret; } g_assert (count == (rcs->version_count - 1)); } if (walker->delta_date && (ret = rcswalk_delta_date (rcs, walker, data))) { return ret; } for (i = 0; i < rcs->version_count; i += 1) { rcs->total_size += rcs->versions[i].size; maxsize = MAX (rcs->versions[i].size, maxsize); } stat_int_add_item (stats->version_stat, rcs->version_count); stat_int_add_item (stats->forward_stat, rcs->forward_count); stat_int_add_item (stats->reverse_stat, rcs->reverse_count); stat_int_add_item (stats->branch_stat, rcs->branch_count); stat_int_add_item (stats->unencoded_stat, rcs->total_size); stat_int_add_item (stats->literal_stat, maxsize); if (walker->onefile && (ret = walker->onefile (rcs, stats, data))) { return ret; } } rcswalk_free (rcs); return 0; } int rcswalk_dir (const char* dir, RcsWalker* walker, RcsStats* stats, void* data, const char* copy_dir) { int ret; DIR* thisdir; struct dirent* ent; if (copy_dir && (ret = config_create_dir (copy_dir))) { return ret; } if (! (thisdir = opendir (dir))) { g_warning ("opendir failed: %s", dir); return errno; } while ((ent = readdir (thisdir))) { char* name = ent->d_name; int len; struct stat buf; char* fullname; char* copyname = NULL; if (strcmp (name, ".") == 0) continue; if (strcmp (name, "..") == 0) continue; len = strlen (name); fullname = g_strdup_printf ("%s/%s", dir, name); if (copy_dir) copyname = g_strdup_printf ("%s/%s", copy_dir, name); if (len > 2 && strcmp (name + len - 2, ",v") == 0) { if ((ret = rcswalk_one (fullname, copyname, walker, stats, data))) { goto abort; } } else { if (stat (fullname, & buf) < 0) { g_warning ("stat failed: %s\n", fullname); goto abort; } if (S_ISDIR (buf.st_mode)) { if ((ret = rcswalk_dir (fullname, walker, stats, data, copyname))) { goto abort; } } } g_free (fullname); if (copyname) g_free (copyname); } if (closedir (thisdir) < 0) { g_warning ("closedir failed: %s", dir); return errno; } return 0; abort: if (thisdir) closedir (thisdir); return -1; } void rcswalk_init (void) { config_register (rcswalk_options, ARRAY_SIZE (rcswalk_options)); } int rcswalk (RcsWalker *walker, const char* copy_base) { void* data = NULL; RcsStats stats; int ret; skip_count = 0; small_count = 0; process_count = 0; large_count = 0; memset (& stats, 0, sizeof (stats)); stats.avg_version_size = stat_bincount_new ("AvgVersionSize"); /* @@@ leak */ stats.version_stat = stat_int_new ("Version"); /* @@@ leak */ stats.forward_stat = stat_int_new ("Forward"); /* @@@ leak */ stats.reverse_stat = stat_int_new ("Reverse"); /* @@@ leak */ stats.branch_stat = stat_int_new ("Branch"); /* @@@ leak */ stats.unencoded_stat = stat_int_new ("Unencoded"); /* @@@ leak */ stats.literal_stat = stat_int_new ("Literal"); /* @@@ leak */ tmp_file_1 = g_strdup_printf ("%s/rcs1.%d", g_get_tmp_dir (), (int) getpid ()); tmp_file_2 = g_strdup_printf ("%s/rcs2.%d", g_get_tmp_dir (), (int) getpid ()); if (walker->initialize) data = walker->initialize (); if ((ret = rcswalk_dir (rcswalk_input_dir, walker, & stats, data, copy_base))) { return ret; } if (walker->finalize) { if ((ret = walker->finalize (& stats, data))) { return ret; } } unlink (tmp_file_1); unlink (tmp_file_2); fprintf (stderr, "rcswalk: processed %d files: too small %d; too large: %d; damaged: %d\n", process_count, small_count, large_count, skip_count); return 0; } /* Statistics */ void rcswalk_report (RcsStats* set) { stat_bincount_report (set->avg_version_size); stat_int_report (set->version_stat); stat_int_report (set->forward_stat); stat_int_report (set->reverse_stat); stat_int_report (set->branch_stat); stat_int_report (set->unencoded_stat); stat_int_report (set->literal_stat); } /* Int stat */ IntStat* stat_int_new (const char* name) { IntStat* s = g_new0 (IntStat, 1); s->name = name; s->values = g_array_new (FALSE, FALSE, sizeof (long long)); return s; } void stat_int_add_item (IntStat* stat, long long v) { if (! stat->count) stat->min = v; stat->count += 1; stat->min = MIN (v, stat->min); stat->max = MAX (v, stat->max); stat->sum += v; g_array_append_val (stat->values, v); } double stat_int_stddev (IntStat *stat) { double f = 0; double m = (double) stat->sum / (double) stat->count; double v; int i; for (i = 0; i < stat->count; i += 1) { long long x = g_array_index (stat->values, long long, i); f += (m - (double) x) * (m - (double) x); } v = f / (double) stat->count; return sqrt (v); } int ll_comp (const void* a, const void* b) { const long long* lla = a; const long long* llb = b; return (*lla) - (*llb); } void stat_int_histogram (IntStat *stat) { int i, consec; long long cum = 0; FILE* p_out; FILE* s_out; if (! (p_out = config_output ("%s.pop.hist", stat->name))) abort (); if (! (s_out = config_output ("%s.sum.hist", stat->name))) abort (); qsort (stat->values->data, stat->count, sizeof (long long), ll_comp); for (i = 0; i < stat->count; i += consec) { long long ix = g_array_index (stat->values, long long, i); for (consec = 1; (i+consec) < stat->count; consec += 1) { long long jx = g_array_index (stat->values, long long, i+consec); if (ix != jx) break; } cum += consec * g_array_index (stat->values, long long, i); fprintf (p_out, "%qd, %0.3f\n", g_array_index (stat->values, long long, i), (double) (i+consec) / (double) stat->count); fprintf (s_out, "%qd, %0.3f\n", g_array_index (stat->values, long long, i), (double) cum / (double) stat->sum); } if (fclose (p_out) < 0 || fclose (s_out) < 0) { g_error ("fclose failed\n"); } } void stat_int_report (IntStat* stat) { FILE* out; if (! (out = config_output ("%s.stat", stat->name))) abort (); fprintf (out, "Name: %s\n", stat->name); fprintf (out, "Count: %d\n", stat->count); fprintf (out, "Min: %qd\n", stat->min); fprintf (out, "Max: %qd\n", stat->max); fprintf (out, "Sum: %qd\n", stat->sum); fprintf (out, "Mean: %0.2f\n", (double) stat->sum / (double) stat->count); fprintf (out, "Stddev: %0.2f\n", stat_int_stddev (stat)); if (fclose (out) < 0) g_error ("fclose failed"); stat_int_histogram (stat); } /* Dbl stat */ DblStat* stat_dbl_new (const char* name) { DblStat* s = g_new0 (DblStat, 1); s->name = name; s->values = g_array_new (FALSE, FALSE, sizeof (double)); return s; } void stat_dbl_add_item (DblStat* stat, double v) { if (! stat->count) stat->min = v; stat->count += 1; stat->min = MIN (v, stat->min); stat->max = MAX (v, stat->max); stat->sum += v; g_array_append_val (stat->values, v); } double stat_dbl_stddev (DblStat *stat) { double f = 0; double m = stat->sum / stat->count; double v; int i; for (i = 0; i < stat->count; i += 1) { double x = g_array_index (stat->values, double, i); f += (m - x) * (m - x); } v = f / stat->count; return sqrt (v); } int dbl_comp (const void* a, const void* b) { const double* da = a; const double* db = b; double diff = (*da) - (*db); if (diff > 0.0) return 1; else if (diff < 0.0) return -1; else return 0; } void stat_dbl_histogram (DblStat *stat) { int i, consec; double cum = 0.0; FILE* p_out; FILE* s_out; if (! (p_out = config_output ("%s.pop.hist", stat->name))) abort (); if (! (s_out = config_output ("%s.sum.hist", stat->name))) abort (); qsort (stat->values->data, stat->count, sizeof (double), dbl_comp); for (i = 0; i < stat->count; i += consec) { double ix = g_array_index (stat->values, double, i); for (consec = 1; (i+consec) < stat->count; consec += 1) { double jx = g_array_index (stat->values, double, i+consec); if (ix != jx) break; } cum += ((double) consec) * g_array_index (stat->values, double, i); fprintf (p_out, "%0.6f, %0.3f\n", g_array_index (stat->values, double, i), (double) (i+consec) / (double) stat->count); fprintf (s_out, "%0.6f, %0.3f\n", g_array_index (stat->values, double, i), cum / stat->sum); } if (fclose (p_out) < 0 || fclose (s_out) < 0) { g_error ("fclose failed\n"); } } void stat_dbl_report (DblStat* stat) { FILE* out; if (! (out = config_output ("%s.stat", stat->name))) abort (); fprintf (out, "Name: %s\n", stat->name); fprintf (out, "Count: %d\n", stat->count); fprintf (out, "Min: %0.6f\n", stat->min); fprintf (out, "Max: %0.6f\n", stat->max); fprintf (out, "Sum: %0.6f\n", stat->sum); fprintf (out, "Mean: %0.6f\n", stat->sum / stat->count); fprintf (out, "Stddev: %0.6f\n", stat_dbl_stddev (stat)); if (fclose (out) < 0) g_error ("fclose failed"); stat_dbl_histogram (stat); } /* Bincount */ BinCounter* stat_bincount_new (const char* name) { BinCounter* bc = g_new0 (BinCounter, 1); bc->name = name; bc->bins = g_ptr_array_new (); return bc; } void stat_bincount_add_item (BinCounter* bc, int bin, double val) { GArray* one; int last; if (bin >= bc->bins->len) { g_ptr_array_set_size (bc->bins, bin+1); } if (! (one = bc->bins->pdata[bin])) { one = bc->bins->pdata[bin] = g_array_new (FALSE, TRUE, sizeof (double)); } g_assert (one); last = one->len; g_array_set_size (one, last + 1); g_array_index (one, double, last) = val; } void stat_bincount_report (BinCounter* bc) { FILE *avg_out; FILE *raw_out; int i; if (! (avg_out = config_output ("%s.avg", bc->name))) abort (); if (! (raw_out = config_output ("%s.raw", bc->name))) abort (); for (i = 0; i < bc->bins->len; i += 1) { GArray* one = bc->bins->pdata[i]; double sum = 0.0; int j; for (j = 0; j < one->len; j += 1) { double d = g_array_index (one, double, j); sum += d; fprintf (raw_out, "%e ", d); } fprintf (raw_out, "\n"); fprintf (avg_out, "%e %d\n", sum / one->len, one->len); } if (fclose (avg_out) < 0) g_error ("fclose failed"); if (fclose (raw_out) < 0) g_error ("fclose failed"); } /* Config stuff */ int config_create_dir (const char* dirname) { struct stat buf; if (stat (dirname, & buf) < 0) { if (mkdir (dirname, 0777) < 0) { fprintf (stderr, "mkdir failed: %s\n", dirname); return errno; } } else { if (! S_ISDIR (buf.st_mode)) { fprintf (stderr, "not a directory: %s\n", dirname); return errno; } } return 0; } int config_clear_dir (const char* dir) { char buf[1024]; if (dir) { sprintf (buf, "rm -rf %s", dir); system (buf); } return 0; } static ConfigOption all_options[64]; static int option_count; void config_init () { static gboolean once = FALSE; if (! once) { once = TRUE; config_register (config_options, ARRAY_SIZE (config_options)); } } void config_register (ConfigOption *opts, int nopts) { int i; config_init (); for (i = 0; i < nopts; i += 1) { all_options[option_count++] = opts[i]; } } void config_set_string (const char* var, const char* val) { int i; for (i = 0; i < option_count; i += 1) { ConfigOption *opt = all_options + i; if (strcmp (opt->name, var) == 0) { (* (const char**) opt->value) = val; opt->found = TRUE; return; } } } int config_parse (const char* config_file) { FILE *in; char oname[1024], value[1024]; int i; if (! (in = fopen (config_file, "r"))) { fprintf (stderr, "fopen failed: %s\n", config_file); return errno; } for (;;) { ConfigOption *opt = NULL; if (fscanf (in, "%s", oname) != 1) break; for (i = 0; i < option_count; i += 1) { if (strcmp (oname, all_options[i].name) == 0) { opt = all_options + i; break; } } if (opt && opt->arg == CO_None) { (* (gboolean*) opt->value) = TRUE; opt->found = TRUE; continue; } if (fscanf (in, "%s", value) != 1) { fprintf (stderr, "no value for option: %s; file: %s\n", oname, config_file); goto abort; } if (! opt) { /*fprintf (stderr, "unrecognized option: %s\n", oname);*/ continue; } switch (opt->type) { case CD_Bool: if (strcasecmp (value, "yes") == 0 || strcasecmp (value, "true") == 0 || strcmp (value, "1") == 0 || strcasecmp (value, "on") == 0) { ((gboolean*) opt->value) = TRUE; } else { ((gboolean*) opt->value) = FALSE; } break; case CD_Int32: if (sscanf (value, "%d", (gint32*) opt->value) != 1) { fprintf (stderr, "parse error for option: %s; file: %s\n", oname, config_file); goto abort; } break; case CD_Double: if (sscanf (value, "%lf", (double*) opt->value) != 1) { fprintf (stderr, "parse error for option: %s; file: %s\n", oname, config_file); goto abort; } break; case CD_String: (* (const char**) opt->value) = g_strdup (value); break; } opt->found = TRUE; } fclose (in); return 0; abort: fclose (in); return -1; } int config_compute_output_dir () { char tmp[1024]; char buf[1024]; int i; gboolean last = FALSE; buf[0] = 0; for (i = 0; i < option_count; i += 1) { ConfigOption *opt = all_options + i; if (opt->style == CS_Ignore) continue; if (! opt->found) continue; if (last) strcat (buf, ","); last = TRUE; strcat (buf, opt->abbrev); strcat (buf, "="); switch (opt->type) { case CD_Bool: if (* (gboolean*) opt->value) strcat (buf, "true"); else strcat (buf, "false"); break; case CD_Int32: sprintf (tmp, "%d", (* (gint32*) opt->value)); strcat (buf, tmp); break; case CD_Double: sprintf (tmp, "%0.2f", (* (double*) opt->value)); strcat (buf, tmp); break; case CD_String: if (opt->style == CS_UseAsFile) { const char* str = (* (const char**) opt->value); const char* ls = strrchr (str, '/'); strcat (buf, ls ? (ls + 1) : str); } else { strcat (buf, (* (const char**) opt->value)); } break; } } config_output_dir = g_strdup_printf ("%s/%s", config_output_base, buf); return 0; } int config_done (void) { int i, ret; FILE *out; for (i = 0; i < option_count; i += 1) { ConfigOption *opt = all_options + i; if (! opt->found && opt->arg == CO_Required) { fprintf (stderr, "required option not found: %s\n", all_options[i].name); return -1; } } if ((ret = config_compute_output_dir ())) { return ret; } if ((ret = config_clear_dir (config_output_dir))) { return ret; } if ((ret = config_create_dir (config_output_dir))) { return ret; } if (! (out = config_output ("Options"))) abort (); for (i = 0; i < option_count; i += 1) { ConfigOption *opt = all_options + i; fprintf (out, "option: %s; value: ", all_options[i].name); switch (opt->type) { case CD_Bool: fprintf (out, "%s", (* (gboolean*) opt->value) ? "TRUE" : "FALSE"); break; case CD_Int32: fprintf (out, "%d", (* (gint32*) opt->value)); break; case CD_Double: fprintf (out, "%0.2f", (* (double*) opt->value)); break; case CD_String: fprintf (out, "%s", (* (const char**) opt->value)); break; } fprintf (out, "\n"); } if (fclose (out)) { fprintf (stderr, "fclose failed\n"); return errno; } return 0; } const char* config_help_arg (ConfigOption *opt) { switch (opt->arg) { case CO_Required: return "required"; case CO_Optional: return "optional"; case CO_None: return "no value"; } return "unknown"; } const char* config_help_type (ConfigOption *opt) { switch (opt->arg) { case CO_None: return "boolean"; default: break; } switch (opt->type) { case CD_Bool: return "boolean"; case CD_Int32: return "int"; case CD_Double: return "double"; case CD_String: return "string"; } return "unknown"; } void config_help (void) { int i; fprintf (stderr, "Expecting the following options in one or more config files on the command line:\n"); for (i = 0; i < option_count; i += 1) { ConfigOption *opt = all_options + i; fprintf (stderr, "%s: %s %s\n", opt->name, config_help_arg (opt), config_help_type (opt)); } } FILE* config_output (const char* format, ...) { gchar *buffer; gchar *file; va_list args; FILE *f; va_start (args, format); buffer = g_strdup_vprintf (format, args); va_end (args); file = g_strdup_printf ("%s/%s", config_output_dir, buffer); if (! (f = fopen (file, "w"))) g_error ("fopen failed: %s\n", buffer); g_free (file); g_free (buffer); return f; } #include #include #include #include "xdfs.h" /* Warning: very cheesy! */ #ifdef DEBUG_EXTRACT FileHandle *fh2 = handle_read_file (filename); guint8* debug_buf = g_malloc (buflen); if (! handle_read (fh2, debug_buf, buflen)) g_error ("read failed"); #endif gboolean rcs_count (const char* filename, guint *encoded_size) { char *readbuf0, *readbuf; gboolean in_string = FALSE; gboolean in_text = FALSE; guint string_start = 0; guint string_end = 0; guint current_pos = 0; /*char *current_delta = NULL;*/ FileHandle *fh = handle_read_file (filename); guint buflen = handle_length (fh); (* encoded_size) = 0; readbuf0 = g_new (guint8, buflen); for (;;) { int c = handle_gets (fh, readbuf0, buflen); readbuf = readbuf0; if (c < 0) break; if (strncmp (readbuf, "text", 4) == 0) in_text = TRUE; if (! in_string && readbuf[0] == '@') { string_start = current_pos + 1; in_string = TRUE; readbuf += 1; } current_pos += c; if (in_string) { while ((readbuf = strchr (readbuf, '@'))) { if (readbuf[1] == '@') { string_start += 1; /* @@@ bogus, just counting. */ readbuf += 2; continue; } in_string = FALSE; break; } string_end = current_pos - 2; if (in_text && ! in_string) { in_text = FALSE; /*g_free (current_delta); current_delta = NULL;*/ (* encoded_size) += (string_end - string_start); } continue; } if (isdigit (readbuf[0])) { #if 0 (* strchr (readbuf, '\n')) = 0; if (current_delta) g_free (current_delta); current_delta = g_strdup (readbuf); #endif } } handle_close (fh); g_free (readbuf0); #if 0 if (current_delta) g_free (current_delta); #endif return TRUE; } #if 0 int main (int argc, char** argv) { guint size; if (argc != 2) g_error ("usage: %s RCS_file\n", argv[0]); if (! rcs_count (argv[1], &size)) g_error ("rcs_parse failed"); return 0; } #endif