Add Oliver Fleetwood as a contributor
[alexxy/gromacs.git] / src / gromacs / utility / binaryinformation.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2004, The GROMACS development team.
6  * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
7  * Copyright (c) 2018,2019,2020,2021, by the GROMACS development team, led by
8  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9  * and including many others, as listed in the AUTHORS file in the
10  * top-level source directory and at http://www.gromacs.org.
11  *
12  * GROMACS is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public License
14  * as published by the Free Software Foundation; either version 2.1
15  * of the License, or (at your option) any later version.
16  *
17  * GROMACS is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with GROMACS; if not, see
24  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
26  *
27  * If you want to redistribute modifications to GROMACS, please
28  * consider that scientific software is very special. Version
29  * control is crucial - bugs must be traceable. We will be happy to
30  * consider code for inclusion in the official distribution, but
31  * derived work must not be called official GROMACS. Details are found
32  * in the README & COPYING files - if they are missing, get the
33  * official version at http://www.gromacs.org.
34  *
35  * To help us fund GROMACS development, we humbly ask that you cite
36  * the research papers on the package. Check out http://www.gromacs.org.
37  */
38 /*! \internal \file
39  * \brief Implements functionality for printing information about the
40  * currently running binary
41  *
42  * \ingroup module_utility
43  */
44 #include "gmxpre.h"
45
46 #include "binaryinformation.h"
47
48 #include "config.h"
49
50 #if GMX_FFT_FFTW3 || GMX_FFT_ARMPL_FFTW3
51 // Needed for construction of the FFT library description string
52 #    include <fftw3.h>
53 #endif
54
55 #if HAVE_LIBMKL
56 #    include <mkl.h>
57 #endif
58
59 #if HAVE_EXTRAE
60 #    include <extrae_user_events.h>
61 #endif
62
63 #if GMX_USE_HWLOC
64 #    include <hwloc.h>
65 #endif
66
67 #include <cstdio>
68 #include <cstdlib>
69 #include <cstring>
70
71 #include <algorithm>
72 #include <array>
73 #include <string>
74
75 /* This file is completely threadsafe - keep it that way! */
76
77 #include "buildinfo.h"
78 #include "gromacs/utility/arraysize.h"
79 #include "gromacs/utility/baseversion.h"
80 #include "gromacs/utility/exceptions.h"
81 #include "gromacs/utility/gmxassert.h"
82 #include "gromacs/utility/path.h"
83 #include "gromacs/utility/programcontext.h"
84 #include "gromacs/utility/stringutil.h"
85 #include "gromacs/utility/sysinfo.h"
86 #include "gromacs/utility/textwriter.h"
87
88 #include "cuda_version_information.h"
89 #include "sycl_version_information.h"
90
91 namespace
92 {
93
94 using gmx::formatString;
95
96 //! \cond Doxygen does not need to care about most of this stuff, and the macro usage is painful to document
97
98 int centeringOffset(int width, int length)
99 {
100     return std::max(width - length, 0) / 2;
101 }
102
103 std::string formatCentered(int width, const char* text)
104 {
105     const int offset = centeringOffset(width, std::strlen(text));
106     return formatString("%*s%s", offset, "", text);
107 }
108
109 void printCopyright(gmx::TextWriter* writer)
110 {
111     // Contributors sorted alphabetically by last name
112     static const char* const Contributors[]  = { "Andrey Alekseenko",
113                                                 "Emile Apol",
114                                                 "Rossen Apostolov",
115                                                 "Paul Bauer",
116                                                 "Herman J.C. Berendsen",
117                                                 "Par Bjelkmar",
118                                                 "Christian Blau",
119                                                 "Viacheslav Bolnykh",
120                                                 "Kevin Boyd",
121                                                 "Aldert van Buuren",
122                                                 "Rudi van Drunen",
123                                                 "Anton Feenstra",
124                                                 "Oliver Fleetwood",
125                                                 "Gaurav Garg",
126                                                 "Gilles Gouaillardet",
127                                                 "Alan Gray",
128                                                 "Gerrit Groenhof",
129                                                 "Anca Hamuraru",
130                                                 "Vincent Hindriksen",
131                                                 "M. Eric Irrgang",
132                                                 "Aleksei Iupinov",
133                                                 "Christoph Junghans",
134                                                 "Joe Jordan",
135                                                 "Dimitrios Karkoulis",
136                                                 "Peter Kasson",
137                                                 "Jiri Kraus",
138                                                 "Carsten Kutzner",
139                                                 "Per Larsson",
140                                                 "Justin A. Lemkul",
141                                                 "Viveca Lindahl",
142                                                 "Magnus Lundborg",
143                                                 "Erik Marklund",
144                                                 "Pascal Merz",
145                                                 "Pieter Meulenhoff",
146                                                 "Teemu Murtola",
147                                                 "Szilard Pall",
148                                                 "Sander Pronk",
149                                                 "Roland Schulz",
150                                                 "Michael Shirts",
151                                                 "Alexey Shvetsov",
152                                                 "Alfons Sijbers",
153                                                 "Peter Tieleman",
154                                                 "Jon Vincent",
155                                                 "Teemu Virolainen",
156                                                 "Christian Wennberg",
157                                                 "Maarten Wolf",
158                                                 "Artem Zhmurov" };
159     static const char* const CopyrightText[] = {
160         "Copyright (c) 1991-2000, University of Groningen, The Netherlands.",
161         "Copyright (c) 2001-2019, The GROMACS development team at",
162         "Uppsala University, Stockholm University and",
163         "the Royal Institute of Technology, Sweden.",
164         "check out http://www.gromacs.org for more information."
165     };
166
167 #define NCONTRIBUTORS static_cast<int>(asize(Contributors))
168 #define NCR static_cast<int>(asize(CopyrightText))
169
170     // TODO a centering behaviour of TextWriter could be useful here
171     writer->writeLine(formatCentered(78, "GROMACS is written by:"));
172     for (int i = 0; i < NCONTRIBUTORS;)
173     {
174         for (int j = 0; j < 3 && i < NCONTRIBUTORS; ++j, ++i)
175         {
176             const int            width = 26;
177             std::array<char, 30> buf;
178             const int            offset = centeringOffset(width, strlen(Contributors[i]));
179             GMX_RELEASE_ASSERT(static_cast<int>(strlen(Contributors[i])) + offset < gmx::ssize(buf),
180                                "Formatting buffer is not long enough");
181             std::fill(buf.begin(), buf.begin() + offset, ' ');
182             std::strncpy(buf.data() + offset, Contributors[i], gmx::ssize(buf) - offset);
183             writer->writeString(formatString(" %-*s", width, buf.data()));
184         }
185         writer->ensureLineBreak();
186     }
187     writer->writeLine(formatCentered(78, "and the project leaders:"));
188     writer->writeLine(
189             formatCentered(78, "Mark Abraham, Berk Hess, Erik Lindahl, and David van der Spoel"));
190     writer->ensureEmptyLine();
191     for (int i = 0; i < NCR; ++i)
192     {
193         writer->writeLine(CopyrightText[i]);
194     }
195     writer->ensureEmptyLine();
196
197     // Folding At Home has different licence to allow digital
198     // signatures in GROMACS, so does not need to show the normal
199     // license statement.
200     if (!GMX_FAHCORE)
201     {
202         writer->writeLine("GROMACS is free software; you can redistribute it and/or modify it");
203         writer->writeLine("under the terms of the GNU Lesser General Public License");
204         writer->writeLine("as published by the Free Software Foundation; either version 2.1");
205         writer->writeLine("of the License, or (at your option) any later version.");
206     }
207 }
208
209 //! Construct a string that describes the library that provides CPU FFT support to this build
210 const char* getCpuFftDescriptionString()
211 {
212 // Define the FFT description string
213 #if GMX_FFT_FFTW3 || GMX_FFT_ARMPL_FFTW3
214 #    if GMX_NATIVE_WINDOWS
215     // Don't buy trouble
216     return "fftw3";
217 #    else
218     // Use the version string provided by libfftw3
219 #        if GMX_DOUBLE
220     return fftw_version;
221 #        else
222     return fftwf_version;
223 #        endif
224 #    endif
225 #endif
226 #if GMX_FFT_MKL
227     return "Intel MKL";
228 #endif
229 #if GMX_FFT_FFTPACK
230     return "fftpack (built-in)";
231 #endif
232 };
233
234 //! Construct a string that describes the library that provides GPU FFT support to this build
235 const char* getGpuFftDescriptionString()
236 {
237     if (GMX_GPU)
238     {
239         if (GMX_GPU_CUDA)
240         {
241             return "cuFFT";
242         }
243         else if (GMX_GPU_OPENCL)
244         {
245             return "clFFT";
246         }
247         else if (GMX_GPU_SYCL)
248         {
249             return "unknown";
250         }
251         else
252         {
253             GMX_RELEASE_ASSERT(false, "Unknown GPU configuration");
254             return "impossible";
255         }
256     }
257     else
258     {
259         return "none";
260     }
261 };
262
263 void gmx_print_version_info(gmx::TextWriter* writer)
264 {
265     writer->writeLine(formatString("GROMACS version:    %s", gmx_version()));
266     const char* const git_hash = gmx_version_git_full_hash();
267     if (git_hash[0] != '\0')
268     {
269         writer->writeLine(formatString("GIT SHA1 hash:      %s", git_hash));
270     }
271     const char* const base_hash = gmx_version_git_central_base_hash();
272     if (base_hash[0] != '\0')
273     {
274         writer->writeLine(formatString("Branched from:      %s", base_hash));
275     }
276     const char* const releaseSourceChecksum = gmxReleaseSourceChecksum();
277     const char* const currentSourceChecksum = gmxCurrentSourceChecksum();
278     if (releaseSourceChecksum[0] != '\0')
279     {
280         if (std::strcmp(releaseSourceChecksum, "NoChecksumFile") == 0)
281         {
282             writer->writeLine(formatString(
283                     "The source code this program was compiled from has not been verified because "
284                     "the reference checksum was missing during compilation. This means you have an "
285                     "incomplete GROMACS distribution, please make sure to download an intact "
286                     "source distribution and compile that before proceeding."));
287             writer->writeLine(formatString("Computed checksum: %s", currentSourceChecksum));
288         }
289         else if (std::strcmp(releaseSourceChecksum, "NoPythonAvailable") == 0)
290         {
291             writer->writeLine(
292                     formatString("Build source could not be verified, because the checksum could "
293                                  "not be computed."));
294         }
295         else if (std::strcmp(releaseSourceChecksum, currentSourceChecksum) != 0)
296         {
297             writer->writeLine(formatString(
298                     "This program has been built from source code that has been altered and does "
299                     "not match the code released as part of the official GROMACS version %s. If "
300                     "you did not intend to use an altered GROMACS version, make sure to download "
301                     "an intact source distribution and compile that before proceeding.",
302                     gmx_version()));
303             writer->writeLine(formatString(
304                     "If you have modified the source code, you are strongly encouraged to set your "
305                     "custom version suffix (using -DGMX_VERSION_STRING_OF_FORK) which will can "
306                     "help later with scientific reproducibility but also when reporting bugs."));
307             writer->writeLine(formatString("Release checksum: %s", releaseSourceChecksum));
308             writer->writeLine(formatString("Computed checksum: %s", currentSourceChecksum));
309         }
310         else
311         {
312             writer->writeLine(formatString("Verified release checksum is %s", releaseSourceChecksum));
313         }
314     }
315
316
317 #if GMX_DOUBLE
318     writer->writeLine("Precision:          double");
319 #else
320     writer->writeLine("Precision:          mixed");
321 #endif
322     writer->writeLine(formatString("Memory model:       %u bit", static_cast<unsigned>(8 * sizeof(void*))));
323
324 #if GMX_THREAD_MPI
325     writer->writeLine("MPI library:        thread_mpi");
326 #elif GMX_MPI
327 #    if HAVE_CUDA_AWARE_MPI
328     writer->writeLine("MPI library:        MPI (CUDA-aware)");
329 #    else
330     writer->writeLine("MPI library:        MPI");
331 #    endif
332 #else
333     writer->writeLine("MPI library:        none");
334 #endif
335 #if GMX_OPENMP
336     writer->writeLine(formatString("OpenMP support:     enabled (GMX_OPENMP_MAX_THREADS = %d)",
337                                    GMX_OPENMP_MAX_THREADS));
338 #else
339     writer->writeLine("OpenMP support:     disabled");
340 #endif
341     writer->writeLine(formatString("GPU support:        %s", getGpuImplementationString()));
342     writer->writeLine(formatString("SIMD instructions:  %s", GMX_SIMD_STRING));
343     writer->writeLine(formatString("CPU FFT library:    %s", getCpuFftDescriptionString()));
344     writer->writeLine(formatString("GPU FFT library:    %s", getGpuFftDescriptionString()));
345 #if GMX_TARGET_X86
346     writer->writeLine(formatString("RDTSCP usage:       %s", GMX_USE_RDTSCP ? "enabled" : "disabled"));
347 #endif
348 #if GMX_USE_TNG
349     writer->writeLine("TNG support:        enabled");
350 #else
351     writer->writeLine("TNG support:        disabled");
352 #endif
353 #if GMX_USE_HWLOC
354     writer->writeLine(formatString("Hwloc support:      hwloc-%s", HWLOC_VERSION));
355 #else
356     writer->writeLine("Hwloc support:      disabled");
357 #endif
358 #if HAVE_EXTRAE
359     unsigned major, minor, revision;
360     Extrae_get_version(&major, &minor, &revision);
361     writer->writeLine(formatString(
362             "Tracing support:    enabled. Using Extrae-%d.%d.%d", major, minor, revision));
363 #else
364     writer->writeLine("Tracing support:    disabled");
365 #endif
366
367
368     /* TODO: The below strings can be quite long, so it would be nice to wrap
369      * them. Can wait for later, as the master branch has ready code to do all
370      * that. */
371     writer->writeLine(formatString("C compiler:         %s", BUILD_C_COMPILER));
372     writer->writeLine(formatString(
373             "C compiler flags:   %s %s", BUILD_CFLAGS, CMAKE_BUILD_CONFIGURATION_C_FLAGS));
374     writer->writeLine(formatString("C++ compiler:       %s", BUILD_CXX_COMPILER));
375     writer->writeLine(formatString(
376             "C++ compiler flags: %s %s", BUILD_CXXFLAGS, CMAKE_BUILD_CONFIGURATION_CXX_FLAGS));
377 #if HAVE_LIBMKL
378     /* MKL might be used for LAPACK/BLAS even if FFTs use FFTW, so keep it separate */
379     writer->writeLine(formatString(
380             "Intel MKL version:  %d.%d.%d", __INTEL_MKL__, __INTEL_MKL_MINOR__, __INTEL_MKL_UPDATE__));
381 #endif
382 #if GMX_GPU_OPENCL
383     writer->writeLine(formatString("OpenCL include dir: %s", OPENCL_INCLUDE_DIR));
384     writer->writeLine(formatString("OpenCL library:     %s", OPENCL_LIBRARY));
385     writer->writeLine(formatString("OpenCL version:     %s", OPENCL_VERSION_STRING));
386 #endif
387 #if GMX_GPU_CUDA
388     writer->writeLine(formatString("CUDA compiler:      %s", CUDA_COMPILER_INFO));
389     writer->writeLine(formatString(
390             "CUDA compiler flags:%s %s", CUDA_COMPILER_FLAGS, CMAKE_BUILD_CONFIGURATION_CXX_FLAGS));
391     writer->writeLine("CUDA driver:        " + gmx::getCudaDriverVersionString());
392     writer->writeLine("CUDA runtime:       " + gmx::getCudaRuntimeVersionString());
393 #endif
394 #if GMX_SYCL_DPCPP
395     writer->writeLine(formatString("SYCL DPC++ flags:   %s", SYCL_DPCPP_COMPILER_FLAGS));
396     writer->writeLine("SYCL DPC++ version: " + gmx::getSyclCompilerVersion());
397 #endif
398 #if GMX_SYCL_HIPSYCL
399     writer->writeLine(formatString("hipSYCL launcher:   %s", SYCL_HIPSYCL_COMPILER_LAUNCHER));
400     writer->writeLine(formatString("hipSYCL flags:      %s", SYCL_HIPSYCL_COMPILER_FLAGS));
401     writer->writeLine(formatString("hipSYCL targets:    %s", SYCL_HIPSYCL_TARGETS));
402     writer->writeLine("hipSYCL version:    " + gmx::getSyclCompilerVersion());
403 #endif
404 }
405
406 //! \endcond
407
408 } // namespace
409
410 namespace gmx
411 {
412
413 BinaryInformationSettings::BinaryInformationSettings() :
414     bExtendedInfo_(false),
415     bCopyright_(false),
416     bProcessId_(false),
417     bGeneratedByHeader_(false),
418     prefix_(""),
419     suffix_("")
420 {
421 }
422
423 void printBinaryInformation(FILE* fp, const IProgramContext& programContext)
424 {
425     TextWriter writer(fp);
426     printBinaryInformation(&writer, programContext, BinaryInformationSettings());
427 }
428
429 void printBinaryInformation(FILE*                            fp,
430                             const IProgramContext&           programContext,
431                             const BinaryInformationSettings& settings)
432 {
433     try
434     {
435         TextWriter writer(fp);
436         printBinaryInformation(&writer, programContext, settings);
437     }
438     GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
439 }
440
441 void printBinaryInformation(TextWriter*                      writer,
442                             const IProgramContext&           programContext,
443                             const BinaryInformationSettings& settings)
444 {
445     // TODO Perhaps the writer could be configured with the prefix and
446     // suffix strings from the settings?
447     const char* prefix          = settings.prefix_;
448     const char* suffix          = settings.suffix_;
449     const char* precisionString = "";
450 #if GMX_DOUBLE
451     precisionString = " (double precision)";
452 #endif
453     const char* const name = programContext.displayName();
454     if (settings.bGeneratedByHeader_)
455     {
456         writer->writeLine(formatString("%sCreated by:%s", prefix, suffix));
457     }
458     // TODO: It would be nice to know here whether we are really running a
459     // Gromacs binary or some other binary that is calling Gromacs; we
460     // could then print "%s is part of GROMACS" or some alternative text.
461     std::string title = formatString(":-) GROMACS - %s, %s%s (-:", name, gmx_version(), precisionString);
462     const int indent =
463             centeringOffset(78 - std::strlen(prefix) - std::strlen(suffix), title.length()) + 1;
464     writer->writeLine(formatString("%s%*c%s%s", prefix, indent, ' ', title.c_str(), suffix));
465     writer->writeLine(formatString("%s%s", prefix, suffix));
466     if (settings.bCopyright_)
467     {
468         GMX_RELEASE_ASSERT(prefix[0] == '\0' && suffix[0] == '\0',
469                            "Prefix/suffix not supported with copyright");
470         printCopyright(writer);
471         writer->ensureEmptyLine();
472         // This line is printed again after the copyright notice to make it
473         // appear together with all the other information, so that it is not
474         // necessary to read stuff above the copyright notice.
475         // The line above the copyright notice puts the copyright notice is
476         // context, though.
477         writer->writeLine(formatString(
478                 "%sGROMACS:      %s, version %s%s%s", prefix, name, gmx_version(), precisionString, suffix));
479     }
480     const char* const binaryPath = programContext.fullBinaryPath();
481     if (!gmx::isNullOrEmpty(binaryPath))
482     {
483         writer->writeLine(formatString("%sExecutable:   %s%s", prefix, binaryPath, suffix));
484     }
485     const gmx::InstallationPrefixInfo installPrefix = programContext.installationPrefix();
486     if (!gmx::isNullOrEmpty(installPrefix.path))
487     {
488         writer->writeLine(formatString("%sData prefix:  %s%s%s",
489                                        prefix,
490                                        installPrefix.path,
491                                        installPrefix.bSourceLayout ? " (source tree)" : "",
492                                        suffix));
493     }
494     const std::string workingDir = Path::getWorkingDirectory();
495     if (!workingDir.empty())
496     {
497         writer->writeLine(formatString("%sWorking dir:  %s%s", prefix, workingDir.c_str(), suffix));
498     }
499     if (settings.bProcessId_)
500     {
501         writer->writeLine(formatString("%sProcess ID:   %d%s", prefix, gmx_getpid(), suffix));
502     }
503     const char* const commandLine = programContext.commandLine();
504     if (!gmx::isNullOrEmpty(commandLine))
505     {
506         writer->writeLine(formatString(
507                 "%sCommand line:%s\n%s  %s%s", prefix, suffix, prefix, commandLine, suffix));
508     }
509     if (settings.bExtendedInfo_)
510     {
511         GMX_RELEASE_ASSERT(prefix[0] == '\0' && suffix[0] == '\0',
512                            "Prefix/suffix not supported with extended info");
513         writer->ensureEmptyLine();
514         gmx_print_version_info(writer);
515     }
516 }
517
518 } // namespace gmx