tools/perf: pmu-events: Fix reproducibility
authorBen Hutchings <ben@decadent.org.uk>
Sun, 25 Aug 2019 12:49:41 +0000 (13:49 +0100)
committerBen Hutchings <benh@debian.org>
Tue, 9 Jun 2020 17:50:00 +0000 (18:50 +0100)
Forwarded: https://lore.kernel.org/lkml/20190825131329.naqzd5kwg7mw5d3f@decadent.org.uk/T/#u

jevents.c uses nftw() to enumerate files and outputs the corresponding
C structs in the order they are found.  This makes it sensitive to
directory ordering, so that the perf executable is not reproducible.

To avoid this, store all the files and directories found and then sort
them by their (relative) path.  (This maintains the parent-first
ordering that nftw() promises.)  Then apply the existing callbacks to
them in the sorted order.

Don't both storing the stat buffers as we don't need them.

References: https://tests.reproducible-builds.org/debian/dbdtxt/bullseye/i386/linux_4.19.37-6.diffoscope.txt.gz
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Gbp-Pq: Topic bugfix/all
Gbp-Pq: Name tools-perf-pmu-events-fix-reproducibility.patch

tools/perf/pmu-events/jevents.c

index 27b4da80f751177968848ff1656caaa5e91dfb1c..8495e79a78942723a8211e4eddca9b28e7f6b2f3 100644 (file)
 #include "json.h"
 #include "jevents.h"
 
+struct found_file {
+       const char      *fpath;
+       int             typeflag;
+       struct FTW      ftwbuf;
+};
+
 int verbose;
 char *prog;
 
@@ -885,6 +891,44 @@ static int get_maxfds(void)
  * nftw() doesn't let us pass an argument to the processing function,
  * so use a global variables.
  */
+static struct found_file *found_files;
+static size_t n_found_files;
+static size_t max_found_files;
+
+static int add_one_file(const char *fpath, const struct stat *sb,
+                       int typeflag, struct FTW *ftwbuf)
+{
+       struct found_file *file;
+
+       if (ftwbuf->level == 0 || ftwbuf->level > 3)
+               return 0;
+
+       /* Grow array if necessary */
+       if (n_found_files >= max_found_files) {
+               if (max_found_files == 0)
+                       max_found_files = 16;
+               else
+                       max_found_files *= 2;
+               found_files = realloc(found_files,
+                                     max_found_files * sizeof(*found_files));
+       }
+
+       file = &found_files[n_found_files++];
+       file->fpath = strdup(fpath);
+       file->typeflag = typeflag;
+       file->ftwbuf = *ftwbuf;
+
+       return 0;
+}
+
+static int compare_files(const void *left, const void *right)
+{
+       const struct found_file *left_file = left;
+       const struct found_file *right_file = right;
+
+       return strcmp(left_file->fpath, right_file->fpath);
+}
+
 static FILE *eventsfp;
 static char *mapfile;
 
@@ -939,19 +983,19 @@ static int is_json_file(const char *name)
        return 0;
 }
 
-static int preprocess_arch_std_files(const char *fpath, const struct stat *sb,
+static int preprocess_arch_std_files(const char *fpath,
                                int typeflag, struct FTW *ftwbuf)
 {
        int level = ftwbuf->level;
        int is_file = typeflag == FTW_F;
 
        if (level == 1 && is_file && is_json_file(fpath))
-               return json_events(fpath, save_arch_std_events, (void *)sb);
+               return json_events(fpath, save_arch_std_events, NULL);
 
        return 0;
 }
 
-static int process_one_file(const char *fpath, const struct stat *sb,
+static int process_one_file(const char *fpath,
                            int typeflag, struct FTW *ftwbuf)
 {
        char *tblname, *bname;
@@ -976,9 +1020,9 @@ static int process_one_file(const char *fpath, const struct stat *sb,
        } else
                bname = (char *) fpath + ftwbuf->base;
 
-       pr_debug("%s %d %7jd %-20s %s\n",
+       pr_debug("%s %d %-20s %s\n",
                 is_file ? "f" : is_dir ? "d" : "x",
-                level, sb->st_size, bname, fpath);
+                level, bname, fpath);
 
        /* base dir or too deep */
        if (level == 0 || level > 3)
@@ -1089,6 +1133,7 @@ int main(int argc, char *argv[])
        const char *output_file;
        const char *start_dirname;
        struct stat stbuf;
+       size_t i;
 
        prog = basename(argv[0]);
        if (argc < 4) {
@@ -1132,8 +1177,26 @@ int main(int argc, char *argv[])
         */
 
        maxfds = get_maxfds();
+       rc = nftw(ldirname, add_one_file, maxfds, 0);
+       if (rc < 0) {
+               /* Make build fail */
+               free_arch_std_events();
+               return 1;
+       } else if (rc) {
+               goto empty_map;
+       }
+
+       /* Sort file names to ensure reproduciblity */
+       qsort(found_files, n_found_files, sizeof(*found_files), compare_files);
+
        mapfile = NULL;
-       rc = nftw(ldirname, preprocess_arch_std_files, maxfds, 0);
+       for (i = 0; i < n_found_files; i++) {
+               rc = preprocess_arch_std_files(found_files[i].fpath,
+                                              found_files[i].typeflag,
+                                              &found_files[i].ftwbuf);
+               if (rc)
+                       break;
+       }
        if (rc && verbose) {
                pr_info("%s: Error preprocessing arch standard files %s\n",
                        prog, ldirname);
@@ -1147,7 +1210,13 @@ int main(int argc, char *argv[])
                goto empty_map;
        }
 
-       rc = nftw(ldirname, process_one_file, maxfds, 0);
+       for (i = 0; i < n_found_files; i++) {
+               rc = process_one_file(found_files[i].fpath,
+                                     found_files[i].typeflag,
+                                     &found_files[i].ftwbuf);
+               if (rc)
+                       break;
+       }
        if (rc && verbose) {
                pr_info("%s: Error walking file tree %s\n", prog, ldirname);
                goto empty_map;