From: Nicholas Nethercote Date: Sat, 14 Feb 2004 16:40:02 +0000 (+0000) Subject: Adding Massif, the heap profiler. X-Git-Tag: svn/VALGRIND_2_1_1~63 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=563c4e566ae13a3b1f3951bb51345189cd7d6526;p=thirdparty%2Fvalgrind.git Adding Massif, the heap profiler. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@2245 --- diff --git a/Makefile.am b/Makefile.am index 0fb69e96da..6e3798d00a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,6 +9,7 @@ SUBDIRS = include coregrind . docs tests auxprogs \ cachegrind \ corecheck \ helgrind \ + massif \ lackey \ none diff --git a/configure.in b/configure.in index e23608f50c..00c9d53ed7 100644 --- a/configure.in +++ b/configure.in @@ -373,12 +373,16 @@ AC_OUTPUT( cachegrind/tests/Makefile cachegrind/docs/Makefile cachegrind/cg_annotate - corecheck/Makefile - corecheck/tests/Makefile - corecheck/docs/Makefile helgrind/Makefile helgrind/tests/Makefile helgrind/docs/Makefile + massif/Makefile + massif/hp2ps/Makefile + massif/tests/Makefile + massif/docs/Makefile + corecheck/Makefile + corecheck/tests/Makefile + corecheck/docs/Makefile lackey/Makefile lackey/tests/Makefile lackey/docs/Makefile diff --git a/coregrind/vg_include.h b/coregrind/vg_include.h index f69969925d..5e4d8109d3 100644 --- a/coregrind/vg_include.h +++ b/coregrind/vg_include.h @@ -1365,9 +1365,6 @@ extern Int VG_(vgexecfd); /* client executable file descriptor */ extern Int VG_(clexecfd); -/* Path to all our library/aux files */ -extern const Char *VG_(libdir); - /* Determine if %esp adjustment must be noted */ extern Bool VG_(need_to_handle_esp_assignment) ( void ); diff --git a/docs/manual.html b/docs/manual.html index 626fcd715e..3530a9994a 100644 --- a/docs/manual.html +++ b/docs/manual.html @@ -98,12 +98,15 @@ An open-source tool for debugging and profiling Linux-x86 executables.

Helgrind: a data-race detector

+

+ Massif: a heap profiler

+

The following is not part of the user manual. It describes how you can write tools for Valgrind, in order to make new program supervision tools. -

+

Valgrind Tools

@@ -111,10 +114,10 @@ The following are not part of the user manual. They describe internal details of how Valgrind works. Reading them may rot your brain. You have been warned. -

+

The design and implementation of Valgrind

-

+

10  How Cachegrind works


diff --git a/include/vg_skin.h.base b/include/vg_skin.h.base index c294a45680..f4562760fc 100644 --- a/include/vg_skin.h.base +++ b/include/vg_skin.h.base @@ -114,6 +114,10 @@ typedef unsigned char Bool; #include "vg_kerneliface.h" +/* Path to all our library/aux files */ +extern const Char *VG_(libdir); + + /*====================================================================*/ /*=== Core/skin interface version ===*/ /*====================================================================*/ diff --git a/massif/Makefile.am b/massif/Makefile.am new file mode 100644 index 0000000000..27ceaae1bd --- /dev/null +++ b/massif/Makefile.am @@ -0,0 +1,24 @@ + +SUBDIRS = . tests docs hp2ps + +AM_CPPFLAGS = -I$(top_srcdir)/include -DVG_LIBDIR="\"$(libdir)"\" +AM_CFLAGS = $(WERROR) -Winline -Wall -Wshadow -O -fomit-frame-pointer \ + @PREFERRED_STACK_BOUNDARY@ -g + +valdir = $(libdir)/valgrind +inplacedir = $(top_srcdir)/.in_place + +val_PROGRAMS = vgskin_massif.so vgpreload_massif.so + +vgskin_massif_so_SOURCES = ms_main.c +vgskin_massif_so_LDFLAGS = -shared + +vgpreload_massif_so_SOURCES = +vgpreload_massif_so_LDADD = $(top_srcdir)/coregrind/vg_replace_malloc.o +vgpreload_massif_so_DEPENDENCIES = $(top_srcdir)/coregrind/vg_replace_malloc.o +vgpreload_massif_so_LDFLAGS = -shared -Wl,-z,interpose,-z,initfirst + +all-local: + mkdir -p $(inplacedir) + -rm -f $(addprefix $(inplacedir)/,$(val_PROGRAMS)) + ln -f -s $(addprefix $(top_srcdir)/$(subdir)/,$(val_PROGRAMS)) $(inplacedir) diff --git a/massif/docs/Makefile.am b/massif/docs/Makefile.am new file mode 100644 index 0000000000..11bf87e44e --- /dev/null +++ b/massif/docs/Makefile.am @@ -0,0 +1,5 @@ +docdir = $(datadir)/doc/valgrind + +doc_DATA = ms_main.html date.gif + +EXTRA_DIST = $(doc_DATA) diff --git a/massif/docs/date.gif b/massif/docs/date.gif new file mode 100644 index 0000000000..eff527acec Binary files /dev/null and b/massif/docs/date.gif differ diff --git a/massif/docs/ms_main.html b/massif/docs/ms_main.html new file mode 100644 index 0000000000..87d1abcf95 --- /dev/null +++ b/massif/docs/ms_main.html @@ -0,0 +1,331 @@ + + + Massif: a heap profiler + + + + +

Massif: a heap profiler

+ +To use this tool, you must specify --tool=massif +on the Valgrind command line. + + +

7.1  Heap profiling

+Massif is a heap profiler, i.e. it measures how much heap memory programs use. +In particular, it can give you information about: + + +Heap profiling is useful to help you reduce the amount of memory your program +uses. On modern machines with virtual memory, this provides the following +benefits: + + +Also, there are certain space leaks that aren't detected by traditional +leak-checkers, such as Memcheck's. That's because the memory isn't ever +actually lost -- a pointer remains to it -- but it's not in use. Programs +that have leaks like this can unnecessarily increase the amount of memory +they are using over time. +

+ + + +

7.2  Why Use a Heap Profiler?

+ +Everybody knows how useful time profilers are for speeding up programs. They +are particularly useful because people are notoriously bad at predicting where +are the bottlenecks in their programs. +

+But the story is different for heap profilers. Some programming languages, +particularly lazy functional languages like Haskell, have quite sophisticated heap +profilers. But there are few tools as powerful for profiling C and C++ +programs. +

+Why is this? Maybe it's because C and C++ programmers must think that +they know where the memory is being allocated. After all, you can see all the +calls to malloc() and new and new[], +right? But, in a big program, do you really know which heap allocations are +being executed, how many times, and how large each allocation is? Can you give +even a vague estimate of the memory footprint for your program? Do you know +this for all the libraries your program uses? What about administration bytes +required by the heap allocator to track heap blocks -- have you thought about +them? What about the stack? If you are unsure about any of these things, +maybe you should think about heap profiling. +

+Massif can tell you these things. +

+Or maybe it's because it's relatively easy to add basic heap profiling +functionality into a program, to tell you how many bytes you have allocated for +certain objects, or similar. But this information might only be simple like +total counts for the whole program's execution. What about space usage at +different points in the program's execution, for example? And reimplementing +heap profiling code for each project is a pain. +

+Massif can save you this effort. +

+ + + +

7.3  Overview

+First off, as for normal Valgrind use, you probably want to compile with +debugging info (the -g flag). But, as opposed to Memcheck, +you probably do want to turn optimisation on, since you should profile +your program as it will be normally run. +

+Then, run your program with valgrind --tool=massif in front of the +normal command line invocation. When the program finishes, Massif will print +summary space statistics. It also creates a graph representing the program's +heap usage in a file called massif.pid.ps, which can +be read by any PostScript viewer, such as Ghostview. +

+It also puts detailed information about heap consumption in a file file +massif.pid.txt (text format) or +massif.pid.html (HTML format), where +pid is the program's process id. +

+ + + +

7.4  Basic Results of Profiling

+ +To gather heap profiling information about the program prog, +type: +

+

+valgrind --tool=massif prog +
+

+The program will execute (slowly). Upon completion, summary statistics +that look like this will be printed: + +

+==27519== Total spacetime:   2,258,106 ms.B
+==27519== heap:              24.0%
+==27519== heap admin:         2.2%
+==27519== stack(s):          73.7%
+
+ +All measurements are done in spacetime, i.e. space (in bytes) multiplied +by time (in milliseconds). Note that because Massif slows a program down a +lot, the actual spacetime figure is fairly meaningless; it's the relative +values that are interesting. +

+Which entries you see in the breakdown depends on the command line options +given. The above example measures all the possible parts of memory: +

+

+ + + +

7.5  Spacetime Graphs

+As well as printing summary information, Massif also creates a file +representing a spacetime graph, massif.pid.hp. +It will produce a file called massif.pid.ps, which can be +viewed in a PostScript viewer. +

+Massif uses a program called hp2ps to convert the raw data into +the PostScript graph. It's distributed with Massif, but came originally +from the Glasgow Haskell +Compiler. You shouldn't need to worry about this at all. However, if +the graph creation fails for any reason, Massif tell you, and will leave +behind a file named massif.pid.hp, containing the raw +heap profiling data. +

+Here's an example graph:
+ spacetime graph +

+The graph is broken into several bands. Most bands represent a single line of +your program that does some heap allocation; each such band represents all +the allocations and deallocations done from that line. Up to twenty bands are +shown; less significant allocation sites are merged into "other" and/or "OTHER" +bands. The accompanying text/HTML file produced by Massif has more detail +about these heap allocation bands. Then there are single bands for the +stack(s) and heap admin bytes. +

+Note: it's the height of a band that's important. Don't let the ups and downs +caused by other bands confuse you. For example, the +read_alias_file band in the example has the same height all the +time it's in existence. +

+The triangles on the x-axis show each point at which a memory census was taken. +These aren't necessarily evenly spread; Massif only takes a census when +memory is allocated or deallocated. The time on the x-axis is wallclock +time, which is not ideal because you can get different graphs for different +executions of the same program, due to random OS delays. But it's not too +bad, and it becomes less of a problem the longer a program runs. +

+Massif takes censuses at an appropriate timescale; censuses take place less +frequently as the program runs for longer. There is no point having more +than 100-200 censuses on a single graph. +

+The graphs give a good overview of where your program's space use comes from, +and how that varies over time. The accompanying text/HTML file gives a lot +more information about heap use. + + +

7.6  Details of Heap Allocations

+ +The text/HTML file contains information to help interpret the heap bands of the +graph. It also contains a lot of extra information about heap allocations that you don't see in the graph. +

+Here's part of the information that accompanies the above graph. + +


+== 0 ===========================
+Heap allocation functions accounted for 50.8% of measured spacetime
+

+Called from: +

+
+The first part shows the total spacetime due to heap allocations, and the +places in the program where most memory was allocated (nb: if this program had +been compiled with -g, actual line numbers would be given). These +places are sorted, from most significant to least, and correspond to the bands +seen in the graph. Insignificant sites (accounting for less than 0.5% of total +spacetime) are omitted. +

+That alone can be useful, but often isn't enough. What if one of these +functions was called from several different places in the program? Which one +of these is responsible for most of the memory used? For +_nl_intern_locale_data(), this question is answered by clicking on +the 22.1% link, which takes us to the following part +of the file. + +


+

== 1 ===========================
+Context accounted for 22.1% of measured spacetime
+   0x401767D0: _nl_intern_locale_data (in /lib/i686/libc-2.3.2.so)
+

+Called from: +

+
+ +At this level, we can see all the places from which +_nl_load_locale_from_archive() was called such that it allocated +memory at 0x401767D0. (We can click on the top 22.1% +link to go back to the parent entry.) At this level, we have moved beyond the +information presented in the graph. In this case, it is only called from one +place. We can again follow the link for more detail, moving to the following +part of the file. + +
+

== 2 ===========================
+Context accounted for 22.1% of measured spacetime
+   0x401767D0: _nl_intern_locale_data (in /lib/i686/libc-2.3.2.so)
+   0x40176F95: _nl_load_locale_from_archive (in /lib/i686/libc-2.3.2.so)
+

+Called from: +

+
+ +In this way we can dig deeper into the call stack, to work out exactly what +sequence of calls led to some memory being allocated. At this point, with a +call depth of 3, the information runs out (thus the address of the child entry, +0x40176184, isn't a link). We could rerun the program with a greater +--depth value if we wanted more information. +

+Sometimes you will get a code location like this: +

+The code address isn't really 0xFFFFFFFF -- that's impossible. This is what +Massif does when it can't work out what the real code address is. +

+Massif produces this information in a plain text file by default, or HTML with +the --format=html option. The plain text version obviously +doesn't have the links, but a similar effect can be achieved by searching on +the code addresses. (In Vim, the '*' and '#' searches are ideal for this.) + + + +

7.7  Massif options

+ +Massif-specific options are: + + + + +

7.8  Accuracy

+The information should be pretty accurate. Some approximations made might +cause some allocation contexts to be attributed with less memory than they +actually allocated, but the amounts should be miniscule. +

+The heap admin spacetime figure is an approximation, as described above. If +anyone knows how to improve its accuracy, please let us know. + + + + diff --git a/massif/hp2ps/AreaBelow.c b/massif/hp2ps/AreaBelow.c new file mode 100644 index 0000000000..741380723e --- /dev/null +++ b/massif/hp2ps/AreaBelow.c @@ -0,0 +1,63 @@ +#include +#include "Main.h" +#include "Defines.h" +#include "Error.h" +#include "HpFile.h" +#include "Utilities.h" + +/* own stuff */ +#include "AreaBelow.h" + +extern void free(); + +/* + * Return the area enclosed by all of the curves. The algorithm + * used is the same as the trapizoidal rule for integration. + */ + +floatish +AreaBelow() +{ + intish i; + intish j; + intish bucket; + floatish value; + struct chunk *ch; + floatish area; + floatish trap; + floatish base; + floatish *maxima; + + maxima = (floatish *) xmalloc(nsamples * sizeof(floatish)); + for (i = 0; i < nsamples; i++) { + maxima[i] = 0.0; + } + + for (i = 0; i < nidents; i++) { + for (ch = identtable[i]->chk; ch; ch = ch->next) { + for (j = 0; j < ch->nd; j++) { + bucket = ch->d[j].bucket; + value = ch->d[j].value; + if (bucket >= nsamples) + Disaster("bucket out of range"); + maxima[ bucket ] += value; + } + } + } + + area = 0.0; + + for (i = 1; i < nsamples; i++) { + base = samplemap[i] - samplemap[i-1]; + if (maxima[i] > maxima[i-1]) { + trap = base * maxima[i-1] + ((base * (maxima[i] - maxima[i-1]))/ 2.0); + } else { + trap = base * maxima[i] + ((base * (maxima[i-1] - maxima[i]))/ 2.0); + } + + area += trap; + } + + free(maxima); + return area; +} diff --git a/massif/hp2ps/AreaBelow.h b/massif/hp2ps/AreaBelow.h new file mode 100644 index 0000000000..d7f713f2b4 --- /dev/null +++ b/massif/hp2ps/AreaBelow.h @@ -0,0 +1,6 @@ +#ifndef AREA_BELOW_H +#define AREA_BELOW_H + +floatish AreaBelow PROTO((void)); + +#endif /* AREA_BELOW_H */ diff --git a/massif/hp2ps/AuxFile.c b/massif/hp2ps/AuxFile.c new file mode 100644 index 0000000000..bbdc4aaed6 --- /dev/null +++ b/massif/hp2ps/AuxFile.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include "Main.h" +#include "Defines.h" +#include "Shade.h" +#include "Error.h" +#include "HpFile.h" +#include "Reorder.h" + +/* own stuff */ +#include "AuxFile.h" + +static void GetAuxLine PROTO((FILE *)); /* forward */ +static void GetAuxTok PROTO((FILE *)); /* forward */ + +void +GetAuxFile(auxfp) + FILE* auxfp; +{ + g_ch = ' '; + endfile = 0; + linenum = 1; + + GetAuxTok(auxfp); + + while (endfile == 0) { + GetAuxLine(auxfp); + } + + fclose(auxfp); +} + + + +/* + * Read the next line from the aux file, check the syntax, and + * perform the appropriate action. + */ + +static void +GetAuxLine(auxfp) + FILE* auxfp; +{ + switch (thetok) { + case X_RANGE_TOK: + GetAuxTok(auxfp); + if (thetok != FLOAT_TOK) { + Error("%s, line %d, floating point number must follow X_RANGE", + auxfile, linenum); + } + auxxrange = thefloatish; + GetAuxTok(auxfp); + break; + case Y_RANGE_TOK: + GetAuxTok(auxfp); + if (thetok != FLOAT_TOK) { + Error("%s, line %d, floating point number must follow Y_RANGE", + auxfile, linenum); + } + auxyrange = thefloatish; + GetAuxTok(auxfp); + break; + case ORDER_TOK: + GetAuxTok(auxfp); + if (thetok != IDENTIFIER_TOK) { + Error("%s, line %d: identifier must follow ORDER", + auxfile, linenum); + } + GetAuxTok(auxfp); + if (thetok != INTEGER_TOK) { + Error("%s, line %d: identifier and integer must follow ORDER", + auxfile, linenum); + } + OrderFor(theident, theinteger); + GetAuxTok(auxfp); + break; + case SHADE_TOK: + GetAuxTok(auxfp); + if (thetok != IDENTIFIER_TOK) { + Error("%s, line %d: identifier must follow SHADE", + auxfile, linenum); + } + GetAuxTok(auxfp); + if (thetok != FLOAT_TOK) { + Error("%s, line %d: identifier and floating point number must follow SHADE", + auxfile, linenum); + } + ShadeFor(theident, thefloatish); + GetAuxTok(auxfp); + break; + case EOF_TOK: + endfile = 1; + break; + default: + Error("%s, line %d: %s unexpected", auxfile, linenum, + TokenToString(thetok)); + break; + } +} + + + +/* + * Read the next token from the input and assign its value + * to the global variable "thetok". In the case of numbers, + * the corresponding value is also assigned to "thefloatish"; + * in the case of identifiers it is assigned to "theident". + */ + +static void GetAuxTok(auxfp) +FILE* auxfp; +{ + + while (isspace(g_ch)) { /* skip whitespace */ + if (g_ch == '\n') linenum++; + g_ch = getc(auxfp); + } + + if (g_ch == EOF) { + thetok = EOF_TOK; + return; + } + + if (isdigit(g_ch)) { + thetok = GetNumber(auxfp); + return; + } else if (IsIdChar(g_ch)) { /* g_ch can't be a digit here */ + GetIdent(auxfp); + if (!isupper(theident[0])) { + thetok = IDENTIFIER_TOK; + } else if (strcmp(theident, "X_RANGE") == 0) { + thetok = X_RANGE_TOK; + } else if (strcmp(theident, "Y_RANGE") == 0) { + thetok = Y_RANGE_TOK; + } else if (strcmp(theident, "ORDER") == 0) { + thetok = ORDER_TOK; + } else if (strcmp(theident, "SHADE") == 0) { + thetok = SHADE_TOK; + } else { + thetok = IDENTIFIER_TOK; + } + return; + } else { + Error("%s, line %d: strange character (%c)", auxfile, linenum, g_ch); + } +} + +void +PutAuxFile(auxfp) + FILE* auxfp; +{ + int i; + + fprintf(auxfp, "X_RANGE %.2f\n", xrange); + fprintf(auxfp, "Y_RANGE %.2f\n", yrange); + + for (i = 0; i < nidents; i++) { + fprintf(auxfp, "ORDER %s %d\n", identtable[i]->name, i+1); + } + + for (i = 0; i < nidents; i++) { + fprintf(auxfp, "SHADE %s %.2f\n", identtable[i]->name, + ShadeOf(identtable[i]->name)); + } + + fclose(auxfp); +} diff --git a/massif/hp2ps/AuxFile.h b/massif/hp2ps/AuxFile.h new file mode 100644 index 0000000000..6e962c492e --- /dev/null +++ b/massif/hp2ps/AuxFile.h @@ -0,0 +1,7 @@ +#ifndef AUX_FILE_H +#define AUX_FILE_H + +void PutAuxFile PROTO((FILE *)); +void GetAuxFile PROTO((FILE *)); + +#endif /* AUX_FILE_H */ diff --git a/massif/hp2ps/Axes.c b/massif/hp2ps/Axes.c new file mode 100644 index 0000000000..0733edd2e8 --- /dev/null +++ b/massif/hp2ps/Axes.c @@ -0,0 +1,241 @@ +#include +#include +#include "Main.h" +#include "Curves.h" +#include "Defines.h" +#include "Dimensions.h" +#include "HpFile.h" +#include "Utilities.h" + +/* own stuff */ +#include "Axes.h" + +typedef enum {MEGABYTE, KILOBYTE, BYTE} mkb; + +static void XAxis PROTO((void)); /* forward */ +static void YAxis PROTO((void)); /* forward */ + +static void XAxisMark PROTO((floatish, floatish)); /* forward */ +static void YAxisMark PROTO((floatish, floatish, mkb)); /* forward */ + +static floatish Round PROTO((floatish)); /* forward */ + +void +Axes() +{ + XAxis(); + YAxis(); +} + +static void +XAxisMark(x, num) + floatish x; floatish num; +{ + /* calibration mark */ + fprintf(psfp, "%f %f moveto\n", xpage(x), ypage(0.0)); + fprintf(psfp, "0 -4 rlineto\n"); + fprintf(psfp, "stroke\n"); + + /* number */ + fprintf(psfp, "HE%d setfont\n", NORMAL_FONT); + fprintf(psfp, "(%.1f)\n", num); + fprintf(psfp, "dup stringwidth pop\n"); + fprintf(psfp, "2 div\n"); + fprintf(psfp, "%f exch sub\n", xpage(x)); + fprintf(psfp, "%f moveto\n", borderspace); + fprintf(psfp, "show\n"); +} + + +#define N_X_MARKS 7 +#define XFUDGE 15 + +extern floatish xrange; +extern char *sampleunitstring; + +static void +XAxis() +{ + floatish increment, i; + floatish t, x; + floatish legendlen; + + /* draw the x axis line */ + fprintf(psfp, "%f %f moveto\n", xpage(0.0), ypage(0.0)); + fprintf(psfp, "%f 0 rlineto\n", graphwidth); + fprintf(psfp, "%f setlinewidth\n", borderthick); + fprintf(psfp, "stroke\n"); + + /* draw x axis legend */ + fprintf(psfp, "HE%d setfont\n", NORMAL_FONT); + fprintf(psfp, "(%s)\n", sampleunitstring); + fprintf(psfp, "dup stringwidth pop\n"); + fprintf(psfp, "%f\n", xpage(0.0) + graphwidth); + fprintf(psfp, "exch sub\n"); + fprintf(psfp, "%f moveto\n", borderspace); + fprintf(psfp, "show\n"); + + + /* draw x axis scaling */ + + increment = Round(xrange / (floatish) N_X_MARKS); + + t = graphwidth / xrange; + legendlen = StringSize(sampleunitstring) + (floatish) XFUDGE; + + for (i = samplemap[0]; i < samplemap[nsamples - 1]; i += increment) { + x = (i - samplemap[0]) * t; + + if (x < (graphwidth - legendlen)) { + XAxisMark(x,i); + } + } +} + +static void +YAxisMark(y, num, unit) + floatish y; floatish num; mkb unit; +{ + /* calibration mark */ + fprintf(psfp, "%f %f moveto\n", xpage(0.0), ypage(y)); + fprintf(psfp, "-4 0 rlineto\n"); + fprintf(psfp, "stroke\n"); + + /* number */ + fprintf(psfp, "HE%d setfont\n", NORMAL_FONT); + + switch (unit) { + case MEGABYTE : + fprintf(psfp, "("); + CommaPrint(psfp, (intish) (num / 1e6 + 0.5)); + fprintf(psfp, "M)\n"); + break; + case KILOBYTE : + fprintf(psfp, "("); + CommaPrint(psfp, (intish) (num / 1e3 + 0.5)); + fprintf(psfp, "k)\n"); + break; + case BYTE: + fprintf(psfp, "("); + CommaPrint(psfp, (intish) (num + 0.5)); + fprintf(psfp, ")\n"); + break; + } + + fprintf(psfp, "dup stringwidth\n"); + fprintf(psfp, "2 div\n"); + fprintf(psfp, "%f exch sub\n", ypage(y)); + + fprintf(psfp, "exch\n"); + fprintf(psfp, "%f exch sub\n", graphx0 - borderspace); + + fprintf(psfp, "exch\n"); + fprintf(psfp, "moveto\n"); + fprintf(psfp, "show\n"); +} + +#define N_Y_MARKS 7 +#define YFUDGE 15 + +extern floatish yrange; +extern char *valueunitstring; + +static void +YAxis() +{ + floatish increment, i; + floatish t, y; + floatish legendlen; + mkb unit; + + /* draw the y axis line */ + fprintf(psfp, "%f %f moveto\n", xpage(0.0), ypage(0.0)); + fprintf(psfp, "0 %f rlineto\n", graphheight); + fprintf(psfp, "%f setlinewidth\n", borderthick); + fprintf(psfp, "stroke\n"); + + /* draw y axis legend */ + fprintf(psfp, "gsave\n"); + fprintf(psfp, "HE%d setfont\n", NORMAL_FONT); + fprintf(psfp, "(%s)\n", valueunitstring); + fprintf(psfp, "dup stringwidth pop\n"); + fprintf(psfp, "%f\n", ypage(0.0) + graphheight); + fprintf(psfp, "exch sub\n"); + fprintf(psfp, "%f exch\n", xpage(0.0) - borderspace); + fprintf(psfp, "translate\n"); + fprintf(psfp, "90 rotate\n"); + fprintf(psfp, "0 0 moveto\n"); + fprintf(psfp, "show\n"); + fprintf(psfp, "grestore\n"); + + /* draw y axis scaling */ + increment = max( yrange / (floatish) N_Y_MARKS, 1.0); + increment = Round(increment); + + if (increment >= 1e6) { + unit = MEGABYTE; + } else if (increment >= 1e3) { + unit = KILOBYTE; + } else { + unit = BYTE; + } + + t = graphheight / yrange; + legendlen = StringSize(valueunitstring) + (floatish) YFUDGE; + + for (i = 0.0; i <= yrange; i += increment) { + y = i * t; + + if (y < (graphheight - legendlen)) { + YAxisMark(y, i, unit); + } + } +} + + +/* + * Find a "nice round" value to use on the axis. + */ + +static floatish OneTwoFive PROTO((floatish)); /* forward */ + +static floatish +Round(y) + floatish y; +{ + int i; + + if (y > 10.0) { + for (i = 0; y > 10.0; y /= 10.0, i++) ; + y = OneTwoFive(y); + for ( ; i > 0; y = y * 10.0, i--) ; + + } else if (y < 1.0) { + for (i = 0; y < 1.0; y *= 10.0, i++) ; + y = OneTwoFive(y); + for ( ; i > 0; y = y / 10.0, i--) ; + + } else { + y = OneTwoFive(y); + } + + return (y); +} + + +/* + * OneTwoFive() -- Runciman's 1,2,5 scaling rule. Argument 1.0 <= y <= 10.0. + */ + +static floatish +OneTwoFive(y) + floatish y; +{ + if (y > 4.0) { + return (5.0); + } else if (y > 1.0) { + return (2.0); + } else { + return (1.0); + } +} diff --git a/massif/hp2ps/Axes.h b/massif/hp2ps/Axes.h new file mode 100644 index 0000000000..e4be505dfb --- /dev/null +++ b/massif/hp2ps/Axes.h @@ -0,0 +1,6 @@ +#ifndef AXES_H +#define AXES_H + +void Axes PROTO((void)); + +#endif /* AXES_H */ diff --git a/massif/hp2ps/CHANGES b/massif/hp2ps/CHANGES new file mode 100644 index 0000000000..60933d6622 --- /dev/null +++ b/massif/hp2ps/CHANGES @@ -0,0 +1,39 @@ +1. + +When generating PostScript to show strings, '(' and ')' may need to be escaped. +These characters are now escaped when the JOB string is shown. + +2. + +Manually deleting samples from a .hp file now does what you would expect. + +3. + +The -t flag for setting the threshold percentage has been scrapped. No one +ever used it. + +4. + +Long JOB strings cause hp2ps to use a big title box. Big and small boxes +can be forced with -b and -s flag. + +5. + +MARKS now print as small triangles which remain below the x axis. + +6. + +There is an updated manual page. + +7. + +-m flag for setting maximum no of bands (default 20, cant be more than 20). +-t flag for setting threshold (between 0% and 5%, default 1%). + +8. + +Axes scaling rounding errors removed. + +9. + +Fixed bug whereby x-axis was assumed to start at zero when placing MARKs. diff --git a/massif/hp2ps/Curves.c b/massif/hp2ps/Curves.c new file mode 100644 index 0000000000..68dc417581 --- /dev/null +++ b/massif/hp2ps/Curves.c @@ -0,0 +1,164 @@ +#include +#include +#include "Main.h" +#include "Defines.h" +#include "Dimensions.h" +#include "HpFile.h" +#include "Shade.h" +#include "Utilities.h" + +/* own stuff */ +#include "Curves.h" + +static floatish *g_x; /* x and y values */ +static floatish *g_y; + +static floatish *g_py; /* previous y values */ + +static void Curve PROTO((struct entry *)); /* forward */ +static void ShadeCurve(); /* forward */ + +void +Curves() +{ + intish i; + + for (i = 0; i < nidents; i++) { + Curve(identtable[i]); + } +} + +/* + * Draw a curve, and fill the area that is below it and above + * the previous curve. + */ + +static void +Curve(e) + struct entry* e; +{ + struct chunk* ch; + int j; + + for (ch = e->chk; ch; ch = ch->next) { + for (j = 0; j < ch->nd; j++) { + g_y[ ch->d[j].bucket ] += ch->d[j].value; + } + } + + ShadeCurve(g_x, g_y, g_py, ShadeOf(e->name)); +} + + +static void PlotCurveLeftToRight PROTO((floatish *, floatish *)); /* forward */ +static void PlotCurveRightToLeft PROTO((floatish *, floatish *)); /* forward */ + +static void SaveCurve PROTO((floatish *, floatish *)); /* forward */ + +/* + * Map virtual x coord to physical x coord + */ + +floatish +xpage(x) + floatish x; +{ + return (x + graphx0); +} + + + +/* + * Map virtual y coord to physical y coord + */ + +floatish +ypage(y) + floatish y; +{ + return (y + graphy0); +} + + +/* + * Fill the region bounded by two splines, using the given + * shade. + */ + +static void +ShadeCurve(x, y, py, shade) + floatish *x; floatish *y; floatish *py; floatish shade; +{ + fprintf(psfp, "%f %f moveto\n", xpage(x[0]), ypage(py[0])); + PlotCurveLeftToRight(x, py); + + fprintf(psfp, "%f %f lineto\n", xpage(x[nsamples - 1]), + ypage(y[nsamples - 1])); + PlotCurveRightToLeft(x, y); + + fprintf(psfp, "closepath\n"); + + fprintf(psfp, "gsave\n"); + + SetPSColour(shade); + fprintf(psfp, "fill\n"); + + fprintf(psfp, "grestore\n"); + fprintf(psfp, "stroke\n"); + + SaveCurve(y, py); +} + +static void +PlotCurveLeftToRight(x,y) + floatish *x; floatish *y; +{ + intish i; + + for (i = 0; i < nsamples; i++) { + fprintf(psfp, "%f %f lineto\n", xpage(x[i]), ypage(y[i])); + } +} + +static void +PlotCurveRightToLeft(x,y) + floatish *x; floatish *y; +{ + intish i; + + for (i = nsamples - 1; i >= 0; i-- ) { + fprintf(psfp, "%f %f lineto\n", xpage(x[i]), ypage(y[i])); + } +} + +/* + * Save the curve coordinates stored in y[] in py[]. + */ + +static void +SaveCurve(y, py) + floatish *y; floatish* py; +{ + intish i; + + for (i = 0; i < nsamples; i++) { + py[i] = y[i]; + } +} + +extern floatish xrange; + +void +CurvesInit() +{ + intish i; + + g_x = (floatish*) xmalloc(nsamples * sizeof(floatish)); + g_y = (floatish*) xmalloc(nsamples * sizeof(floatish)); + g_py = (floatish*) xmalloc(nsamples * sizeof(floatish)); + + for (i = 0; i < nsamples; i++) { + g_x[i] = ((samplemap[i] - samplemap[0])/ xrange) * graphwidth; + g_y[i] = g_py[i] = 0.0; + } +} diff --git a/massif/hp2ps/Curves.h b/massif/hp2ps/Curves.h new file mode 100644 index 0000000000..0aa397f42c --- /dev/null +++ b/massif/hp2ps/Curves.h @@ -0,0 +1,10 @@ +#ifndef CURVES_H +#define CURVES_H + +void Curves PROTO((void)); +void CurvesInit PROTO((void)); + +floatish xpage PROTO((floatish)); +floatish ypage PROTO((floatish)); + +#endif /* CURVES_H */ diff --git a/massif/hp2ps/Defines.h b/massif/hp2ps/Defines.h new file mode 100644 index 0000000000..8d38546fec --- /dev/null +++ b/massif/hp2ps/Defines.h @@ -0,0 +1,61 @@ +#ifndef DEFINES_H +#define DEFINES_H + +/* + * Things that can be altered. + */ + +#define THRESHOLD_PERCENT _thresh_ /* all values below 1% insignificant */ +#define DEFAULT_THRESHOLD 1.0 +extern floatish _thresh_; + +#define TWENTY _twenty_ /* show top 20 bands, grouping excess */ +#define DEFAULT_TWENTY 20 /* this is default and absolute maximum */ +extern int _twenty_; + +#define LARGE_FONT 12 /* Helvetica 12pt */ +#define NORMAL_FONT 10 /* Helvetica 10pt */ + +#define BORDER_HEIGHT 432.0 /* page border box 432pt (6 inches high) */ +#define BORDER_WIDTH 648.0 /* page border box 648pt (9 inches wide) */ +#define BORDER_SPACE 5.0 /* page border space */ +#define BORDER_THICK 0.5 /* page border line thickness 0.5pt */ + + +#define TITLE_HEIGHT 20.0 /* title box is 20pt high */ +#define TITLE_TEXT_FONT LARGE_FONT /* title in large font */ +#define TITLE_TEXT_SPACE 6.0 /* space between title text and box */ + + +#define AXIS_THICK 0.5 /* axis thickness 0.5pt */ +#define AXIS_TEXT_SPACE 6 /* space between axis legends and axis */ +#define AXIS_TEXT_FONT NORMAL_FONT /* axis legends in normal font */ +#define AXIS_Y_TEXT_SPACE 35 /* space for y axis text */ + +#define KEY_BOX_WIDTH 14 /* key boxes are 14pt high */ + +#define SMALL_JOB_STRING_WIDTH 35 /* small title for 35 characters or less */ +#define BIG_JOB_STRING_WIDTH 80 /* big title for everything else */ + +#define GRAPH_X0 (AXIS_Y_TEXT_SPACE + (2 * BORDER_SPACE)) +#define GRAPH_Y0 (AXIS_TEXT_FONT + (2 * BORDER_SPACE)) + + +/* + * Things that should be left well alone. + */ + + + +#define START_X 72 /* start 72pt (1 inch) from left (portrait) */ +#define START_Y 108 /* start 108pt (1.5 inch) from bottom (portrait) */ + +#define NUMBER_LENGTH 32 + +#define N_CHUNK 24 + +#define VERSION "0.25" /* as of 95/03/21 */ + +#define max(x,y) ((x) > (y) ? (x) : (y)) /* not everyone has this */ + +#endif /* DEFINES_H */ diff --git a/massif/hp2ps/Deviation.c b/massif/hp2ps/Deviation.c new file mode 100644 index 0000000000..49e8d21d17 --- /dev/null +++ b/massif/hp2ps/Deviation.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include "Main.h" +#include "Defines.h" +#include "Error.h" +#include "HpFile.h" +#include "Utilities.h" + +extern void free(); + +/* own stuff */ +#include "Deviation.h" + +/* + * Reorder the identifiers in the identifier table so that the + * ones whose data points exhibit the mininal standard deviation + * come first. + */ + +void +Deviation() +{ + intish i; + intish j; + floatish dev; + struct chunk* ch; + int min; + floatish t; + struct entry* e; + floatish *averages; + floatish *deviations; + + averages = (floatish*) xmalloc(nidents * sizeof(floatish)); + deviations = (floatish*) xmalloc(nidents * sizeof(floatish)); + + /* find averages */ + + for (i = 0; i < nidents; i++) { + averages[i] = 0.0; + } + + for (i = 0; i < nidents; i++) { + for (ch = identtable[i]->chk; ch; ch = ch->next) { + for (j = 0; j < ch->nd; j++) { + averages[i] += ch->d[j].value; + } + } + } + + for (i = 0; i < nidents; i++) { + averages[i] /= (floatish) nsamples; + } + + /* calculate standard deviation */ + + for (i = 0; i < nidents; i++) { + deviations[i] = 0.0; + } + + for (i = 0; i < nidents; i++) { + for (ch = identtable[i]->chk; ch; ch = ch->next) { + for (j = 0; j < ch->nd; j++) { + dev = ch->d[j].value - averages[i]; + deviations[i] += dev * dev; + } + } + } + + for (i = 0; i < nidents; i++) { + deviations[i] = (floatish) sqrt ((doublish) (deviations[i] / + (floatish) (nsamples - 1))); + } + + + /* sort on basis of standard deviation */ + + for (i = 0; i < nidents-1; i++) { + min = i; + for (j = i+1; j < nidents; j++) { + if (deviations[ j ] < deviations[min]) { + min = j; + } + } + + t = deviations[min]; + deviations[min] = deviations[i]; + deviations[i] = t; + + e = identtable[min]; + identtable[min] = identtable[i]; + identtable[i] = e; + } + + free(averages); + free(deviations); +} + +void +Identorder(iflag) + int iflag; /* a funny three-way flag ? WDP 95/03 */ +{ + int i; + int j; + int min; + struct entry* e; + + /* sort on basis of ident string */ + if (iflag > 0) { + /* greatest at top i.e. smallest at start */ + + for (i = 0; i < nidents-1; i++) { + min = i; + for (j = i+1; j < nidents; j++) { + if (strcmp(identtable[j]->name, identtable[min]->name) < 0) { + min = j; + } + } + + e = identtable[min]; + identtable[min] = identtable[i]; + identtable[i] = e; + } + } else { + /* smallest at top i.e. greatest at start */ + + for (i = 0; i < nidents-1; i++) { + min = i; + for (j = i+1; j < nidents; j++) { + if (strcmp(identtable[j]->name, identtable[min]->name) > 0) { + min = j; + } + } + + e = identtable[min]; + identtable[min] = identtable[i]; + identtable[i] = e; + } + } +} diff --git a/massif/hp2ps/Deviation.h b/massif/hp2ps/Deviation.h new file mode 100644 index 0000000000..14e4df1ad0 --- /dev/null +++ b/massif/hp2ps/Deviation.h @@ -0,0 +1,7 @@ +#ifndef DEVIATION_H +#define DEVIATION_H + +void Deviation PROTO((void)); +void Identorder PROTO((int)); + +#endif /* DEVIATION_H */ diff --git a/massif/hp2ps/Dimensions.c b/massif/hp2ps/Dimensions.c new file mode 100644 index 0000000000..b0d1bd53e0 --- /dev/null +++ b/massif/hp2ps/Dimensions.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include "Main.h" +#include "Defines.h" +#include "HpFile.h" +#include "Scale.h" + +/* own stuff */ +#include "Dimensions.h" + +/* + * Get page and other dimensions before printing. + */ + +floatish borderheight = BORDER_HEIGHT; +floatish borderwidth = BORDER_WIDTH; +floatish borderspace = BORDER_SPACE; +floatish borderthick = BORDER_THICK; + +floatish titlewidth = (BORDER_WIDTH - (2 * BORDER_SPACE)); +floatish titletextspace = TITLE_TEXT_SPACE; +floatish titleheight; + +floatish graphx0 = GRAPH_X0; +floatish graphy0 = GRAPH_Y0; + +floatish graphheight; +floatish graphwidth; + +static floatish KeyWidth PROTO((void)); /* forward */ + +void +Dimensions() +{ + xrange = samplemap[nsamples - 1] - samplemap[0]; + xrange = max(xrange, auxxrange); + if (xrange == 0.0) xrange = 1.0; /* avoid division by 0.0 */ + + yrange = MaxCombinedHeight(); + yrange = max(yrange, auxyrange); + if (yrange == 0.0) yrange = 1.0; /* avoid division by 0.0 */ + + if (!bflag && !sflag) { + bflag = strlen(jobstring) > SMALL_JOB_STRING_WIDTH; + } + + if (bflag) { + titleheight = 2 * TITLE_HEIGHT; + } else { + titleheight = TITLE_HEIGHT; + } + + graphwidth = titlewidth - graphx0 - (TWENTY ? KeyWidth() : 0); + graphheight = borderheight - titleheight - (2 * borderspace) - graphy0; +} + +/* + * Calculate the width of the key. + */ + +static floatish +KeyWidth() +{ + intish i; + floatish c; + + c = 0.0; + + for (i = 0; i < nidents; i++) { + c = max(c, StringSize(identtable[i]->name)); + } + + c += 3.0 * borderspace; + + c += (floatish) KEY_BOX_WIDTH; + + return c; +} + + +/* + * A desperately grim solution. + */ + + +floatish fonttab[] = { + /* 20 (' ') = */ 3.0, + /* 21 ('!') = */ 1.0, + /* 22 ('"') = */ 1.0, + /* 23 ('#') = */ 3.0, + /* 24 ('$') = */ 3.0, + /* 25 ('%') = */ 3.0, + /* 26 ('&') = */ 3.0, + /* 27 (''') = */ 1.0, + /* 28 ('(') = */ 3.0, + /* 29 (')') = */ 3.0, + /* 2a ('*') = */ 2.0, + /* 2b ('+') = */ 3.0, + /* 2c (',') = */ 1.0, + /* 2d ('-') = */ 3.0, + /* 2e ('.') = */ 1.0, + /* 2f ('/') = */ 3.0, + /* 30 ('0') = */ 4.0, + /* 31 ('1') = */ 4.0, + /* 32 ('2') = */ 4.0, + /* 33 ('3') = */ 4.0, + /* 34 ('4') = */ 4.0, + /* 35 ('5') = */ 4.0, + /* 36 ('6') = */ 4.0, + /* 37 ('7') = */ 4.0, + /* 38 ('8') = */ 4.0, + /* 39 ('9') = */ 4.0, + /* 3a (':') = */ 1.0, + /* 3b (';') = */ 1.0, + /* 3c ('<') = */ 3.0, + /* 3d ('=') = */ 3.0, + /* 3e ('>') = */ 3.0, + /* 3f ('?') = */ 2.0, + /* 40 ('@') = */ 3.0, + /* 41 ('A') = */ 5.0, + /* 42 ('B') = */ 5.0, + /* 43 ('C') = */ 5.0, + /* 44 ('D') = */ 5.0, + /* 45 ('E') = */ 5.0, + /* 46 ('F') = */ 5.0, + /* 47 ('G') = */ 5.0, + /* 48 ('H') = */ 5.0, + /* 49 ('I') = */ 1.0, + /* 4a ('J') = */ 5.0, + /* 4b ('K') = */ 5.0, + /* 4c ('L') = */ 5.0, + /* 4d ('M') = */ 5.0, + /* 4e ('N') = */ 5.0, + /* 4f ('O') = */ 5.0, + /* 50 ('P') = */ 5.0, + /* 51 ('Q') = */ 5.0, + /* 52 ('R') = */ 5.0, + /* 53 ('S') = */ 5.0, + /* 54 ('T') = */ 5.0, + /* 55 ('U') = */ 5.0, + /* 56 ('V') = */ 5.0, + /* 57 ('W') = */ 5.0, + /* 58 ('X') = */ 5.0, + /* 59 ('Y') = */ 5.0, + /* 5a ('Z') = */ 5.0, + /* 5b ('[') = */ 2.0, + /* 5c ('\') = */ 3.0, + /* 5d (']') = */ 2.0, + /* 5e ('^') = */ 1.0, + /* 5f ('_') = */ 3.0, + /* 60 ('`') = */ 1.0, + /* 61 ('a') = */ 3.0, + /* 62 ('b') = */ 3.0, + /* 63 ('c') = */ 3.0, + /* 64 ('d') = */ 3.0, + /* 65 ('e') = */ 3.0, + /* 66 ('f') = */ 3.0, + /* 67 ('g') = */ 3.0, + /* 68 ('h') = */ 3.0, + /* 69 ('i') = */ 1.0, + /* 6a ('j') = */ 2.0, + /* 6b ('k') = */ 3.0, + /* 6c ('l') = */ 1.0, + /* 6d ('m') = */ 5.0, + /* 6e ('n') = */ 3.0, + /* 6f ('o') = */ 3.0, + /* 70 ('p') = */ 3.0, + /* 71 ('q') = */ 3.0, + /* 72 ('r') = */ 2.0, + /* 73 ('s') = */ 3.0, + /* 74 ('t') = */ 2.0, + /* 75 ('u') = */ 3.0, + /* 76 ('v') = */ 3.0, + /* 77 ('w') = */ 3.0, + /* 78 ('x') = */ 3.0, + /* 79 ('y') = */ 3.0, + /* 7a ('z') = */ 3.0, + /* 7b ('{') = */ 2.0, + /* 7c ('|') = */ 1.0, + /* 7d ('}') = */ 2.0, + /* 7e ('~') = */ 2.0 +}; + + +/* + * What size is a string (in points)? + */ + +#define FUDGE (2.834646 * 0.6) + +floatish +StringSize(s) + char* s; +{ + floatish r; + + for (r = 0.0; *s; s++) { + r += fonttab[(*s) - 0x20]; + } + + return r * FUDGE; +} diff --git a/massif/hp2ps/Dimensions.h b/massif/hp2ps/Dimensions.h new file mode 100644 index 0000000000..7bcc05beee --- /dev/null +++ b/massif/hp2ps/Dimensions.h @@ -0,0 +1,22 @@ +#ifndef DIMENSIONS_H +#define DIMENSIONS_H + +extern floatish borderheight; +extern floatish borderwidth; +extern floatish borderspace; +extern floatish borderthick; + +extern floatish titleheight; +extern floatish titlewidth; +extern floatish titletextspace; + +extern floatish graphx0; +extern floatish graphy0; + +extern floatish graphheight; +extern floatish graphwidth; + +void Dimensions PROTO((void)); +floatish StringSize PROTO((char *)); + +#endif /* DIMENSIONS_H */ diff --git a/massif/hp2ps/Error.c b/massif/hp2ps/Error.c new file mode 100644 index 0000000000..784420419b --- /dev/null +++ b/massif/hp2ps/Error.c @@ -0,0 +1,55 @@ +#include +#include "Main.h" +#include "Defines.h" + +/* own stuff */ +#include "Error.h" + +void exit PROTO((int)); + +/*VARARGS0*/ +void +Error(a1,a2,a3,a4) + char* a1; char* a2; char* a3; char* a4; +{ + fflush(stdout); + fprintf(stderr, "%s: ", programname); + fprintf(stderr, a1, a2, a3, a4); + fprintf(stderr, "\n"); + exit(1); +} + +/*VARARGS0*/ +void +Disaster(a1,a2,a3,a4) + char* a1; char* a2; char* a3; char* a4; +{ + fflush(stdout); + fprintf(stderr, "%s: ", programname); + fprintf(stderr, " Disaster! ("); + fprintf(stderr, a1, a2, a3, a4); + fprintf(stderr, ")\n"); + exit(1); +} + +void +Usage(str) + char *str; +{ + if (str) printf("error: %s\n", str); + printf("usage: %s -b -d -ef -g -i -p -mn -p -s -tf -y [file[.hp]]\n", programname); + printf("where -b use large title box\n"); + printf(" -d sort by standard deviation\n"); + printf(" -ef[in|mm|pt] produce Encapsulated PostScript f units wide (f > 2 inches)\n"); + printf(" -g produce output suitable for GHOSTSCRIPT previever\n"); + printf(" -i[+|-] sort by identifier string (-i+ gives greatest on top) \n"); + printf(" -mn print maximum of n bands (default & max 20)\n"); + printf(" -m0 removes the band limit altogether\n"); + printf(" -p use previous scaling, shading and ordering\n"); + printf(" -s use small title box\n"); + printf(" -tf ignore trace bands which sum below f%% (default 1%%, max 5%%)\n"); + printf(" -y traditional\n"); + printf(" -c colour ouput\n"); + exit(0); +} + diff --git a/massif/hp2ps/Error.h b/massif/hp2ps/Error.h new file mode 100644 index 0000000000..0febc84ab0 --- /dev/null +++ b/massif/hp2ps/Error.h @@ -0,0 +1,8 @@ +#ifndef ERROR_H +#define ERROR_H + +extern void Error (); /*PROTO((char *, ...)); */ +extern void Disaster (); /* PROTO((char *, ...)); */ +extern void Usage (); /* PROTO((char *)); */ + +#endif /* ERROR_H */ diff --git a/massif/hp2ps/HpFile.c b/massif/hp2ps/HpFile.c new file mode 100644 index 0000000000..071016fae7 --- /dev/null +++ b/massif/hp2ps/HpFile.c @@ -0,0 +1,587 @@ +#include +#include +#include +#include +#include "Main.h" +#include "Defines.h" +#include "Error.h" +#include "HpFile.h" +#include "Utilities.h" + +#ifndef atof +double atof PROTO((const char *)); +#endif + +/* own stuff already included */ + +#define N_MARKS 50 /* start size of the mark table */ +#define N_SAMPLES 500 /* start size of the sample table */ + +char *theident; +char *thestring; +int theinteger; +floatish thefloatish; +int g_ch; /* last character read */ +token thetok; /* last token */ +int linenum; /* current line number */ +int endfile; /* true at end of file */ + +static boolish gotjob = 0; /* "JOB" read */ +static boolish gotdate = 0; /* "DATE" read */ +static boolish gotvalueunit = 0; /* "VALUE_UNIT" read */ +static boolish gotsampleunit = 0; /* "SAMPLE_UNIT" read */ +static boolish insample = 0; /* true when in sample */ + +static floatish lastsample; /* the last sample time */ + +static void GetHpLine PROTO((FILE *)); /* forward */ +static void GetHpTok PROTO((FILE *)); /* forward */ + +static struct entry *GetEntry PROTO((char *)); /* forward */ + +static void MakeIdentTable PROTO((void)); /* forward */ + +char *jobstring; +char *datestring; + +char *sampleunitstring; +char *valueunitstring; + +floatish *samplemap; /* sample intervals */ +floatish *markmap; /* sample marks */ + +/* + * An extremely simple parser. The input is organised into lines of + * the form + * + * JOB s -- job identifier string + * DATE s -- date string + * SAMPLE_UNIT s -- sample unit eg "seconds" + * VALUE_UNIT s -- value unit eg "bytes" + * MARK i -- sample mark + * BEGIN_SAMPLE i -- start of ith sample + * identifier i -- there are i identifiers in this sample + * END_SAMPLE i -- end of ith sample + * + */ + +void +GetHpFile(infp) + FILE *infp; +{ + nsamples = 0; + nmarks = 0; + nidents = 0; + + g_ch = ' '; + endfile = 0; + linenum = 1; + lastsample = 0.0; + + GetHpTok(infp); + + while (endfile == 0) { + GetHpLine(infp); + } + + if (!gotjob) { + Error("%s: JOB missing", hpfile); + } + + if (!gotdate) { + Error("%s: DATE missing", hpfile); + } + + if (!gotvalueunit) { + Error("%s: VALUE_UNIT missing", hpfile); + } + + if (!gotsampleunit) { + Error("%s: SAMPLE_UNIT missing", hpfile); + } + + if (nsamples == 0) { + Error("%s: contains no samples", hpfile); + } + + + MakeIdentTable(); + + fclose(hpfp); +} + + +/* + * Read the next line from the input, check the syntax, and perform + * the appropriate action. + */ + +static void +GetHpLine(infp) + FILE* infp; +{ + static intish nmarkmax = 0, nsamplemax = 0; + + switch (thetok) { + case JOB_TOK: + GetHpTok(infp); + if (thetok != STRING_TOK) { + Error("%s, line %d: string must follow JOB", hpfile, linenum); + } + jobstring = thestring; + gotjob = 1; + GetHpTok(infp); + break; + + case DATE_TOK: + GetHpTok(infp); + if (thetok != STRING_TOK) { + Error("%s, line %d: string must follow DATE", hpfile, linenum); + } + datestring = thestring; + gotdate = 1; + GetHpTok(infp); + break; + + case SAMPLE_UNIT_TOK: + GetHpTok(infp); + if (thetok != STRING_TOK) { + Error("%s, line %d: string must follow SAMPLE_UNIT", hpfile, + linenum); + } + sampleunitstring = thestring; + gotsampleunit = 1; + GetHpTok(infp); + break; + + case VALUE_UNIT_TOK: + GetHpTok(infp); + if (thetok != STRING_TOK) { + Error("%s, line %d: string must follow VALUE_UNIT", hpfile, + linenum); + } + valueunitstring = thestring; + gotvalueunit = 1; + GetHpTok(infp); + break; + + case MARK_TOK: + GetHpTok(infp); + if (thetok != FLOAT_TOK) { + Error("%s, line %d, floating point number must follow MARK", + hpfile, linenum); + } + if (insample) { + Error("%s, line %d, MARK occurs within sample", hpfile, linenum); + } + if (nmarks >= nmarkmax) { + if (!markmap) { + nmarkmax = N_MARKS; + markmap = (floatish*) xmalloc(nmarkmax * sizeof(floatish)); + } else { + nmarkmax *= 2; + markmap = (floatish*) xrealloc(markmap, nmarkmax * sizeof(floatish)); + } + } + markmap[ nmarks++ ] = thefloatish; + GetHpTok(infp); + break; + + case BEGIN_SAMPLE_TOK: + insample = 1; + GetHpTok(infp); + if (thetok != FLOAT_TOK) { + Error("%s, line %d, floating point number must follow BEGIN_SAMPLE", hpfile, linenum); + } + if (thefloatish < lastsample) { + Error("%s, line %d, samples out of sequence", hpfile, linenum); + } else { + lastsample = thefloatish; + } + if (nsamples >= nsamplemax) { + if (!samplemap) { + nsamplemax = N_SAMPLES; + samplemap = (floatish*) xmalloc(nsamplemax * sizeof(floatish)); + } else { + nsamplemax *= 2; + samplemap = (floatish*) xrealloc(samplemap, + nsamplemax * sizeof(floatish)); + } + } + samplemap[ nsamples ] = thefloatish; + GetHpTok(infp); + break; + + case END_SAMPLE_TOK: + insample = 0; + GetHpTok(infp); + if (thetok != FLOAT_TOK) { + Error("%s, line %d: floating point number must follow END_SAMPLE", + hpfile, linenum); + } + nsamples++; + GetHpTok(infp); + break; + + case IDENTIFIER_TOK: + GetHpTok(infp); + if (thetok != INTEGER_TOK) { + Error("%s, line %d: integer must follow identifier", hpfile, + linenum); + } + StoreSample(GetEntry(theident), nsamples, (floatish) theinteger); + GetHpTok(infp); + break; + + case EOF_TOK: + endfile = 1; + break; + + default: + Error("%s, line %d: %s unexpected", hpfile, linenum, + TokenToString(thetok)); + break; + } +} + + +char * +TokenToString(t) + token t; +{ + switch (t) { + case EOF_TOK: return "EOF"; + case INTEGER_TOK: return "integer"; + case FLOAT_TOK: return "floating point number"; + case IDENTIFIER_TOK: return "identifier"; + case STRING_TOK: return "string"; + case BEGIN_SAMPLE_TOK: return "BEGIN_SAMPLE"; + case END_SAMPLE_TOK: return "END_SAMPLE"; + case JOB_TOK: return "JOB"; + case DATE_TOK: return "DATE"; + case SAMPLE_UNIT_TOK: return "SAMPLE_UNIT"; + case VALUE_UNIT_TOK: return "VALUE_UNIT"; + case MARK_TOK: return "MARK"; + + case X_RANGE_TOK: return "X_RANGE"; + case Y_RANGE_TOK: return "Y_RANGE"; + case ORDER_TOK: return "ORDER"; + case SHADE_TOK: return "SHADE"; + default: return "(strange token)"; + } +} + +/* + * Read the next token from the input and assign its value + * to the global variable "thetok". In the case of numbers, + * the corresponding value is also assigned to "theinteger" + * or "thefloatish" as appropriate; in the case of identifiers + * it is assigned to "theident". + */ + +static void +GetHpTok(infp) + FILE* infp; +{ + + while (isspace(g_ch)) { /* skip whitespace */ + if (g_ch == '\n') linenum++; + g_ch = getc(infp); + } + + if (g_ch == EOF) { + thetok = EOF_TOK; + return; + } + + if (isdigit(g_ch)) { + thetok = GetNumber(infp); + return; + } else if (g_ch == '\"') { + GetString(infp); + thetok = STRING_TOK; + return; + } else if (IsIdChar(g_ch)) { + ASSERT(! (isdigit(g_ch))); /* g_ch can't be a digit here */ + GetIdent(infp); + if (!isupper(theident[0])) { + thetok = IDENTIFIER_TOK; + } else if (strcmp(theident, "BEGIN_SAMPLE") == 0) { + thetok = BEGIN_SAMPLE_TOK; + } else if (strcmp(theident, "END_SAMPLE") == 0) { + thetok = END_SAMPLE_TOK; + } else if (strcmp(theident, "JOB") == 0) { + thetok = JOB_TOK; + } else if (strcmp(theident, "DATE") == 0) { + thetok = DATE_TOK; + } else if (strcmp(theident, "SAMPLE_UNIT") == 0) { + thetok = SAMPLE_UNIT_TOK; + } else if (strcmp(theident, "VALUE_UNIT") == 0) { + thetok = VALUE_UNIT_TOK; + } else if (strcmp(theident, "MARK") == 0) { + thetok = MARK_TOK; + } else { + thetok = IDENTIFIER_TOK; + } + return; + } else { + Error("%s, line %d: strange character (%c)", hpfile, linenum, g_ch); + } +} + + +/* + * Read a sequence of digits and convert the result to an integer + * or floating point value (assigned to the "theinteger" or + * "thefloatish"). + */ + +static char numberstring[ NUMBER_LENGTH - 1 ]; + +token +GetNumber(infp) + FILE* infp; +{ + int i; + int containsdot; + + ASSERT(isdigit(ch)); /* we must have a digit to start with */ + + containsdot = 0; + + for (i = 0; i < NUMBER_LENGTH && (isdigit(g_ch) || g_ch == '.'); i++) { + numberstring[ i ] = g_ch; + containsdot |= (g_ch == '.'); + g_ch = getc(infp); + } + + ASSERT(i < NUMBER_LENGTH); /* did not overflow */ + + numberstring[ i ] = '\0'; + + if (containsdot) { + thefloatish = (floatish) atof(numberstring); + return FLOAT_TOK; + } else { + theinteger = atoi(numberstring); + return INTEGER_TOK; + } +} + +/* + * Read a sequence of identifier characters and assign the result + * to the string "theident". + */ + +void +GetIdent(infp) + FILE *infp; +{ + unsigned int i; + char idbuffer[5000]; + + for (i = 0; i < (sizeof idbuffer)-1 && IsIdChar(g_ch); i++) { + idbuffer[ i ] = g_ch; + g_ch = getc(infp); + } + + idbuffer[ i ] = '\0'; + + if (theident) + free(theident); + + theident = copystring(idbuffer); +} + + +/* + * Read a sequence of characters that make up a string and + * assign the result to "thestring". + */ + +void +GetString(infp) + FILE *infp; +{ + unsigned int i; + char stringbuffer[5000]; + + ASSERT(ch == '\"'); + + g_ch = getc(infp); /* skip the '\"' that begins the string */ + + for (i = 0; i < (sizeof stringbuffer)-1 && g_ch != '\"'; i++) { + stringbuffer[ i ] = g_ch; + g_ch = getc(infp); + } + + stringbuffer[i] = '\0'; + thestring = copystring(stringbuffer); + + ASSERT(g_ch == '\"'); + + g_ch = getc(infp); /* skip the '\"' that terminates the string */ +} + +boolish +IsIdChar(ch) + int ch; +{ + return (!isspace(ch)); +} + + +/* + * The information associated with each identifier is stored + * in a linked list of chunks. The table below allows the list + * of chunks to be retrieved given an identifier name. + */ + +#define N_HASH 513 + +static struct entry* hashtable[ N_HASH ]; + +static intish +Hash(s) + char *s; +{ + int r; + + for (r = 0; *s; s++) { + r = r + r + r + *s; + } + + if (r < 0) r = -r; + + return r % N_HASH; +} + +/* + * Get space for a new chunk. Initialise it, and return a pointer + * to the new chunk. + */ + +static struct chunk* +MakeChunk() +{ + struct chunk* ch; + struct datapoint* d; + + ch = (struct chunk*) xmalloc( sizeof(struct chunk) ); + + d = (struct datapoint*) xmalloc (sizeof(struct datapoint) * N_CHUNK); + + ch->nd = 0; + ch->d = d; + ch->next = 0; + return ch; +} + + +/* + * Get space for a new entry. Initialise it, and return a pointer + * to the new entry. + */ + +struct entry * +MakeEntry(name) + char *name; +{ + struct entry* e; + + e = (struct entry *) xmalloc(sizeof(struct entry)); + e->chk = MakeChunk(); + e->name = copystring(name); + return e; +} + +/* + * Get the entry associated with "name", creating a new entry if + * necessary. + */ + +static struct entry * +GetEntry(name) + char* name; +{ + intish h; + struct entry* e; + + h = Hash(name); + + for (e = hashtable[ h ]; e; e = e->next) { + if (strcmp(e->name, name) == 0) { + break; + } + } + + if (e) { + return (e); + } else { + nidents++; + e = MakeEntry(name); + e->next = hashtable[ h ]; + hashtable[ h ] = e; + return (e); + } +} + + +/* + * Store information from a sample. + */ + +void +StoreSample(en, bucket, value) + struct entry* en; intish bucket; floatish value; +{ + struct chunk* chk; + + for (chk = en->chk; chk->next != 0; chk = chk->next) + ; + + if (chk->nd < N_CHUNK) { + chk->d[ chk->nd ].bucket = bucket; + chk->d[ chk->nd ].value = value; + chk->nd += 1; + } else { + struct chunk* t; + t = chk->next = MakeChunk(); + t->d[ 0 ].bucket = bucket; + t->d[ 0 ].value = value; + t->nd += 1; + } +} + + +struct entry** identtable; + +/* + * The hash table is useful while reading the input, but it + * becomes a liability thereafter. The code below converts + * it to a more easily processed table. + */ + +static void +MakeIdentTable() +{ + intish i; + intish j; + struct entry* e; + + nidents = 0; + for (i = 0; i < N_HASH; i++) { + for (e = hashtable[ i ]; e; e = e->next) { + nidents++; + } + } + + identtable = (struct entry**) xmalloc(nidents * sizeof(struct entry*)); + j = 0; + + for (i = 0; i < N_HASH; i++) { + for (e = hashtable[ i ]; e; e = e->next, j++) { + identtable[ j ] = e; + } + } +} diff --git a/massif/hp2ps/HpFile.h b/massif/hp2ps/HpFile.h new file mode 100644 index 0000000000..571a584f87 --- /dev/null +++ b/massif/hp2ps/HpFile.h @@ -0,0 +1,77 @@ +#ifndef HP_FILE_H +#define HP_FILE_H + +typedef enum { + /* These tokens are found in ".hp" files */ + + EOF_TOK, + INTEGER_TOK, + FLOAT_TOK, + IDENTIFIER_TOK, + STRING_TOK, + BEGIN_SAMPLE_TOK, + END_SAMPLE_TOK, + JOB_TOK, + DATE_TOK, + SAMPLE_UNIT_TOK, + VALUE_UNIT_TOK, + MARK_TOK, + + /* These extra ones are found only in ".aux" files */ + + X_RANGE_TOK, + Y_RANGE_TOK, + ORDER_TOK, + SHADE_TOK +} token; + +struct datapoint { + int bucket; + floatish value; +}; + +struct chunk { + struct chunk *next; + short nd; /* 0 .. N_CHUNK - 1 */ + struct datapoint *d; +}; + + +struct entry { + struct entry *next; + struct chunk *chk; + char *name; +}; + +extern char *theident; +extern char *thestring; +extern int theinteger; +extern floatish thefloatish; +extern int g_ch; +extern token thetok; +extern int linenum; +extern int endfile; + +char *TokenToString PROTO((token)); + +extern struct entry** identtable; + +extern floatish *samplemap; +extern floatish *markmap; + +void GetHpFile PROTO((FILE *)); +void StoreSample PROTO((struct entry *, intish, floatish)); +struct entry *MakeEntry PROTO((char *)); + +token GetNumber PROTO((FILE *)); +void GetIdent PROTO((FILE *)); +void GetString PROTO((FILE *)); +boolish IsIdChar PROTO((int)); /* int is a "char" from getc */ + +extern char *jobstring; +extern char *datestring; + +extern char *sampleunitstring; +extern char *valueunitstring; + +#endif /* HP_FILE_H */ diff --git a/massif/hp2ps/INSTALL b/massif/hp2ps/INSTALL new file mode 100644 index 0000000000..92fe7b77dd --- /dev/null +++ b/massif/hp2ps/INSTALL @@ -0,0 +1,17 @@ +NOTE: these are instructions for installing hp2ps separately. If you only want +to use it with the Valgrind tool, Massif, it should be installed automatically. + +The original Makefile, for which the below instructions apply, is kept as +Makefile.old. + +----------------------------------------------------------------------------- +For binary distribution: + + - just copy "hp2ps" into an appropriate directory. + +For source distribution: + + - edit "Makefile" and set the $DSTBIN to the directory you wish to install + hp2ps in + + - do "make install" diff --git a/massif/hp2ps/Key.c b/massif/hp2ps/Key.c new file mode 100644 index 0000000000..a8a761dee0 --- /dev/null +++ b/massif/hp2ps/Key.c @@ -0,0 +1,63 @@ +#include +#include +#include "Main.h" +#include "Defines.h" +#include "Dimensions.h" +#include "HpFile.h" +#include "Shade.h" + +/* own stuff */ +#include "Key.h" + +static void KeyEntry PROTO((floatish, char *, floatish)); + +void Key() +{ + intish i; + floatish c; + floatish dc; + + for (i = 0; i < nidents; i++) /* count identifiers */ + ; + + c = graphy0; + dc = graphheight / (floatish) (i + 1); + + for (i = 0; i < nidents; i++) { + c += dc; + KeyEntry(c, identtable[i]->name, ShadeOf(identtable[i]->name)); + } +} + + + +static void +KeyEntry(centreline, name, colour) + floatish centreline; char* name; floatish colour; +{ + floatish namebase; + floatish keyboxbase; + floatish kstart; + + namebase = centreline - (floatish) (NORMAL_FONT / 2); + keyboxbase = centreline - ((floatish) KEY_BOX_WIDTH / 2.0); + + kstart = graphx0 + graphwidth; + + fprintf(psfp, "%f %f moveto\n", kstart + borderspace, keyboxbase); + fprintf(psfp, "0 %d rlineto\n", KEY_BOX_WIDTH); + fprintf(psfp, "%d 0 rlineto\n", KEY_BOX_WIDTH); + fprintf(psfp, "0 %d rlineto\n", -KEY_BOX_WIDTH); + fprintf(psfp, "closepath\n"); + + fprintf(psfp, "gsave\n"); + SetPSColour(colour); + fprintf(psfp, "fill\n"); + fprintf(psfp, "grestore\n"); + fprintf(psfp, "stroke\n"); + + fprintf(psfp, "HE%d setfont\n", NORMAL_FONT); + fprintf(psfp, "%f %f moveto\n", kstart + (floatish) KEY_BOX_WIDTH + 2 * borderspace, namebase); + + fprintf(psfp, "(%s) show\n", name); +} diff --git a/massif/hp2ps/Key.h b/massif/hp2ps/Key.h new file mode 100644 index 0000000000..d2a7b8eae3 --- /dev/null +++ b/massif/hp2ps/Key.h @@ -0,0 +1,6 @@ +#ifndef KEY_H +#define KEY_H + +void Key PROTO((void)); + +#endif /* KEY_H */ diff --git a/massif/hp2ps/LICENSE b/massif/hp2ps/LICENSE new file mode 100644 index 0000000000..b5059b71f6 --- /dev/null +++ b/massif/hp2ps/LICENSE @@ -0,0 +1,31 @@ +The Glasgow Haskell Compiler License + +Copyright 2002, The University Court of the University of Glasgow. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither name of the University nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF +GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/massif/hp2ps/Main.c b/massif/hp2ps/Main.c new file mode 100644 index 0000000000..e1cfe636df --- /dev/null +++ b/massif/hp2ps/Main.c @@ -0,0 +1,253 @@ +#include +#include +#include +#include "Main.h" +#include "Defines.h" +#include "AuxFile.h" +#include "AreaBelow.h" +#include "Dimensions.h" +#include "HpFile.h" +#include "PsFile.h" +#include "Reorder.h" +#include "Scale.h" +#include "TopTwenty.h" +#include "TraceElement.h" +#include "Deviation.h" +#include "Error.h" +#include "Utilities.h" + +boolish pflag = 0; /* read auxiliary file */ +boolish eflag = 0; /* scaled EPSF */ +boolish dflag = 0; /* sort by standard deviation */ +int iflag = 0; /* sort by identifier (3-way flag) */ +boolish gflag = 0; /* output suitable for previewer */ +boolish yflag = 0; /* ignore marks */ +boolish bflag = 0; /* use a big title box */ +boolish sflag = 0; /* use a small title box */ +int mflag = 0; /* max no. of bands displayed (default 20) */ +boolish tflag = 0; /* ignored threshold specified */ +boolish cflag = 0; /* colour output */ + +boolish filter; /* true when running as a filter */ + +static floatish WidthInPoints PROTO((char *)); /* forward */ +static FILE *Fp PROTO((char *, char **, char *, char *)); /* forward */ + +char *hpfile; +char *psfile; +char *auxfile; + +char *programname; + +static char *pathName; +static char *baseName; /* "basename" is a std C library name (sigh) */ + +FILE* hpfp; +FILE* psfp; +FILE* auxfp; + +floatish xrange = 0.0; +floatish yrange = 0.0; + +floatish auxxrange = 0.0; +floatish auxyrange = 0.0; + +floatish epsfwidth; +floatish areabelow; + +intish nsamples; +intish nmarks; +intish nidents; + +floatish THRESHOLD_PERCENT = DEFAULT_THRESHOLD; +int TWENTY = DEFAULT_TWENTY; + +int main(argc, argv) +int argc; +char* argv[]; +{ + + programname = copystring(Basename(argv[0])); + + argc--, argv++; + while (argc && argv[0][0] == '-') { + while (*++*argv) + switch(**argv) { + case 'p': + pflag++; + break; + case 'e': + eflag++; + epsfwidth = WidthInPoints(*argv + 1); + goto nextarg; + case 'd': + dflag++; + goto nextarg; + case 'i': + switch( *(*argv + 1) ) { + case '-': + iflag = -1; + case '+': + default: + iflag = 1; + } + goto nextarg; + case 'g': + gflag++; + goto nextarg; + case 'y': + yflag++; + goto nextarg; + case 'b': + bflag++; + goto nextarg; + case 's': + sflag++; + goto nextarg; + case 'm': + mflag++; + TWENTY = atoi(*argv + 1); + if (TWENTY > DEFAULT_TWENTY) + Usage(*argv-1); + goto nextarg; + case 't': + tflag++; + THRESHOLD_PERCENT = (floatish) atof(*argv + 1); + if (THRESHOLD_PERCENT < 0 || THRESHOLD_PERCENT > 5) + Usage(*argv-1); + goto nextarg; + case 'c': + cflag++; + goto nextarg; + case '?': + default: + Usage(*argv-1); + } +nextarg: ; + argc--, argv++; + } + + hpfile = "stdin"; + psfile = "stdout"; + + hpfp = stdin; + psfp = stdout; + + filter = argc < 1; + + + + if (!filter) { + pathName = copystring(argv[0]); + DropSuffix(pathName, ".hp"); + baseName = copystring(Basename(pathName)); + + hpfp = Fp(pathName, &hpfile, ".hp", "r"); + psfp = Fp(baseName, &psfile, ".ps", "w"); + + if (pflag) auxfp = Fp(baseName, &auxfile, ".aux", "r"); + } + + GetHpFile(hpfp); + + if (!filter && pflag) GetAuxFile(auxfp); + + + TraceElement(); /* Orders on total, Removes trace elements (tflag) */ + + if (dflag) Deviation(); /* ReOrders on deviation */ + + if (iflag) Identorder(iflag); /* ReOrders on identifier */ + + if (pflag) Reorder(); /* ReOrders on aux file */ + + if (TWENTY) TopTwenty(); /* Selects top twenty (mflag) */ + + Dimensions(); + + areabelow = AreaBelow(); + + Scale(); + + PutPsFile(); + + if (!filter) { + auxfp = Fp(baseName, &auxfile, ".aux", "w"); + PutAuxFile(auxfp); + } + + return(0); +} + + + +typedef enum {POINTS, INCHES, MILLIMETRES} pim; + +static pim Units PROTO((char *)); /* forward */ + +static floatish +WidthInPoints(wstr) + char *wstr; +{ + floatish result; + + result = (floatish) atof(wstr); + + switch (Units(wstr)) { + case INCHES: + result *= 72.0; + break; + case MILLIMETRES: + result *= 2.834646; + break; + case POINTS: + default: ; + } + + if (result <= 144) /* Minimum of 2in wide ! */ + Usage(wstr); + + return result; +} + + +static pim +Units(wstr) + char* wstr; +{ +int i; + + i = strlen(wstr) - 2; + + if (wstr[i] == 'p' && wstr[i+1] == 't') { + return POINTS; + } else if (wstr[i] == 'i' && wstr[i+1] == 'n') { + return INCHES; + } else if (wstr[i] == 'm' && wstr[i+1] == 'm') { + return MILLIMETRES; + } else { + return POINTS; + } +} + +static FILE * +Fp(rootname, filename, suffix, mode) + char* rootname; char** filename; char* suffix; char* mode; +{ + *filename = copystring2(rootname, suffix); + + return(OpenFile(*filename, mode)); +} + +#ifdef DEBUG +void +_stgAssert (filename, linenum) + char *filename; + unsigned int linenum; +{ + fflush(stdout); + fprintf(stderr, "ASSERTION FAILED: file %s, line %u\n", filename, linenum); + fflush(stderr); + abort(); +} +#endif diff --git a/massif/hp2ps/Main.h b/massif/hp2ps/Main.h new file mode 100644 index 0000000000..3aedb917b4 --- /dev/null +++ b/massif/hp2ps/Main.h @@ -0,0 +1,78 @@ +#ifndef MAIN_H +#define MAIN_H + +//#include "config.h" + +#ifdef __STDC__ +#define PROTO(x) x +#else +#define PROTO(x) () +#endif + +/* our own ASSERT macro (for C) */ +#ifndef DEBUG +#define ASSERT(predicate) /*nothing*/ + +#else +void _ghcAssert PROTO((char *, unsigned int)); + +#define ASSERT(predicate) \ + if (predicate) \ + /*null*/; \ + else \ + _ghcAssert(__FILE__, __LINE__) +#endif + +/* partain: some ubiquitous types: floatish & intish. + Dubious to use float/int, but that is what it used to be... + (WDP 95/03) +*/ +typedef double floatish; +typedef double doublish; /* higher precision, if anything; little used */ +typedef int boolish; + +/* Use "long long" if we have it: the numbers in profiles can easily + * overflow 32 bits after a few seconds execution. + */ +#define HAVE_LONG_LONG 1 // --added by njn, because config.h removed + +#ifdef HAVE_LONG_LONG +typedef long long int intish; +#else +typedef long int intish; +#endif + +extern intish nsamples; +extern intish nmarks; +extern intish nidents; + +extern floatish maxcombinedheight; +extern floatish areabelow; +extern floatish epsfwidth; + +extern floatish xrange; +extern floatish yrange; + +extern floatish auxxrange; +extern floatish auxyrange; + +extern boolish eflag; +extern boolish gflag; +extern boolish yflag; +extern boolish bflag; +extern boolish sflag; +extern int mflag; +extern boolish tflag; +extern boolish cflag; + +extern char *programname; + +extern char *hpfile; +extern char *psfile; +extern char *auxfile; + +extern FILE *hpfp; +extern FILE *psfp; +extern FILE *g_auxfp; + +#endif /* MAIN_H */ diff --git a/massif/hp2ps/Marks.c b/massif/hp2ps/Marks.c new file mode 100644 index 0000000000..27702c73d9 --- /dev/null +++ b/massif/hp2ps/Marks.c @@ -0,0 +1,43 @@ +#include +#include "Main.h" +#include "Curves.h" +#include "Dimensions.h" +#include "HpFile.h" + +/* own stuff */ +#include "Marks.h" + +static void Caret PROTO((floatish, floatish, floatish)); + +void +Marks() +{ + intish i; + floatish m; + + for (i = 0; i < nmarks; i++) { + m = ((markmap[i] - samplemap[0]) / xrange) * graphwidth; + Caret(xpage(m), ypage(0.0), 4.0); + } +} + + +/* + * Draw a small white caret at (x,y) with width 2 * d + */ + +static void +Caret(x,y,d) + floatish x; floatish y; floatish d; +{ + fprintf(psfp, "%f %f moveto\n", x - d, y); + fprintf(psfp, "%f %f rlineto\n", d, -d); + fprintf(psfp, "%f %f rlineto\n", d, d); + fprintf(psfp, "closepath\n"); + + fprintf(psfp, "gsave\n"); + fprintf(psfp, "1.0 setgray\n"); + fprintf(psfp, "fill\n"); + fprintf(psfp, "grestore\n"); + fprintf(psfp, "stroke\n"); +} diff --git a/massif/hp2ps/Marks.h b/massif/hp2ps/Marks.h new file mode 100644 index 0000000000..41956f6e83 --- /dev/null +++ b/massif/hp2ps/Marks.h @@ -0,0 +1,6 @@ +#ifndef MARKS_H +#define MARKS_H + +void Marks PROTO((void)); + +#endif /* MARKS_H */ diff --git a/massif/hp2ps/PsFile.c b/massif/hp2ps/PsFile.c new file mode 100644 index 0000000000..40b08dab85 --- /dev/null +++ b/massif/hp2ps/PsFile.c @@ -0,0 +1,280 @@ +#include +#include +#include "Main.h" +#include "Defines.h" +#include "Dimensions.h" +#include "Curves.h" +#include "HpFile.h" +#include "Axes.h" +#include "Key.h" +#include "Marks.h" +#include "Utilities.h" + +/* own stuff */ +#include "PsFile.h" + +static void Prologue PROTO((void)); /* forward */ +static void Variables PROTO((void)); /* forward */ +static void BorderOutlineBox PROTO((void)); /* forward */ +static void BigTitleOutlineBox PROTO((void)); /* forward */ +static void TitleOutlineBox PROTO((void)); /* forward */ +static void BigTitleText PROTO((void)); /* forward */ +static void TitleText PROTO((void)); /* forward */ + +void +PutPsFile() +{ + Prologue(); + Variables(); + BorderOutlineBox(); + + if (bflag) { + BigTitleOutlineBox(); + BigTitleText(); + } else { + TitleOutlineBox(); + TitleText(); + } + + CurvesInit(); + + Axes(); + + if (TWENTY) Key(); + + Curves(); + + if (!yflag) Marks(); + + fprintf(psfp, "showpage\n"); +} + + +static void StandardSpecialComments PROTO((void)); /* forward */ +static void EPSFSpecialComments PROTO((floatish)); /* forward */ +static void Landscape PROTO((void)); /* forward */ +static void Portrait PROTO((void)); /* forward */ +static void Scaling PROTO((floatish)); /* forward */ + +static void +Prologue() +{ + if (eflag) { + floatish epsfscale = epsfwidth / (floatish) borderwidth; + EPSFSpecialComments(epsfscale); + Scaling(epsfscale); + } else { + StandardSpecialComments(); + if (gflag) Portrait(); else Landscape(); + } +} + +extern char *jobstring; +extern char *datestring; + +static void +StandardSpecialComments() +{ + fprintf(psfp, "%%!PS-Adobe-2.0\n"); + fprintf(psfp, "%%%%Title: %s\n", jobstring); + fprintf(psfp, "%%%%Creator: %s (version %s)\n", programname, VERSION); + fprintf(psfp, "%%%%CreationDate: %s\n", datestring); + fprintf(psfp, "%%%%EndComments\n"); +} + +static void +EPSFSpecialComments(epsfscale) + floatish epsfscale; +{ + fprintf(psfp, "%%!PS-Adobe-2.0\n"); + fprintf(psfp, "%%%%Title: %s\n", jobstring); + fprintf(psfp, "%%%%Creator: %s (version %s)\n", programname, VERSION); + fprintf(psfp, "%%%%CreationDate: %s\n", datestring); + fprintf(psfp, "%%%%BoundingBox: 0 0 %d %d\n", + (int) (borderwidth * epsfscale + 0.5), + (int) (borderheight * epsfscale + 0.5) ); + fprintf(psfp, "%%%%EndComments\n"); +} + + + +static void +Landscape() +{ + fprintf(psfp, "-90 rotate\n"); + fprintf(psfp, "%f %f translate\n", -(borderwidth + (floatish) START_Y), + (floatish) START_X); +} + +static void +Portrait() +{ + fprintf(psfp, "%f %f translate\n", (floatish) START_X, (floatish) START_Y); +} + +static void +Scaling(epsfscale) + floatish epsfscale; +{ + fprintf(psfp, "%f %f scale\n", epsfscale, epsfscale); +} + + +static void +Variables() +{ + fprintf(psfp, "/HE%d /Helvetica findfont %d scalefont def\n", + NORMAL_FONT, NORMAL_FONT); + + fprintf(psfp, "/HE%d /Helvetica findfont %d scalefont def\n", + LARGE_FONT, LARGE_FONT); +} + + +static void +BorderOutlineBox() +{ + fprintf(psfp, "newpath\n"); + fprintf(psfp, "0 0 moveto\n"); + fprintf(psfp, "0 %f rlineto\n", borderheight); + fprintf(psfp, "%f 0 rlineto\n", borderwidth); + fprintf(psfp, "0 %f rlineto\n", -borderheight); + fprintf(psfp, "closepath\n"); + fprintf(psfp, "%f setlinewidth\n", borderthick); + fprintf(psfp, "stroke\n"); +} + +static void +BigTitleOutlineBox() +{ + fprintf(psfp, "newpath\n"); + fprintf(psfp, "%f %f moveto\n", borderspace, + borderheight - titleheight - borderspace); + fprintf(psfp, "0 %f rlineto\n", titleheight); + fprintf(psfp, "%f 0 rlineto\n", titlewidth); + fprintf(psfp, "0 %f rlineto\n", -titleheight); + fprintf(psfp, "closepath\n"); + fprintf(psfp, "%f setlinewidth\n", borderthick); + fprintf(psfp, "stroke\n"); + + fprintf(psfp, "%f %f moveto\n", borderspace, + borderheight - titleheight / 2 - borderspace); + fprintf(psfp, "%f 0 rlineto\n", titlewidth); + fprintf(psfp, "stroke\n"); +} + + +static void +TitleOutlineBox() +{ + fprintf(psfp, "newpath\n"); + fprintf(psfp, "%f %f moveto\n", borderspace, + borderheight - titleheight - borderspace); + fprintf(psfp, "0 %f rlineto\n", titleheight); + fprintf(psfp, "%f 0 rlineto\n", titlewidth); + fprintf(psfp, "0 %f rlineto\n", -titleheight); + fprintf(psfp, "closepath\n"); + fprintf(psfp, "%f setlinewidth\n", borderthick); + fprintf(psfp, "stroke\n"); +} + +static void EscapePrint PROTO((char *, int)); /* forward */ + +static void +BigTitleText() +{ + floatish x, y; + + x = borderspace + titletextspace; + y = borderheight - titleheight / 2 - borderspace + titletextspace; + + /* job identifier goes on top at the far left */ + + fprintf(psfp, "HE%d setfont\n", TITLE_TEXT_FONT); + fprintf(psfp, "%f %f moveto\n", x, y); + fputc('(', psfp); + EscapePrint(jobstring, BIG_JOB_STRING_WIDTH); + fprintf(psfp, ") show\n"); + + y = borderheight - titleheight - borderspace + titletextspace; + + /* area below curve gows at the botton, far left */ + + fprintf(psfp, "HE%d setfont\n", TITLE_TEXT_FONT); + fprintf(psfp, "%f %f moveto\n", x, y); + fputc('(', psfp); + CommaPrint(psfp, (intish)areabelow); + fprintf(psfp, " %s x %s)\n", valueunitstring, sampleunitstring); + fprintf(psfp, "show\n"); + + /* date goes at far right */ + + fprintf(psfp, "HE%d setfont\n", TITLE_TEXT_FONT); + fprintf(psfp, "(%s)\n", datestring); + fprintf(psfp, "dup stringwidth pop\n"); + fprintf(psfp, "%f\n", (titlewidth + borderspace) - titletextspace); + fprintf(psfp, "exch sub\n"); + fprintf(psfp, "%f moveto\n", y); + fprintf(psfp, "show\n"); +} + + +static void +TitleText() +{ + floatish x, y; + + x = borderspace + titletextspace; + y = borderheight - titleheight - borderspace + titletextspace; + + /* job identifier goes at far left */ + + fprintf(psfp, "HE%d setfont\n", TITLE_TEXT_FONT); + fprintf(psfp, "%f %f moveto\n", x, y); + fputc('(', psfp); + EscapePrint(jobstring, SMALL_JOB_STRING_WIDTH); + fprintf(psfp, ") show\n"); + + /* area below curve is centered */ + + fprintf(psfp, "HE%d setfont\n", TITLE_TEXT_FONT); + fputc('(', psfp); + CommaPrint(psfp, (intish) areabelow); + fprintf(psfp, " %s x %s)\n", valueunitstring, sampleunitstring); + + fprintf(psfp, "dup stringwidth pop\n"); + fprintf(psfp, "2 div\n"); + fprintf(psfp, "%f\n", titlewidth / 2); + fprintf(psfp, "exch sub\n"); + fprintf(psfp, "%f moveto\n", y); + fprintf(psfp, "show\n"); + + /* date goes at far right */ + + fprintf(psfp, "HE%d setfont\n", TITLE_TEXT_FONT); + fprintf(psfp, "(%s)\n", datestring); + fprintf(psfp, "dup stringwidth pop\n"); + fprintf(psfp, "%f\n", (titlewidth + borderspace) - titletextspace); + fprintf(psfp, "exch sub\n"); + fprintf(psfp, "%f moveto\n", y); + fprintf(psfp, "show\n"); +} + +/* + * Print a string s in width w, escaping characters where necessary. + */ + +static void +EscapePrint(s,w) + char* s; int w; +{ + for ( ; *s && w > 0; s++, w--) { + if (*s == '(') { /* escape required */ + fputc('\\', psfp); + } else if (*s == ')') { + fputc('\\', psfp); + } + + fputc(*s, psfp); + } +} diff --git a/massif/hp2ps/PsFile.h b/massif/hp2ps/PsFile.h new file mode 100644 index 0000000000..acec0703bc --- /dev/null +++ b/massif/hp2ps/PsFile.h @@ -0,0 +1,6 @@ +#ifndef PS_FILE_H +#define PS_FILE_H + +void PutPsFile PROTO((void)); + +#endif /* PS_FILE_H */ diff --git a/massif/hp2ps/README b/massif/hp2ps/README new file mode 100644 index 0000000000..f0f09ce35a --- /dev/null +++ b/massif/hp2ps/README @@ -0,0 +1 @@ +This "hp2ps" program was written by Dave Wakeling at York. diff --git a/massif/hp2ps/Reorder.c b/massif/hp2ps/Reorder.c new file mode 100644 index 0000000000..94bda2c5b4 --- /dev/null +++ b/massif/hp2ps/Reorder.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include "Main.h" +#include "Defines.h" +#include "Error.h" +#include "HpFile.h" +#include "Utilities.h" + +/* own stuff */ +#include "Reorder.h" + +static struct order { + char* ident; + int order; +} *ordermap = 0; + +static int ordermapmax = 0; +static int ordermapindex = 0; + + +void +OrderFor(ident, order) + char* ident; + int order; +{ + if (! ordermap) { + ordermapmax = (nidents > TWENTY ? nidents : TWENTY) * 2; + /* Assume nidents read is indication of the No of + idents in the .aux file (*2 for good luck !) */ + ordermap = xmalloc(ordermapmax * sizeof(struct order)); + } + + if (ordermapindex < ordermapmax) { + ordermap[ ordermapindex ].ident = copystring(ident); + ordermap[ ordermapindex ].order = order; + ordermapindex++; + } else { + Disaster("order map overflow"); + } +} + +/* + * Get the order of to be used for "ident" if there is one. + * Otherwise, return 0 which is the minimum ordering value. + */ + +int +OrderOf(ident) + char* ident; +{ + int i; + + for (i = 0; i < ordermapindex; i++) { + if (strcmp(ordermap[i].ident, ident) == 0) { /* got it */ + return(ordermap[i].order); + } + } + + return 0; +} + +/* + * Reorder on the basis of information from ".aux" file. + */ + +void +Reorder() +{ + intish i; + intish j; + int min; + struct entry* e; + int o1, o2; + + for (i = 0; i < nidents-1; i++) { + min = i; + for (j = i+1; j < nidents; j++) { + o1 = OrderOf(identtable[ j ]->name); + o2 = OrderOf(identtable[ min ]->name); + + if (o1 < o2 ) min = j; + } + + e = identtable[ min ]; + identtable[ min ] = identtable[ i ]; + identtable[ i ] = e; + } +} diff --git a/massif/hp2ps/Reorder.h b/massif/hp2ps/Reorder.h new file mode 100644 index 0000000000..089ef75cfc --- /dev/null +++ b/massif/hp2ps/Reorder.h @@ -0,0 +1,8 @@ +#ifndef REORDER_H +#define REORDER_H + +void Reorder PROTO((void)); +int OrderOf PROTO((char *)); +void OrderFor PROTO((char *, int)); + +#endif /* REORDER_H */ diff --git a/massif/hp2ps/Scale.c b/massif/hp2ps/Scale.c new file mode 100644 index 0000000000..576e173c14 --- /dev/null +++ b/massif/hp2ps/Scale.c @@ -0,0 +1,87 @@ +#include +#include "Main.h" +#include "Defines.h" +#include "Dimensions.h" +#include "Error.h" +#include "HpFile.h" +#include "Utilities.h" + +/* own stuff */ +#include "Scale.h" + +/* + * Return the maximum combined height that all the sample + * curves will reach. This (absolute) figure can then be + * used to scale the samples automatically so that they + * fit on the page. + */ + +extern void free(); + +floatish +MaxCombinedHeight() +{ + intish i; + intish j; + floatish mx; + int bucket; + floatish value; + struct chunk* ch; + floatish *maxima; + + maxima = (floatish*) xmalloc(nsamples * sizeof(floatish)); + for (i = 0; i < nsamples; i++) { + maxima[ i ] = 0.0; + } + + for (i = 0; i < nidents; i++) { + for (ch = identtable[i]->chk; ch; ch = ch->next) { + for (j = 0; j < ch->nd; j++) { + bucket = ch->d[j].bucket; + value = ch->d[j].value; + if (bucket >= nsamples) + Disaster("bucket out of range"); + maxima[ bucket ] += value; + } + } + } + + for (mx = maxima[ 0 ], i = 0; i < nsamples; i++) { + if (maxima[ i ] > mx) mx = maxima[ i ]; + } + + free(maxima); + return mx; +} + + + +/* + * Scale the values from the samples so that they will fit on + * the page. + */ + +extern floatish xrange; +extern floatish yrange; + +void +Scale() +{ + intish i; + intish j; + floatish sf; + struct chunk* ch; + + if (yrange == 0.0) /* no samples */ + return; + + sf = graphheight / yrange; + + for (i = 0; i < nidents; i++) { + for (ch = identtable[i]->chk; ch; ch = ch->next) { + for (j = 0; j < ch->nd; j++) { + ch->d[j].value = ch->d[j].value * sf; + } + } + } +} diff --git a/massif/hp2ps/Scale.h b/massif/hp2ps/Scale.h new file mode 100644 index 0000000000..0c19d6c3c0 --- /dev/null +++ b/massif/hp2ps/Scale.h @@ -0,0 +1,7 @@ +#ifndef SCALE_H +#define SCALE_H + +floatish MaxCombinedHeight PROTO((void)); +void Scale PROTO((void)); + +#endif /* SCALE_H */ diff --git a/massif/hp2ps/Shade.c b/massif/hp2ps/Shade.c new file mode 100644 index 0000000000..2920fb68a4 --- /dev/null +++ b/massif/hp2ps/Shade.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include "Main.h" +#include "Defines.h" +#include "Error.h" +#include "Utilities.h" + +/* own stuff */ +#include "Shade.h" + +static struct shade { + char* ident; + floatish shade; +} *shademap; + +static int shademapmax = 0; +static int shademapindex = 0; + +/* + * Set the shade to be used for "ident" to "shade". + */ + +void +ShadeFor(ident, shade) + char* ident; + floatish shade; +{ + if (! shademap) { + shademapmax = (nidents > TWENTY ? nidents : TWENTY) * 2; + /* Assume nidents read is indication of the No of + idents in the .aux file (*2 for good luck) */ + /* NB *2 is needed as .aux and .hp elements may differ */ + shademap = xmalloc(shademapmax * sizeof(struct shade)); + } + + if (shademapindex < shademapmax) { + shademap[ shademapindex ].ident = copystring(ident); + shademap[ shademapindex ].shade = shade; + shademapindex++; + } else { + Disaster("shade map overflow"); + } +} + +/* + * Get the shade to be used for "ident" if there is one. + * Otherwise, think of a new one. + */ + +static floatish ThinkOfAShade PROTO((void)); /* forward */ + +floatish +ShadeOf(ident) + char* ident; +{ + int i; + floatish shade; + + for (i = 0; i < shademapindex; i++) { + if (strcmp(shademap[i].ident, ident) == 0) { /* got it */ + return(shademap[i].shade); + } + } + + shade = ThinkOfAShade(); + + ShadeFor(ident, shade); + + return shade; +} + + + +#define N_MONO_SHADES 10 + +static floatish m_shades[ N_MONO_SHADES ] = { + 0.00000, 0.20000, 0.60000, 0.30000, 0.90000, + 0.40000, 1.00000, 0.70000, 0.50000, 0.80000 +}; + +#define N_COLOUR_SHADES 27 + +/* HACK: 0.100505 means 100% red, 50% green, 50% blue */ + +static floatish c_shades[ N_COLOUR_SHADES ] = { + 0.000000, 0.000010, 0.001000, 0.001010, 0.100000, + 0.100010, 0.101000, 0.101010, 0.000005, 0.000500, + 0.000510, 0.001005, 0.050000, 0.050010, 0.051000, + 0.051010, 0.100005, 0.100500, 0.100510, 0.101005, + 0.000505, 0.050005, 0.050500, 0.050510, 0.051005, + 0.100505, 0.050505 +}; + +static floatish +ThinkOfAShade() +{ + static int thisshade = -1; + + thisshade++; + return cflag ? + c_shades[ thisshade % N_COLOUR_SHADES ] : + m_shades[ thisshade % N_MONO_SHADES ] ; +} + +static floatish +extract_colour(shade,factor) + floatish shade; + intish factor; +{ + intish i,j; + + i = (int)(shade * factor); + j = i / 100; + return (i - j * 100) / 10.0; +} + +void +SetPSColour(shade) + floatish shade; +{ + if (cflag) { + fprintf(psfp, "%f %f %f setrgbcolor\n", + extract_colour(shade, (intish)100), + extract_colour(shade, (intish)10000), + extract_colour(shade, (intish)1000000)); + } else { + fprintf(psfp, "%f setgray\n", shade); + } +} diff --git a/massif/hp2ps/Shade.h b/massif/hp2ps/Shade.h new file mode 100644 index 0000000000..0e49c90d04 --- /dev/null +++ b/massif/hp2ps/Shade.h @@ -0,0 +1,8 @@ +#ifndef SHADE_H +#define SHADE_H + +floatish ShadeOf PROTO((char *)); +void ShadeFor PROTO((char *, floatish)); +void SetPSColour PROTO((floatish)); + +#endif /* SHADE_H */ diff --git a/massif/hp2ps/TopTwenty.c b/massif/hp2ps/TopTwenty.c new file mode 100644 index 0000000000..9060aaf6d2 --- /dev/null +++ b/massif/hp2ps/TopTwenty.c @@ -0,0 +1,73 @@ +#include +#include "Main.h" +#include "Defines.h" +#include "Error.h" +#include "HpFile.h" +#include "Utilities.h" + +/* own stuff */ +#include "TopTwenty.h" + +/* + * We only have room in the key for a maximum of 20 identifiers. + * We therefore choose to keep the top 20 bands --- these will + * be the most important ones, since this pass is performed after + * the threshold and standard deviation passes. If there are more + * than 20 bands, the excess are gathered together as an "OTHER" ] + * band which appears as band 20. + */ + +extern void free(); + +void +TopTwenty() +{ + intish i; + intish j; + intish compact; + intish bucket; + floatish value; + struct entry* en; + struct chunk* ch; + floatish *other; + + i = nidents; + if (i <= TWENTY) return; /* nothing to do! */ + + other = (floatish*) xmalloc(nsamples * sizeof(floatish)); + /* build a list of samples for "OTHER" */ + + compact = (i - TWENTY) + 1; + + for (i = 0; i < nsamples; i++) { + other[ i ] = 0.0; + } + + for (i = 0; i < compact && i < nidents; i++) { + for (ch = identtable[i]->chk; ch; ch = ch->next) { + for (j = 0; j < ch->nd; j++) { + bucket = ch->d[j].bucket; + value = ch->d[j].value; + if (bucket >= nsamples) + Disaster("bucket out of range"); + other[ bucket ] += value; + } + } + } + + en = MakeEntry("OTHER"); + en->next = 0; + + for (i = 0; i < nsamples; i++) { + StoreSample(en, i, other[i]); + } + + /* slide samples down */ + for (i = compact; i < nidents; i++) { + identtable[i-compact+1] = identtable[i]; + } + + nidents = TWENTY; + identtable[0] = en; + free(other); +} diff --git a/massif/hp2ps/TopTwenty.h b/massif/hp2ps/TopTwenty.h new file mode 100644 index 0000000000..53a7aed509 --- /dev/null +++ b/massif/hp2ps/TopTwenty.h @@ -0,0 +1,6 @@ +#ifndef TOP_TWENTY_H +#define TOP_TWENTY_H + +void TopTwenty PROTO((void)); + +#endif /* TOP_TWENTY_H */ diff --git a/massif/hp2ps/TraceElement.c b/massif/hp2ps/TraceElement.c new file mode 100644 index 0000000000..984faf5325 --- /dev/null +++ b/massif/hp2ps/TraceElement.c @@ -0,0 +1,97 @@ +#include +#include "Main.h" +#include "Defines.h" +#include "HpFile.h" +#include "Error.h" +#include "Utilities.h" + +/* own stuff */ +#include "TraceElement.h" + +/* + * Compute the total volume for each identifier, and the grand + * total of these totals. The identifiers whose totals when + * added together amount to less that a threshold percentage + * (default 1%) of the grand total are considered to be ``trace + * elements'' and they are thrown away. + */ + +extern void free(); + +extern floatish thresholdpercent; + +void TraceElement() +{ + intish i; + intish j; + struct chunk* ch; + floatish grandtotal; + intish min; + floatish t; + floatish p; + struct entry* e; + intish *totals; + + totals = (intish *) xmalloc(nidents * sizeof(intish)); + + /* find totals */ + + for (i = 0; i < nidents; i++) { + totals[ i ] = 0; + } + + for (i = 0; i < nidents; i++) { + for (ch = identtable[i]->chk; ch; ch = ch->next) { + for (j = 0; j < ch->nd; j++) { + totals[ i ] += ch->d[j].value; + } + } + } + + /* sort on the basis of total */ + + for (i = 0; i < nidents-1; i++) { + min = i; + for (j = i+1; j < nidents; j++) { + if (totals[ j ] < totals[ min ]) { + min = j; + } + } + + t = totals[ min ]; + totals[ min ] = totals[ i ]; + totals[ i ] = t; + + e = identtable[ min ]; + identtable[ min ] = identtable[ i ]; + identtable[ i ] = e; + } + + + /* find the grand total (NB: can get *BIG*!) */ + + grandtotal = 0.0; + + for (i = 0; i < nidents; i++) { + grandtotal += (floatish) totals[ i ]; + } + + t = 0.0; /* cumulative percentage */ + + for (i = 0; i < nidents; i++) { + p = (100.0 * (floatish) totals[i]) / grandtotal; + t = t + p; + if (t >= THRESHOLD_PERCENT) { + break; + } + } + + /* identifiers from 0 to i-1 should be removed */ + for (j = 0; i < nidents; i++, j++) { + identtable[j] = identtable[i]; + } + + nidents = j; + + free(totals); +} diff --git a/massif/hp2ps/TraceElement.h b/massif/hp2ps/TraceElement.h new file mode 100644 index 0000000000..d843392a23 --- /dev/null +++ b/massif/hp2ps/TraceElement.h @@ -0,0 +1,6 @@ +#ifndef TRACE_ELEMENT_H +#define TRACE_ELEMENT_H + +void TraceElement PROTO((void)); + +#endif /* TRACE_ELEMENT_H */ diff --git a/massif/hp2ps/Utilities.c b/massif/hp2ps/Utilities.c new file mode 100644 index 0000000000..57d20b1cc5 --- /dev/null +++ b/massif/hp2ps/Utilities.c @@ -0,0 +1,132 @@ +#include +#include +#include "Main.h" +#include "Error.h" + +extern void* malloc(); + +char* +Basename(name) + char* name; +{ + char* t; + + t = name; + + while (*name) { + if (*name == '/') { + t = name+1; + } + name++; + } + + return t; +} + +void +DropSuffix(name, suffix) + char* name; char* suffix; +{ + char* t; + + t = (char*) 0; + + while (*name) { + if (*name == '.') { + t = name; + } + name++; + } + + if (t != (char*) 0 && strcmp(t, suffix) == 0) { + *t = '\0'; + } +} + +FILE* +OpenFile(s, mode) + char* s; char* mode; +{ + FILE* r; + + if ((r = fopen(s, mode)) == NULL) { + /*NOTREACHED*/ + Error("cannot open %s", s); + } + + return r; +} + + +#define ONETHOUSAND 1000 + +/* + * Print a positive integer with commas + */ + +void +CommaPrint(fp,n) + FILE* fp; + intish n; +{ + if (n < ONETHOUSAND) { + fprintf(fp, "%d", (int)n); + } else { + CommaPrint(fp, n / ONETHOUSAND); + fprintf(fp, ",%03d", (int)n % ONETHOUSAND); + } +} + +void * +xmalloc(n) + int n; +{ + void *r; + + r = (void*) malloc(n); + if (!r) { + /*NOTREACHED*/ + Disaster("%s, sorry, out of memory", hpfile); + } + return r; +} + +void * +xrealloc(p, n) + void *p; + int n; +{ + void *r; + extern void *realloc(); + + r = realloc(p, n); + if (!r) { + /*NOTREACHED*/ + Disaster("%s, sorry, out of memory", hpfile); + } + return r; +} + +char * +copystring(s) + char *s; +{ + char *r; + + r = (char*) xmalloc(strlen(s)+1); + strcpy(r, s); + return r; +} + +char * +copystring2(s, t) + char *s, *t; +{ + char *r; + + r = (char*) xmalloc(strlen(s)+strlen(t)+1); + strcpy(r, s); + strcat(r, t); + return r; +} + diff --git a/massif/hp2ps/Utilities.h b/massif/hp2ps/Utilities.h new file mode 100644 index 0000000000..bffc87d3e3 --- /dev/null +++ b/massif/hp2ps/Utilities.h @@ -0,0 +1,13 @@ +#ifndef UTILITIES_H +#define UTILITIES_H + +char* Basename PROTO((char *)); +void DropSuffix PROTO((char *, char *)); +FILE* OpenFile PROTO((char *, char *)); +void CommaPrint PROTO((FILE *, intish)); +char *copystring PROTO((char *)); +char *copystring2 PROTO((char *, char *)); +void *xmalloc PROTO((int)); +void *xrealloc PROTO((void *, int)); + +#endif /* UTILITIES_H */ diff --git a/massif/hp2ps/hp2ps.1 b/massif/hp2ps/hp2ps.1 new file mode 100644 index 0000000000..fd0bca0234 --- /dev/null +++ b/massif/hp2ps/hp2ps.1 @@ -0,0 +1,145 @@ +.\" man page for hp2ps +.ds PS P\s-2OST\s+2S\s-2CRIPT\s+2 +.\" typeset examples in fixed size font as indented paragraph +.de Ex +.sp +.RS +.nf +.ft C +.. +.de Xe +.RE +.sp +.fi +.. +.TH HP2PS 1 "18 April 1992" +.SH NAME +hp2ps \- convert a heap profile to a \*(PS graph +.SH SYNOPSIS +.B hp2ps +[flags] [file][.hp] +.SH DESCRIPTION +The program +.B hp2ps +converts a heap profile stored in +.IR file +into a \*(PS graph, sending the result to +.IR file.ps. +By convention, files to be processed by +.B hp2ps +have a +.I .hp +extension. However, for compatibility with older versions of +.B hp2ps, +this extension can be omitted. If +.IR file +is omitted entirely, then the program behaves as a filter. +.SH OPTIONS +The flags are: +.IP "\fB\-d\fP" +In order to make graphs more readable, +.B hp2ps +sorts the shaded bands for each identifier. The default sort ordering is for +the bands with the largest area to be stacked on top of the smaller ones. +The +.B \-d +option causes rougher bands (those reprsenting series of values with the +largest standard deviations) to be stacked on top of smoother ones. +.IP "\fB\-b\fP" +Normally, +.B hp2ps +puts the title of the graph in a small box at the top of the page. However, +if the JOB string is too long to fit in a small box (more than 35 characters), +then +.B hp2ps +will choose to use a big box instead. The +.B \-b +option forces +.B hp2ps +to use a big box. +.IP "\fB\-e\fP \fIfloat\fP[in|mm|pt]" +Generate encapsulated \*(PS suitable for inclusion in LaTeX documents. +Usually, the \*(PS graph is drawn in landscape mode in an area +9 inches wide by 6 inches high, and +.B hp2ps +arranges for this area to be approximately centered on a sheet of a4 +paper. This format is convenient of studying the graph in detail, but +it is unsuitable for inclusion in LaTeX documents. The +.B \-e +option causes the graph to be drawn in portrait mode, with +.I float +specifying the width in inches, millimetres or points (the default). +The resulting \*(PS file conforms to the +.I "Encapsulated Post Script" +(EPS) convention, and it can be included in a LaTeX document using Rokicki's +dvi-to-\*(PS converter +.B dvips. +.B hp2ps +requires the width to exceed 2 inches. +.IP "\fB\-g\fP" +Create output suitable for the +.B gs +\*(PS previewer (or similar). In this case the graph is printed in portrait +mode without scaling. The output is unsuitable for a laser printer. +.IP "\fB\-p\fP" +Use previous parameters. By default, the \*(PS graph is automatically +scaled both horizontally and vertically so that it fills the page. +However, when preparing a seires of graphs for use in a presentation, +it is often useful to draw a new graph using the same scale, shading and +ordering as a previous one. The +.B \-p +flag causes the graph to be drawn using the parameters determined by +a previous run of +.B hp2ps +on +.IR file. +.IP "\fB\-s\fP" +Use a small box for the title. +.IP "\fB\-y\fP" +Draw the graph in the traditional York style, ignoring marks. +.IP "\fB\-?\fP" +Print out usage information. +.SH "INPUT FORMAT" +The format of a heap profile is best described by example: +.Ex +JOB "a.out -p" +DATE "Fri Apr 17 11:43:45 1992" +SAMPLE_UNIT "seconds" +VALUE_UNIT "bytes" +BEGIN_SAMPLE 0.00 + SYSTEM 24 +END_SAMPLE 0.00 +BEGIN_SAMPLE 1.00 + elim 180 + insert 24 + intersect 12 + disin 60 + main 12 + reduce 20 + SYSTEM 12 +END_SAMPLE 1.00 +MARK 1.50 +MARK 1.75 +MARK 1.80 +BEGIN_SAMPLE 2.00 + elim 192 + insert 24 + intersect 12 + disin 84 + main 12 + SYSTEM 24 +END_SAMPLE 2.00 +BEGIN_SAMPLE 2.82 +END_SAMPLE 2.82 + +.Xe +.SH "SEE ALSO" +dvips(1), latex(1), hbchp (1), lmlchp(1) +.br +C. Runciman and D. Wakeling, +.I +Heap Profiling for Lazy Functional Languages, YCS-172, University of York, 1992 +.SH NOTES +\*(PS is a registered trademark of Adobe Systems Incorporated. +.SH AUTHOR +David Wakeling of the University of York. diff --git a/massif/ms_main.c b/massif/ms_main.c new file mode 100644 index 0000000000..54c4ccec9f --- /dev/null +++ b/massif/ms_main.c @@ -0,0 +1,1815 @@ + +/*--------------------------------------------------------------------*/ +/*--- Massif: a heap profiling skin. ms_main.c ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Massif, a Valgrind skin for profiling memory + usage of programs. + + Copyright (C) 2003 Nicholas Nethercote + njn25@cam.ac.uk + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307, USA. + + The GNU General Public License is contained in the file COPYING. +*/ + +// Memory profiler. Produces a graph, gives lots of information about +// allocation contexts, in terms of space.time values (ie. area under the +// graph). Allocation context information is hierarchical, and can thus +// be inspected step-wise to an appropriate depth. See comments on data +// structures below for more info on how things work. + +#include "vg_skin.h" +//#include "vg_profile.c" + +#include "valgrind.h" // For {MALLOC,FREE}LIKE_BLOCK + +/*------------------------------------------------------------*/ +/*--- Overview of operation ---*/ +/*------------------------------------------------------------*/ + +// Heap blocks are tracked, and the amount of space allocated by various +// contexts (ie. lines of code, more or less) is also tracked. +// Periodically, a census is taken, and the amount of space used, at that +// point, by the most significant (highly allocating) contexts is recorded. +// Census start off frequently, but are scaled back as the program goes on, +// so that there are always a good number of them. At the end, overall +// spacetimes for different contexts (of differing levels of precision) is +// calculated, the graph is printed, and the text giving spacetimes for the +// increasingly precise contexts is given. +// +// Measures the following: +// - heap blocks +// - heap admin bytes +// - stack(s) +// - code (code segments loaded at startup, and loaded with mmap) +// - data (data segments loaded at startup, and loaded/created with mmap, +// and brk()d segments) + +/*------------------------------------------------------------*/ +/*--- Main types ---*/ +/*------------------------------------------------------------*/ + +// An XPt represents an "execution point", ie. a code address. Each XPt is +// part of a tree of XPts (an "execution tree", or "XTree"). Each +// top-to-bottom path through an XTree gives an execution context ("XCon"), +// and is equivalent to a traditional Valgrind ExeContext. +// +// The XPt at the top of an XTree (but below "alloc_xpt") is called a +// "top-XPt". The XPts are the bottom of an XTree (leaf nodes) are +// "bottom-XPTs". The number of XCons in an XTree is equal to the number of +// bottom-XPTs in that XTree. +// +// All XCons have the same top-XPt, "alloc_xpt", which represents all +// allocation functions like malloc(). It's a bit of a fake XPt, though, +// and is only used because it makes some of the code simpler. +// +// XTrees are bi-directional. +// +// > parent < Example: if child1() calls parent() and child2() +// / | \ also calls parent(), and parent() calls malloc(), +// | / \ | the XTree will look like this. +// | v v | +// child1 child2 + +typedef struct _XPt XPt; + +struct _XPt { + Addr eip; // code address + + // Bottom-XPts: space for the precise context. + // Other XPts: space of all the descendent bottom-XPts. + // Nb: this value goes up and down as the program executes. + UInt curr_space; + + // An approximate space.time calculation used along the way for selecting + // which contexts to include at each census point. + // !!! top-XPTs only !!! + ULong spacetime; + + // spacetime2 is an exact space.time calculation done at the end, and + // used in the results. + // Note that it is *doubled*, to avoid rounding errors. + // !!! not used for 'alloc_xpt' !!! + ULong spacetime2; + + // n_children and max_children are integers; a very big program might + // have more than 65536 allocation points (Konqueror startup has 1800). + XPt* parent; // pointer to parent XPt + UInt n_children; // number of children + UInt max_children; // capacity of children array + XPt** children; // pointers to children XPts +}; + +// Each census snapshots the most significant XTrees, each XTree having a +// top-XPt as its root. The 'curr_space' element for each XPt is recorded +// in the snapshot. The snapshot contains all the XTree's XPts, not in a +// tree structure, but flattened into an array. This flat snapshot is used +// at the end for computing spacetime2 for each XPt. +// +// Graph resolution, x-axis: no point having more than about 200 census +// x-points; you can't see them on the graph. Therefore: +// +// - do a census every 1 ms for first 200 --> 200, all (200 ms) +// - halve (drop half of them) --> 100, every 2nd (200 ms) +// - do a census every 2 ms for next 200 --> 200, every 2nd (400 ms) +// - halve --> 100, every 4th (400 ms) +// - do a census every 4 ms for next 400 --> 200, every 4th (800 ms) +// - etc. +// +// This isn't exactly right, because we actually drop (N/2)-1 when halving, +// but it shows the basic idea. + +#define MAX_N_CENSI 200 // Keep it even, for simplicity + +// Graph resolution, y-axis: hp2ps only draws the 19 biggest (in space-time) +// bands, rest get lumped into OTHERS. I only print the top N +// (cumulative-so-far space-time) at each point. N should be a bit bigger +// than 19 in case the cumulative space-time doesn't fit with the eventual +// space-time computed by hp2ps (but it should be close if the samples are +// evenly spread, since hp2ps does an approximate per-band space-time +// calculation that just sums the totals; ie. it assumes all samples are +// the same distance apart). + +#define MAX_SNAPSHOTS 32 + +typedef + struct { + XPt* xpt; + UInt space; + } + XPtSnapshot; + +// An XTree snapshot is stored as an array of of XPt snapshots. +typedef XPtSnapshot* XTreeSnapshot; + +typedef + struct { + Int ms_time; // Int: must allow -1 + XTreeSnapshot xtree_snapshots[MAX_SNAPSHOTS+1]; // +1 for zero-termination + UInt others_space; + UInt heap_admin_space; + UInt stacks_space; + } + Census; + +// Metadata for heap blocks. Each one contains a pointer to a bottom-XPt, +// which is a foothold into the XCon at which it was allocated. From +// HP_Chunks, XPt 'space' fields are incremented (at allocation) and +// decremented (at deallocation). +// +// Nb: first two fields must match core's VgHashNode. +typedef + struct _HP_Chunk { + struct _HP_Chunk* next; + Addr data; // Ptr to actual block + UInt size; // Size requested + XPt* where; // Where allocated; bottom-XPt + } + HP_Chunk; + +/*------------------------------------------------------------*/ +/*--- Profiling events ---*/ +/*------------------------------------------------------------*/ + +typedef + enum { + VgpGetXPt = VgpFini+1, + VgpGetXPtSearch, + VgpCensus, + VgpCensusHeap, + VgpCensusSnapshot, + VgpCensusTreeSize, + VgpUpdateXCon, + VgpCalcSpacetime2, + VgpPrintHp, + VgpPrintXPts, + } + VgpSkinCC; + +/*------------------------------------------------------------*/ +/*--- Statistics ---*/ +/*------------------------------------------------------------*/ + +// Konqueror startup, to give an idea of the numbers involved with a biggish +// program, with default depth: +// +// depth=3 depth=40 +// - 310,000 allocations +// - 300,000 frees +// - 15,000 XPts 800,000 XPts +// - 1,800 top-XPts + +static UInt n_xpts = 0; +static UInt n_bot_xpts = 0; +static UInt n_allocs = 0; +static UInt n_zero_allocs = 0; +static UInt n_frees = 0; +static UInt n_children_reallocs = 0; +static UInt n_snapshot_frees = 0; + +static UInt n_halvings = 0; +static UInt n_real_censi = 0; +static UInt n_fake_censi = 0; +static UInt n_attempted_censi = 0; + +/*------------------------------------------------------------*/ +/*--- Globals ---*/ +/*------------------------------------------------------------*/ + +#define FILENAME_LEN 256 + +#define SPRINTF(zz_buf, fmt, args...) \ + do { Int len = VG_(sprintf)(zz_buf, fmt, ## args); \ + VG_(write)(fd, (void*)zz_buf, len); \ + } while (0) + +#define BUF_LEN 1024 // general purpose +static Char buf [BUF_LEN]; +static Char buf2[BUF_LEN]; +static Char buf3[BUF_LEN]; + +static UInt sigstacks_space = 0; // Current signal stacks space sum + +static VgHashTable malloc_list = NULL; // HP_Chunks + +static UInt n_heap_blocks = 0; + + +#define MAX_ALLOC_FNS 32 // includes the builtin ones + +// First six filled in, rest should be zeroed. argc/argv-style vector. +static UInt n_alloc_fns = 8; +static Char* alloc_fns[MAX_ALLOC_FNS] = { + "malloc", + "operator new(unsigned)", + "operator new[](unsigned)", + "__builtin_new", + "__builtin_vec_new", + "calloc", + "realloc", + "my_malloc", // from vg_libpthread.c +}; + + +/*------------------------------------------------------------*/ +/*--- Command line args ---*/ +/*------------------------------------------------------------*/ + +#define MAX_DEPTH 50 + +typedef + enum { + XText, XHTML, + } + XFormat; + +static Bool clo_heap = True; +static UInt clo_heap_admin = 8; +static Bool clo_stacks = True; +static Bool clo_depth = 3; +static XFormat clo_format = XText; + +Bool SK_(process_cmd_line_option)(Char* arg) +{ + if (VG_CLO_STREQ(arg, "--heap=yes")) + clo_heap = True; + else if (VG_CLO_STREQ(arg, "--heap=no")) + clo_heap = False; + + else if (VG_CLO_STREQN(13, arg, "--heap-admin=")) { + clo_heap_admin = (Int)VG_(atoll)(&arg[13]); + if (clo_heap_admin > 100) { + VG_(message)(Vg_UserMsg, + "Admin size for heap blocks too large"); + VG_(bad_option)(arg); + } + } + + else if (VG_CLO_STREQ(arg, "--stacks=yes")) + clo_stacks = True; + else if (VG_CLO_STREQ(arg, "--stacks=no")) + clo_stacks = False; + + else if (VG_CLO_STREQN(8, arg, "--depth=")) { + clo_depth = (Int)VG_(atoll)(&arg[8]); + if (clo_depth < 1) clo_depth = 1; + if (clo_depth >= MAX_DEPTH) clo_depth = MAX_DEPTH; + } + + else if (VG_CLO_STREQN(11, arg, "--alloc-fn=")) { + alloc_fns[n_alloc_fns] = & arg[11]; + n_alloc_fns++; + if (n_alloc_fns >= MAX_ALLOC_FNS) { + VG_(printf)("Too many alloc functions specified, sorry"); + VG_(bad_option)(arg); + } + } + + else if (VG_CLO_STREQ(arg, "--format=text")) + clo_format = XText; + else if (VG_CLO_STREQ(arg, "--format=html")) + clo_format = XHTML; + + else + return VG_(replacement_malloc_process_cmd_line_option)(arg); + + return True; +} + +void SK_(print_usage)(void) +{ + VG_(printf)( +" --heap=no|yes profile heap blocks [yes]\n" +" --heap-admin= average admin bytes per heap block [8]\n" +" --stacks=no|yes profile stack(s) [yes]\n" +" --depth= depth of contexts [3]\n" +" --alloc-fn= specify as an alloc function [empty]\n" +" --format=text|html format of textual output [text]\n" + ); + VG_(replacement_malloc_print_usage)(); +} + +void SK_(print_debug_usage)(void) +{ + VG_(replacement_malloc_print_debug_usage)(); +} + +/*------------------------------------------------------------*/ +/*--- Execution contexts ---*/ +/*------------------------------------------------------------*/ + +// Fake XPt representing all allocation functions like malloc(). Acts as +// parent node to all top-XPts. +static XPt* alloc_xpt; + +// Cheap allocation for blocks that never need to be freed. Saves about 10% +// for Konqueror startup with --depth=40. +static void* perm_malloc(UInt n_bytes) +{ + static Addr hp = 0; // current heap pointer + static Addr hp_lim = 0; // maximum usable byte in current block + + #define SUPERBLOCK_SIZE (1 << 20) // 1 MB + + if (hp + n_bytes > hp_lim) { + hp = (Addr)VG_(get_memory_from_mmap)(SUPERBLOCK_SIZE, "perm_malloc"); + hp_lim = hp + SUPERBLOCK_SIZE - 1; + } + + hp += n_bytes; + + return (void*)(hp - n_bytes); +} + + + +static XPt* new_XPt(Addr eip, XPt* parent, Bool is_bottom) +{ + XPt* xpt = perm_malloc(sizeof(XPt)); + xpt->eip = eip; + + xpt->curr_space = 0; + xpt->spacetime = 0; + xpt->spacetime2 = 0; + + xpt->parent = parent; + sk_assert(parent == NULL || 0xffffffff != parent->eip); + + xpt->n_children = 0; + + // If a bottom-XPt, don't allocate space for children. This can be 50% + // or more, although it tends to drop as --depth increases (eg. 10% for + // konqueror with --depth=20). + if ( is_bottom ) { + xpt->max_children = 0; + xpt->children = NULL; + n_bot_xpts++; + } else { + xpt->max_children = 4; + xpt->children = VG_(malloc)( xpt->max_children * sizeof(XPt*) ); + } + + // Update statistics + n_xpts++; + + return xpt; +} + +static Bool is_alloc_fn(Addr eip) +{ + Int i; + + if ( VG_(get_fnname)(eip, buf, BUF_LEN) ) { + for (i = 0; i < n_alloc_fns; i++) { + if (VG_STREQ(buf, alloc_fns[i])) + return True; + } + } + return False; +} + +// Returns an XCon, from the bottom-XPt. Nb: the XPt returned must be a +// bottom-XPt now and must always remain a bottom-XPt. We go to some effort +// to ensure this in certain cases. See comments below. +static XPt* get_XCon( ThreadId tid, Bool custom_malloc ) +{ + // Static to minimise stack size. +1 for added 0xffffffff %eip. + static Addr eips[MAX_DEPTH + MAX_ALLOC_FNS + 1]; + + XPt* xpt = alloc_xpt; + UInt n_eips, L, A, B, nC; + UInt overestimate; + Bool reached_bottom; + + VGP_PUSHCC(VgpGetXPt); + + // Want at least clo_depth non-alloc-fn entries in the snapshot. + // However, because we have 1 or more (an unknown number, at this point) + // alloc-fns ignored, we overestimate the size needed for the stack + // snapshot. Then, if necessary, we repeatedly increase the size until + // it is enough. + overestimate = 2; + while (True) { + n_eips = VG_(stack_snapshot)( tid, eips, clo_depth + overestimate ); + + // Now we add a dummy "unknown" %eip at the end. This is only used if we + // run out of %eips before hitting clo_depth. It's done to ensure the + // XPt we return is (now and forever) a bottom-XPt. If the returned XPt + // wasn't a bottom-XPt (now or later) it would cause problems later (eg. + // the parent's spacetime wouldn't be equal to the total of the + // childrens' spacetimes). + eips[ n_eips++ ] = 0xffffffff; + + // Skip over alloc functions in eips[]. + for (L = 0; is_alloc_fn(eips[L]) && L < n_eips; L++) { } + + // Must be at least one alloc function, unless client used + // MALLOCLIKE_BLOCK + if (!custom_malloc) sk_assert(L > 0); + + // Should be at least one non-alloc function. If not, try again. + if (L == n_eips) { + overestimate += 2; + if (overestimate > MAX_ALLOC_FNS) + VG_(skin_panic)("No stk snapshot big enough to find non-alloc fns"); + } else { + break; + } + } + A = L; + B = n_eips - 1; + reached_bottom = False; + + // By this point, the eips we care about are in eips[A]..eips[B] + + // Now do the search/insertion of the XCon. 'L' is the loop counter, + // being the index into eips[]. + while (True) { + // Look for %eip in xpt's children. + // XXX: linear search, ugh -- about 10% of time for konqueror startup + // XXX: tried cacheing last result, only hit about 4% for konqueror + // Nb: this search hits about 98% of the time for konqueror + VGP_PUSHCC(VgpGetXPtSearch); + + // If we've searched/added deep enough, or run out of EIPs, this is + // the bottom XPt. + if (L - A + 1 == clo_depth || L == B) + reached_bottom = True; + + nC = 0; + while (True) { + if (nC == xpt->n_children) { + // not found, insert new XPt + sk_assert(xpt->max_children != 0); + sk_assert(xpt->n_children <= xpt->max_children); + // Expand 'children' if necessary + if (xpt->n_children == xpt->max_children) { + xpt->max_children *= 2; + xpt->children = VG_(realloc)( xpt->children, + xpt->max_children * sizeof(XPt*) ); + n_children_reallocs++; + } + // Make new XPt for %eip, insert in list + xpt->children[ xpt->n_children++ ] = + new_XPt(eips[L], xpt, reached_bottom); + break; + } + if (eips[L] == xpt->children[nC]->eip) break; // found the %eip + nC++; // keep looking + } + VGP_POPCC(VgpGetXPtSearch); + + // Return found/built bottom-XPt. + if (reached_bottom) { + sk_assert(0 == xpt->children[nC]->n_children); // Must be bottom-XPt + VGP_POPCC(VgpGetXPt); + return xpt->children[nC]; + } + + // Descend to next level in XTree, the newly found/built non-bottom-XPt + xpt = xpt->children[nC]; + L++; + } +} + +// Update 'curr_space' of every XPt in the XCon, by percolating upwards. +static void update_XCon(XPt* xpt, Int space_delta) +{ + VGP_PUSHCC(VgpUpdateXCon); + + sk_assert(True == clo_heap); + sk_assert(0 != space_delta); + sk_assert(NULL != xpt); + sk_assert(0 == xpt->n_children); // must be bottom-XPt + + while (xpt != alloc_xpt) { + if (space_delta < 0) sk_assert(xpt->curr_space >= -space_delta); + xpt->curr_space += space_delta; + xpt = xpt->parent; + } + if (space_delta < 0) sk_assert(alloc_xpt->curr_space >= -space_delta); + alloc_xpt->curr_space += space_delta; + + VGP_POPCC(VgpUpdateXCon); +} + +// Actually want a reverse sort, biggest to smallest +static Int XPt_cmp_spacetime(void* n1, void* n2) +{ + XPt* xpt1 = *(XPt**)n1; + XPt* xpt2 = *(XPt**)n2; + return (xpt1->spacetime < xpt2->spacetime ? 1 : -1); +} + +static Int XPt_cmp_spacetime2(void* n1, void* n2) +{ + XPt* xpt1 = *(XPt**)n1; + XPt* xpt2 = *(XPt**)n2; + return (xpt1->spacetime2 < xpt2->spacetime2 ? 1 : -1); +} + + +/*------------------------------------------------------------*/ +/*--- A generic Queue ---*/ +/*------------------------------------------------------------*/ + +typedef + struct { + UInt head; // Index of first entry + UInt tail; // Index of final+1 entry, ie. next free slot + UInt max_elems; + void** elems; + } + Queue; + +static Queue* construct_queue(UInt size) +{ + UInt i; + Queue* q = VG_(malloc)(sizeof(Queue)); + q->head = 0; + q->tail = 0; + q->max_elems = size; + q->elems = VG_(malloc)(size * sizeof(void*)); + for (i = 0; i < size; i++) + q->elems[i] = NULL; + + return q; +} + +static void destruct_queue(Queue* q) +{ + VG_(free)(q->elems); + VG_(free)(q); +} + +static void shuffle(Queue* dest_q, void** old_elems) +{ + UInt i, j; + for (i = 0, j = dest_q->head; j < dest_q->tail; i++, j++) + dest_q->elems[i] = old_elems[j]; + dest_q->head = 0; + dest_q->tail = i; + for ( ; i < dest_q->max_elems; i++) + dest_q->elems[i] = NULL; // paranoia +} + +// Shuffles elements down. If not enough slots free, increase size. (We +// don't wait until we've completely run out of space, because there could +// be lots of shuffling just before that point which would be slow.) +static void adjust(Queue* q) +{ + void** old_elems; + + sk_assert(q->tail == q->max_elems); + if (q->head < 10) { + old_elems = q->elems; + q->max_elems *= 2; + q->elems = VG_(malloc)(q->max_elems * sizeof(void*)); + shuffle(q, old_elems); + VG_(free)(old_elems); + } else { + shuffle(q, q->elems); + } +} + +static void enqueue(Queue* q, void* elem) +{ + if (q->tail == q->max_elems) + adjust(q); + q->elems[q->tail++] = elem; +} + +static Bool is_empty_queue(Queue* q) +{ + return (q->head == q->tail); +} + +static void* dequeue(Queue* q) +{ + if (is_empty_queue(q)) + return NULL; // Queue empty + else + return q->elems[q->head++]; +} + +/*------------------------------------------------------------*/ +/*--- malloc() et al replacement wrappers ---*/ +/*------------------------------------------------------------*/ + +static __inline__ +void add_HP_Chunk(HP_Chunk* hc) +{ + n_heap_blocks++; + VG_(HT_add_node) ( malloc_list, (VgHashNode*)hc ); +} + +static __inline__ +HP_Chunk* get_HP_Chunk(void* p, HP_Chunk*** prev_chunks_next_ptr) +{ + return (HP_Chunk*)VG_(HT_get_node) ( malloc_list, (UInt)p, + (VgHashNode***)prev_chunks_next_ptr ); +} + +static __inline__ +void remove_HP_Chunk(HP_Chunk* hc, HP_Chunk** prev_chunks_next_ptr) +{ + sk_assert(n_heap_blocks > 0); + n_heap_blocks--; + *prev_chunks_next_ptr = hc->next; +} + +// Forward declaration +static void hp_census(void); + +static __inline__ +void new_block_meta ( void* p, Int size, Bool custom_malloc ) +{ + HP_Chunk* hc; + + VGP_PUSHCC(VgpCliMalloc); + + if (0 == size) n_zero_allocs++; + + // Make new HP_Chunk node, add to malloclist + hc = VG_(malloc)(sizeof(HP_Chunk)); + hc->size = size; + hc->data = (Addr)p; + + if (clo_heap) { + hc->where = get_XCon( VG_(get_current_or_recent_tid)(), custom_malloc ); + if (size != 0) + update_XCon(hc->where, size); + } else { + hc->where = NULL; // paranoia + } + + add_HP_Chunk( hc ); + + hp_census(); // do a census! + + VGP_POPCC(VgpCliMalloc); +} + +static __inline__ +void* new_block ( Int size, UInt align, Bool is_zeroed ) +{ + void* p; + + if (size < 0) return NULL; + + VGP_PUSHCC(VgpCliMalloc); + + // Update statistics + n_allocs++; + + p = VG_(cli_malloc)( align, size ); + if (is_zeroed) VG_(memset)(p, 0, size); + new_block_meta(p, size, /*custom_malloc*/False); + + VGP_POPCC(VgpCliMalloc); + return p; +} + +static __inline__ +void die_block ( void* p, Bool custom_free ) +{ + HP_Chunk* hc; + HP_Chunk** remove_handle; + + VGP_PUSHCC(VgpCliMalloc); + + // Update statistics + n_frees++; + + hc = get_HP_Chunk ( p, &remove_handle ); + if (hc == NULL) + return; // must have been a bogus free(), or p==NULL + + sk_assert(hc->data == (Addr)p); + + if (clo_heap && hc->size != 0) + update_XCon(hc->where, -hc->size); + + // Actually free the heap block + if (!custom_free) + VG_(cli_free)( p ); + + // Remove HP_Chunk from malloclist, destroy + remove_HP_Chunk(hc, remove_handle); + + hp_census(); // do a census! + + VG_(free)( hc ); + VGP_POPCC(VgpCliMalloc); +} + + +void* SK_(malloc) ( Int n ) +{ + return new_block( n, VG_(clo_alignment), /*is_zeroed*/False ); +} + +void* SK_(__builtin_new) ( Int n ) +{ + return new_block( n, VG_(clo_alignment), /*is_zeroed*/False ); +} + +void* SK_(__builtin_vec_new) ( Int n ) +{ + return new_block( n, VG_(clo_alignment), /*is_zeroed*/False ); +} + +void* SK_(calloc) ( Int m, Int size ) +{ + return new_block( m*size, VG_(clo_alignment), /*is_zeroed*/True ); +} + +void SK_(free) ( void* p ) +{ + die_block( p, /*custom_free*/False ); +} + +void SK_(__builtin_delete) ( void* p ) +{ + die_block( p, /*custom_free*/False); +} + +void SK_(__builtin_vec_delete) ( void* p ) +{ + die_block( p, /*custom_free*/False ); +} + +void* SK_(realloc) ( void* p_old, Int new_size ) +{ + HP_Chunk* hc; + HP_Chunk** remove_handle; + Int i; + void* p_new; + UInt old_size; + XPt *old_where, *new_where; + + VGP_PUSHCC(VgpCliMalloc); + + // First try and find the block. + hc = get_HP_Chunk ( p_old, &remove_handle ); + if (hc == NULL) { + VGP_POPCC(VgpCliMalloc); + return NULL; // must have been a bogus free() + } + + sk_assert(hc->data == (Addr)p_old); + old_size = hc->size; + + if (new_size <= old_size) { + // new size is smaller or same; block not moved + p_new = p_old; + + } else { + // new size is bigger; make new block, copy shared contents, free old + p_new = VG_(cli_malloc)(VG_(clo_alignment), new_size); + + for (i = 0; i < old_size; i++) + ((UChar*)p_new)[i] = ((UChar*)p_old)[i]; + + VG_(cli_free)(p_old); + } + + old_where = hc->where; + new_where = get_XCon( VG_(get_current_or_recent_tid)(), + /*custom_malloc*/False); + + // Update HP_Chunk + hc->data = (Addr)p_new; + hc->size = new_size; + hc->where = new_where; + + // Update XPt curr_space fields + if (clo_heap) { + if (0 != old_size) update_XCon(old_where, -old_size); + if (0 != new_size) update_XCon(new_where, new_size); + } + + // If block has moved, have to remove and reinsert in the malloclist + // (since the updated 'data' field is the hash lookup key). + if (p_new != p_old) { + remove_HP_Chunk(hc, remove_handle); + add_HP_Chunk(hc); + } + + VGP_POPCC(VgpCliMalloc); + return p_new; +} + + +/*------------------------------------------------------------*/ +/*--- Taking a census ---*/ +/*------------------------------------------------------------*/ + +static Census censi[MAX_N_CENSI]; +static UInt curr_census = 0; + +// Must return False so that all stacks are traversed +static UInt count_stack_size_counter; +static Bool count_stack_size( Addr stack_min, Addr stack_max ) +{ + count_stack_size_counter += (stack_max - stack_min); + return False; +} + +static UInt get_xtree_size(XPt* xpt, UInt ix) +{ + UInt i; + +// VG_(printf)("%4d ", xpt->curr_space); + + // If this one has size zero, all the children will be size zero too, so + // nothing interesting to record. +// if (0 != xpt->curr_space || 0 == ix) { + if (xpt->curr_space / (double)alloc_xpt->curr_space > 0.002 || 0 == ix) { + ix++; + + // Count all (non-zero) descendent XPts + for (i = 0; i < xpt->n_children; i++) + ix = get_xtree_size(xpt->children[i], ix); + } + return ix; +} + +static +UInt do_space_snapshot(XPt xpt[], XTreeSnapshot xtree_snapshot, UInt ix) +{ + UInt i; + + // Snapshot this XPt, if non-zero space, or the first one +// if (0 != xpt->curr_space || 0 == ix) { + if (xpt->curr_space / (double)alloc_xpt->curr_space > 0.002 || 0 == ix) { + xtree_snapshot[ix].xpt = xpt; + xtree_snapshot[ix].space = xpt->curr_space; + ix++; + + // Snapshot all (non-zero) descendent XPts + for (i = 0; i < xpt->n_children; i++) + ix = do_space_snapshot(xpt->children[i], xtree_snapshot, ix); + } + return ix; +} + +static UInt ms_interval; +static UInt do_every_nth_census = 30; + +// Weed out half the censi; we choose those that represent the smallest +// time-spans, because that loses the least information. +// +// Algorithm for N censi: We find the census representing the smallest +// timeframe, and remove it. We repeat this until (N/2)-1 censi are gone. +// (It's (N/2)-1 because we never remove the first and last censi.) +// We have to do this one census at a time, rather than finding the (N/2)-1 +// smallest censi in one hit, because when a census is removed, it's +// neighbours immediately cover greater timespans. So it's N^2, but N only +// equals 200, and this is only done every 100 censi, which is not too often. +static void halve_censi(void) +{ + Int i, jp, j, jn, k; + Census* min_census; + + n_halvings++; + if (VG_(clo_verbosity) > 1) + VG_(message)(Vg_UserMsg, "Halving censi..."); + + // Sets j to the index of the first not-yet-removed census at or after i + #define FIND_CENSUS(i, j) \ + for (j = i; -1 == censi[j].ms_time; j++) { } + + for (i = 2; i < MAX_N_CENSI; i += 2) { + // Find the censi representing the smallest timespan. The timespan + // for census n = d(N-1,N)+d(N,N+1), where d(A,B) is the time between + // censi A and B. We don't consider the first and last censi for + // removal. + Int min_span = 0x7fffffff; + Int min_j = 0; + + // Initial triple: (prev, curr, next) == (jp, j, jn) + jp = 0; + FIND_CENSUS(1, j); + FIND_CENSUS(j+1, jn); + while (jn < MAX_N_CENSI) { + Int timespan = censi[jn].ms_time - censi[jp].ms_time; + sk_assert(timespan >= 0); + if (timespan < min_span) { + min_span = timespan; + min_j = j; + } + // Move on to next triple + jp = j; + j = jn; + FIND_CENSUS(jn+1, jn); + } + // We've found the least important census, now remove it + min_census = & censi[ min_j ]; + for (k = 0; NULL != min_census->xtree_snapshots[k]; k++) { + n_snapshot_frees++; + VG_(free)(min_census->xtree_snapshots[k]); + min_census->xtree_snapshots[k] = NULL; + } + min_census->ms_time = -1; + } + + // Slide down the remaining censi over the removed ones. The '<=' is + // because we are removing on (N/2)-1, rather than N/2. + for (i = 0, j = 0; i <= MAX_N_CENSI / 2; i++, j++) { + FIND_CENSUS(j, j); + if (i != j) { + censi[i] = censi[j]; + } + } + curr_census = i; + + // Double intervals + ms_interval *= 2; + do_every_nth_census *= 2; + + if (VG_(clo_verbosity) > 1) + VG_(message)(Vg_UserMsg, "...done"); +} + +// Take a census. Census time seems to be insignificant (usually <= 0 ms, +// almost always <= 1ms) so don't have to worry about subtracting it from +// running time in any way. +// +// XXX: NOT TRUE! with bigger depths, konqueror censuses can easily take +// 50ms! +static void hp_census(void) +{ + static UInt ms_prev_census = 0; + static UInt ms_next_census = 0; // zero allows startup census + + Int ms_time, ms_time_since_prev; + Int i, K; + Census* census; + + VGP_PUSHCC(VgpCensus); + + // Only do a census if it's time + ms_time = VG_(read_millisecond_timer)(); + ms_time_since_prev = ms_time - ms_prev_census; + if (ms_time < ms_next_census) { + n_fake_censi++; + VGP_POPCC(VgpCensus); + return; + } + n_real_censi++; + + census = & censi[curr_census]; + + census->ms_time = ms_time; + + // Heap: snapshot the K most significant XTrees ------------------- + if (clo_heap) { + K = ( alloc_xpt->n_children < MAX_SNAPSHOTS + ? alloc_xpt->n_children + : MAX_SNAPSHOTS); // max out + + // Update .spacetime field (approximatively) for all top-XPts. + // We *do not* do it for any non-top-XPTs. + for (i = 0; i < alloc_xpt->n_children; i++) { + XPt* top_XPt = alloc_xpt->children[i]; + top_XPt->spacetime += top_XPt->curr_space * ms_time_since_prev; + } + // Sort top-XPts by spacetime2 field. + VG_(ssort)(alloc_xpt->children, alloc_xpt->n_children, sizeof(XPt*), + XPt_cmp_spacetime); + + VGP_PUSHCC(VgpCensusHeap); + + // For each significant top-level XPt, record space info about its + // entire XTree, in a single census entry. + // Nb: the xtree_size count/snapshot buffer allocation, and the actual + // snapshot, take similar amounts of time (measured with the + // millesecond counter). + for (i = 0; i < K; i++) { + UInt xtree_size, xtree_size2; +// VG_(printf)("%7u ", alloc_xpt->children[i]->spacetime); + // Count how many XPts are in the XTree; make array of that size + // (+1 for zero termination, which calloc() does for us). + VGP_PUSHCC(VgpCensusTreeSize); + xtree_size = get_xtree_size( alloc_xpt->children[i], 0 ); + VGP_POPCC(VgpCensusTreeSize); + census->xtree_snapshots[i] = + VG_(calloc)(xtree_size+1, sizeof(XPtSnapshot)); + if (VG_(clo_verbosity) > 1) + VG_(printf)("calloc: %d (%d B)\n", xtree_size+1, + (xtree_size+1) * sizeof(XPtSnapshot)); + + // Take space-snapshot: copy 'curr_space' for every XPt in the + // XTree into the snapshot array, along with pointers to the XPts. + // (Except for ones with curr_space==0, which wouldn't contribute + // to the final spacetime2 calculation anyway; excluding them + // saves a lot of memory and up to 40% time with big --depth valus. + VGP_PUSHCC(VgpCensusSnapshot); + xtree_size2 = do_space_snapshot(alloc_xpt->children[i], + census->xtree_snapshots[i], 0); + sk_assert(xtree_size == xtree_size2); + VGP_POPCC(VgpCensusSnapshot); + } +// VG_(printf)("\n\n"); + // Zero-terminate 'xtree_snapshot' array + census->xtree_snapshots[i] = NULL; + + VGP_POPCC(VgpCensusHeap); + + //VG_(printf)("printed %d censi\n", K); + + // Lump the rest into a single "others" entry. + census->others_space = 0; + for (i = K; i < alloc_xpt->n_children; i++) { + census->others_space += alloc_xpt->children[i]->curr_space; + } + } + + // Heap admin ------------------------------------------------------- + if (clo_heap_admin > 0) + census->heap_admin_space = clo_heap_admin * n_heap_blocks; + + // Stack(s) --------------------------------------------------------- + if (clo_stacks) { + count_stack_size_counter = sigstacks_space; + // slightly abusing this function + VG_(first_matching_thread_stack)( count_stack_size ); + census->stacks_space = count_stack_size_counter; + i++; + } + + // Finish, update interval if necessary ----------------------------- + curr_census++; + census = NULL; // don't use again now that curr_census changed + + // Halve the entries, if our census table is full + if (MAX_N_CENSI == curr_census) { + halve_censi(); + } + + // Take time for next census from now, rather than when this census + // should have happened. Because, if there's a big gap due to a kernel + // operation, there's no point doing catch-up censi every BB for a while + // -- that would just give N censi at almost the same time. + if (VG_(clo_verbosity) > 1) { + VG_(message)(Vg_UserMsg, "census: %d ms (took %d ms)", ms_time, + VG_(read_millisecond_timer)() - ms_time ); + } + ms_prev_census = ms_time; + ms_next_census = ms_time + ms_interval; + //ms_next_census += ms_interval; + + //VG_(printf)("Next: %d ms\n", ms_next_census); + + VGP_POPCC(VgpCensus); +} + +/*------------------------------------------------------------*/ +/*--- Tracked events ---*/ +/*------------------------------------------------------------*/ + +static void new_mem_stack_signal(Addr a, UInt len) +{ + sigstacks_space += len; +} + +static void die_mem_stack_signal(Addr a, UInt len) +{ + sk_assert(sigstacks_space >= len); + sigstacks_space -= len; +} + +/*------------------------------------------------------------*/ +/*--- Client Requests ---*/ +/*------------------------------------------------------------*/ + +Bool SK_(handle_client_request) ( ThreadId tid, UInt* argv, UInt* ret ) +{ + switch (argv[0]) { + case VG_USERREQ__MALLOCLIKE_BLOCK: { + void* p = (void*)argv[1]; + UInt sizeB = argv[2]; + *ret = 0; + new_block_meta( p, sizeB, /*custom_malloc*/True ); + return True; + } + case VG_USERREQ__FREELIKE_BLOCK: { + void* p = (void*)argv[1]; + *ret = 0; + die_block( p, /*custom_free*/True ); + return True; + } + default: + *ret = 0; + return False; + } +} + +/*------------------------------------------------------------*/ +/*--- Initialisation ---*/ +/*------------------------------------------------------------*/ + +// Current directory at startup. +static Char* base_dir; + +UInt VG_(vg_malloc_redzone_szB) = 0; + +void SK_(pre_clo_init)() +{ + VG_(details_name) ("Massif"); + VG_(details_version) ("0.0.3"); + VG_(details_description) ("a space profiler"); + VG_(details_copyright_author)("Copyright (C) 2003, Nicholas Nethercote"); + VG_(details_bug_reports_to) ("njn25@cam.ac.uk"); + + // Needs + VG_(needs_libc_freeres)(); + VG_(needs_command_line_options)(); + VG_(needs_client_requests) (); + + // Events to track + VG_(init_new_mem_stack_signal) ( new_mem_stack_signal ); + VG_(init_die_mem_stack_signal) ( die_mem_stack_signal ); + + // Profiling events + VGP_(register_profile_event)(VgpGetXPt, "get-XPt"); + VGP_(register_profile_event)(VgpGetXPtSearch, "get-XPt-search"); + VGP_(register_profile_event)(VgpCensus, "census"); + VGP_(register_profile_event)(VgpCensusHeap, "census-heap"); + VGP_(register_profile_event)(VgpCensusSnapshot, "census-snapshot"); + VGP_(register_profile_event)(VgpCensusTreeSize, "census-treesize"); + VGP_(register_profile_event)(VgpUpdateXCon, "update-XCon"); + VGP_(register_profile_event)(VgpCalcSpacetime2, "calc-spacetime2"); + VGP_(register_profile_event)(VgpPrintHp, "print-hp"); + VGP_(register_profile_event)(VgpPrintXPts, "print-XPts"); + + // HP_Chunks + malloc_list = VG_(HT_construct)(); + + // Dummy node at top of the context structure. + alloc_xpt = new_XPt(0, NULL, /*is_bottom*/False); + + sk_assert( VG_(getcwd_alloc)(&base_dir) ); +} + +void SK_(post_clo_init)(void) +{ + ms_interval = 1; + + // Do an initial sample for t = 0 + hp_census(); +} + +/*------------------------------------------------------------*/ +/*--- Instrumentation ---*/ +/*------------------------------------------------------------*/ + +UCodeBlock* SK_(instrument)(UCodeBlock* cb_in, Addr orig_addr) +{ + return cb_in; +} + +/*------------------------------------------------------------*/ +/*--- Spacetime recomputation ---*/ +/*------------------------------------------------------------*/ + +// Although we've been calculating spacetime along the way, because the +// earlier calculations were done at a finer timescale, the .spacetime field +// might not agree with what hp2ps sees, because we've thrown away some of +// the information. So recompute it at the scale that hp2ps sees, so we can +// confidently determine which contexts hp2ps will choose for displaying as +// distinct bands. This recomputation only happens to the significant ones +// that get printed in the .hp file, so it's cheap. +// +// The spacetime calculation: +// ( a[0]*d(0,1) + a[1]*(d(0,1) + d(1,2)) + ... + a[N-1]*d(N-2,N-1) ) / 2 +// where +// a[N] is the space at census N +// d(A,B) is the time interval between censi A and B +// and +// d(A,B) + d(B,C) == d(A,C) +// +// Key point: we can calculate the area for a census without knowing the +// previous or subsequent censi's space; because any over/underestimates +// for this census will be reversed in the next, balancing out. This is +// important, as getting the previous/next census entry for a particular +// AP is a pain with this data structure, but getting the prev/next +// census time is easy. +// +// Each heap calculation gets added to its context's spacetime2 field. +// The ULong* values are all running totals, hence the use of "+=" everywhere. + +// This does the calculations for a single census. +static void calc_spacetime2b(Census* census, UInt d_t1_t2, + ULong* twice_heap_ST, + ULong* twice_heap_admin_ST, + ULong* twice_stack_ST) +{ + UInt i, j; + XPtSnapshot* xpt_snapshot; + + // Heap -------------------------------------------------------- + if (clo_heap) { + for (i = 0; NULL != census->xtree_snapshots[i]; i++) { + // Compute total heap spacetime2 for the entire XTree using only the + // top-XPt (the first XPt in xtree_snapshot). + *twice_heap_ST += d_t1_t2 * census->xtree_snapshots[i][0].space; + + // Increment spacetime2 for every XPt in xtree_snapshot (inc. top one) + for (j = 0; NULL != census->xtree_snapshots[i][j].xpt; j++) { + xpt_snapshot = & census->xtree_snapshots[i][j]; + xpt_snapshot->xpt->spacetime2 += d_t1_t2 * xpt_snapshot->space; + } + } + *twice_heap_ST += d_t1_t2 * census->others_space; + } + + // Heap admin -------------------------------------------------- + if (clo_heap_admin > 0) + *twice_heap_admin_ST += d_t1_t2 * census->heap_admin_space; + + // Stack(s) ---------------------------------------------------- + if (clo_stacks) + *twice_stack_ST += d_t1_t2 * census->stacks_space; +} + +// This does the calculations for all censi. +static void calc_spacetime2(ULong* heap2, ULong* heap_admin2, ULong* stack2) +{ + UInt i, N = curr_census; + + VGP_PUSHCC(VgpCalcSpacetime2); + + *heap2 = 0; + *heap_admin2 = 0; + *stack2 = 0; + + if (N <= 1) + return; + + calc_spacetime2b( &censi[0], censi[1].ms_time - censi[0].ms_time, + heap2, heap_admin2, stack2 ); + + for (i = 1; i <= N-2; i++) { + calc_spacetime2b( & censi[i], censi[i+1].ms_time - censi[i-1].ms_time, + heap2, heap_admin2, stack2 ); + } + + calc_spacetime2b( & censi[N-1], censi[N-1].ms_time - censi[N-2].ms_time, + heap2, heap_admin2, stack2 ); + // Now get rid of the halves. May lose a 0.5 on each, doesn't matter. + *heap2 /= 2; + *heap_admin2 /= 2; + *stack2 /= 2; + + VGP_POPCC(VgpCalcSpacetime2); +} + +/*------------------------------------------------------------*/ +/*--- Writing the graph file ---*/ +/*------------------------------------------------------------*/ + +static Char* make_filename(Char* dir, Char* suffix) +{ + Char* filename; + + /* Block is big enough for dir name + massif.. */ + filename = VG_(malloc)((VG_(strlen)(dir) + 32)*sizeof(Char)); + VG_(sprintf)(filename, "%s/massif.%d%s", dir, VG_(getpid)(), suffix); + + return filename; +} + +// Make string acceptable to hp2ps (sigh): remove spaces, escape parentheses. +static Char* clean_fnname(Char *d, Char* s) +{ + Char* dorig = d; + while (*s) { + if (' ' == *s) { *d = '%'; } + else if ('(' == *s) { *d++ = '\\'; *d = '('; } + else if (')' == *s) { *d++ = '\\'; *d = ')'; } + else { *d = *s; }; + s++; + d++; + } + *d = '\0'; + return dorig; +} + +static void file_err ( Char* file ) +{ + VG_(message)(Vg_UserMsg, "error: can't open output file `%s'", file ); + VG_(message)(Vg_UserMsg, " ... so profile results will be missing."); +} + +/* Format, by example: + + JOB "a.out -p" + DATE "Fri Apr 17 11:43:45 1992" + SAMPLE_UNIT "seconds" + VALUE_UNIT "bytes" + BEGIN_SAMPLE 0.00 + SYSTEM 24 + END_SAMPLE 0.00 + BEGIN_SAMPLE 1.00 + elim 180 + insert 24 + intersect 12 + disin 60 + main 12 + reduce 20 + SYSTEM 12 + END_SAMPLE 1.00 + MARK 1.50 + MARK 1.75 + MARK 1.80 + BEGIN_SAMPLE 2.00 + elim 192 + insert 24 + intersect 12 + disin 84 + main 12 + SYSTEM 24 + END_SAMPLE 2.00 + BEGIN_SAMPLE 2.82 + END_SAMPLE 2.82 + */ +static void write_hp_file(void) +{ + Int i, j; + Int fd, res; + Char *hp_file, *ps_file, *aux_file; + Char* cmdfmt; + Char* cmdbuf; + Int cmdlen; + + VGP_PUSHCC(VgpPrintHp); + + // Open file + hp_file = make_filename( base_dir, ".hp" ); + ps_file = make_filename( base_dir, ".ps" ); + aux_file = make_filename( base_dir, ".aux" ); + fd = VG_(open)(hp_file, VKI_O_CREAT|VKI_O_TRUNC|VKI_O_WRONLY, + VKI_S_IRUSR|VKI_S_IWUSR); + if (fd < 0) { + file_err( hp_file ); + VGP_POPCC(VgpPrintHp); + return; + } + + // File header, including command line + SPRINTF(buf, "JOB \""); + for (i = 0; i < VG_(client_argc); i++) + SPRINTF(buf, "%s ", VG_(client_argv)[i]); + SPRINTF(buf, /*" (%d ms/sample)\"\n"*/ "\"\n" + "DATE \"\"\n" + "SAMPLE_UNIT \"ms\"\n" + "VALUE_UNIT \"bytes\"\n", ms_interval); + + // Censi + for (i = 0; i < curr_census; i++) { + Census* census = & censi[i]; + + // Census start + SPRINTF(buf, "MARK %d.0\n" + "BEGIN_SAMPLE %d.0\n", + census->ms_time, census->ms_time); + + // Heap ----------------------------------------------------------- + if (clo_heap) { + // Print all the significant XPts from that census + for (j = 0; NULL != census->xtree_snapshots[j]; j++) { + // Grab the jth top-XPt + XTreeSnapshot xtree_snapshot = & census->xtree_snapshots[j][0]; + if ( ! VG_(get_fnname)(xtree_snapshot->xpt->eip, buf2, 16)) { + VG_(sprintf)(buf2, "???"); + } + SPRINTF(buf, "x%x:%s %d\n", xtree_snapshot->xpt->eip, + clean_fnname(buf3, buf2), xtree_snapshot->space); + } + + // Remaining heap block alloc points, combined + if (census->others_space > 0) + SPRINTF(buf, "other %d\n", census->others_space); + } + + // Heap admin ----------------------------------------------------- + if (clo_heap_admin > 0 && census->heap_admin_space) + SPRINTF(buf, "heap-admin %d\n", census->heap_admin_space); + + // Stack(s) ------------------------------------------------------- + if (clo_stacks) + SPRINTF(buf, "stack(s) %d\n", census->stacks_space); + + // Census end + SPRINTF(buf, "END_SAMPLE %d.0\n", census->ms_time); + } + + // Close file + sk_assert(fd >= 0); + VG_(close)(fd); + + // Attempt to convert file using hp2ps + cmdfmt = "%s/hp2ps -c -t1 %s"; + cmdlen = VG_(strlen)(VG_(libdir)) + VG_(strlen)(hp_file) + + VG_(strlen)(cmdfmt); + cmdbuf = VG_(malloc)( sizeof(Char) * cmdlen ); + VG_(sprintf)(cmdbuf, cmdfmt, VG_(libdir), hp_file); + res = VG_(system)(cmdbuf); + VG_(free)(cmdbuf); + if (res != 0) { + VG_(message)(Vg_UserMsg, + "Conversion to PostScript failed. Try converting manually."); + } else { + // remove the .hp and .aux file + VG_(unlink)(hp_file); + VG_(unlink)(aux_file); + } + + VG_(free)(hp_file); + VG_(free)(ps_file); + VG_(free)(aux_file); + + VGP_POPCC(VgpPrintHp); +} + +/*------------------------------------------------------------*/ +/*--- Writing the XPt text/HTML file ---*/ +/*------------------------------------------------------------*/ + +static void percentify(Int n, Int pow, Int field_width, char xbuf[]) +{ + int i, len, space; + + VG_(sprintf)(xbuf, "%d.%d%%", n / pow, n % pow); + len = VG_(strlen)(xbuf); + space = field_width - len; + if (space < 0) space = 0; /* Allow for v. small field_width */ + i = len; + + /* Right justify in field */ + for ( ; i >= 0; i--) xbuf[i + space] = xbuf[i]; + for (i = 0; i < space; i++) xbuf[i] = ' '; +} + +// Nb: uses a static buffer, each call trashes the last string returned. +static Char* make_perc(ULong spacetime, ULong total_spacetime) +{ + static Char mbuf[32]; + + UInt p = 10; + percentify(spacetime * 100 * p / total_spacetime, p, 5, mbuf); + return mbuf; +} + +// Nb: passed in XPt is a lower-level XPt; %eips are grabbed from +// bottom-to-top of XCon, and then printed in the reverse order. +static UInt pp_XCon(Int fd, XPt* xpt) +{ + Addr rev_eips[clo_depth+1]; + Int i = 0; + Int n = 0; + Bool is_HTML = ( XHTML == clo_format ); + Char* maybe_br = ( is_HTML ? "
" : "" ); + Char* maybe_indent = ( is_HTML ? "  " : "" ); + + sk_assert(NULL != xpt); + + while (True) { + rev_eips[i] = xpt->eip; + n++; + if (alloc_xpt == xpt->parent) break; + i++; + xpt = xpt->parent; + } + + for (i = n-1; i >= 0; i--) { + // -1 means point to calling line + VG_(describe_eip)(rev_eips[i]-1, buf2, BUF_LEN); + SPRINTF(buf, " %s%s%s\n", maybe_indent, buf2, maybe_br); + } + + return n; +} + +// Important point: for HTML, each XPt must be identified uniquely for the +// HTML links to all match up correctly. Using xpt->eip is not +// sufficient, because function pointers mean that you can call more than +// one other function from a single code location. So instead we use the +// address of the xpt struct itself, which is guaranteed to be unique. + +static void pp_all_XPts2(Int fd, Queue* q, ULong heap_spacetime, + ULong total_spacetime) +{ + UInt i; + XPt *xpt, *child; + UInt L = 0; + UInt c1 = 1; + UInt c2 = 0; + ULong sum = 0; + UInt n; + Char *eip_desc, *perc; + Bool is_HTML = ( XHTML == clo_format ); + Char* maybe_br = ( is_HTML ? "
" : "" ); + Char* maybe_p = ( is_HTML ? "

" : "" ); + Char* maybe_ul = ( is_HTML ? "

" : "" ); + Char* end_hr = ( is_HTML ? "
" : + "=================================" ); + Char* depth = ( is_HTML ? "--depth" : "--depth" ); + + SPRINTF(buf, "== %d ===========================%s\n", L, maybe_br); + + while (NULL != (xpt = (XPt*)dequeue(q))) { + // Check that non-top-level XPts have a zero .spacetime field. + if (xpt->parent != alloc_xpt) sk_assert( 0 == xpt->spacetime ); + + // Check that the sum of all children .spacetime2s equals parent's + // (unless alloc_xpt, when it should == 0). + if (alloc_xpt == xpt) { + sk_assert(0 == xpt->spacetime2); + } else { + sum = 0; + for (i = 0; i < xpt->n_children; i++) { + sum += xpt->children[i]->spacetime2; + } + //sk_assert(sum == xpt->spacetime2); + // It's possible that not all the children were included in the + // spacetime2 calculations. Hopefully almost all of them were, and + // all the important ones. +// sk_assert(sum <= xpt->spacetime2); +// sk_assert(sum * 1.05 > xpt->spacetime2 ); +// if (sum != xpt->spacetime2) { +// VG_(printf)("%ld, %ld\n", sum, xpt->spacetime2); +// } + } + + if (xpt == alloc_xpt) { + SPRINTF(buf, "Heap allocation functions accounted for " + "%s of measured spacetime%s\n", + make_perc(heap_spacetime, total_spacetime), maybe_br); + } else { + // Remember: spacetime2 is space.time *doubled* + perc = make_perc(xpt->spacetime2 / 2, total_spacetime); + if (is_HTML) { + SPRINTF(buf, "" + "Context accounted for " + "%s of measured spacetime
\n", + xpt, xpt, perc); + } else { + SPRINTF(buf, "Context accounted for %s of measured spacetime\n", + perc); + } + n = pp_XCon(fd, xpt); + sk_assert(n == L); + } + + // Sort children by spacetime2 + VG_(ssort)(xpt->children, xpt->n_children, sizeof(XPt*), + XPt_cmp_spacetime2); + + SPRINTF(buf, "%s\nCalled from:%s\n", maybe_p, maybe_ul); + for (i = 0; i < xpt->n_children; i++) { + child = xpt->children[i]; + + // Stop when <1% of total spacetime + if (child->spacetime2 * 1000 / (total_spacetime * 2) < 5) { + UInt n_insig = xpt->n_children - i; + Char* s = ( n_insig == 1 ? "" : "s" ); + Char* and = ( 0 == i ? "" : "and " ); + Char* other = ( 0 == i ? "" : "other " ); + SPRINTF(buf, " %s%s%d %sinsignificant place%s%s\n\n", + maybe_li, and, n_insig, other, s, maybe_fli); + break; + } + + // Remember: spacetime2 is space.time *doubled* + perc = make_perc(child->spacetime2 / 2, total_spacetime); + eip_desc = VG_(describe_eip)(child->eip-1, buf2, BUF_LEN); + if (is_HTML) { + SPRINTF(buf, "
  • ", child ); + + if (child->n_children > 0) { + SPRINTF(buf, "%s", child, perc); + } else { + SPRINTF(buf, "%s", perc); + } + SPRINTF(buf, ": %s\n", eip_desc); + } else { + SPRINTF(buf, " %6s: %s\n\n", perc, eip_desc); + } + + if (child->n_children > 0) { + enqueue(q, (void*)child); + c2++; + } + } + SPRINTF(buf, "%s%s", maybe_ful, maybe_p); + c1--; + + // Putting markers between levels of the structure: + // c1 tracks how many to go on this level, c2 tracks how many we've + // queued up for the next level while finishing off this level. + // When c1 gets to zero, we've changed levels, so print a marker, + // move c2 into c1, and zero c2. + if (0 == c1) { + L++; + c1 = c2; + c2 = 0; + if (! is_empty_queue(q) ) { // avoid empty one at end + SPRINTF(buf, "== %d ===========================%s\n", L, maybe_br); + } + } else { + SPRINTF(buf, "---------------------------------%s\n", maybe_br); + } + } + SPRINTF(buf, "%s\n\nEnd of information. Rerun with a bigger " + "%s value for more.\n", end_hr, depth); +} + +static void pp_all_XPts(Int fd, XPt* xpt, ULong heap_spacetime, + ULong total_spacetime) +{ + Queue* q = construct_queue(100); + enqueue(q, xpt); + pp_all_XPts2(fd, q, heap_spacetime, total_spacetime); + destruct_queue(q); +} + +static void +write_text_file(ULong total_ST, ULong heap_ST) +{ + Int fd, i; + Char* text_file; + Char* maybe_p = ( XHTML == clo_format ? "

    " : "" ); + + VGP_PUSHCC(VgpPrintXPts); + + // Open file + text_file = make_filename( base_dir, + ( XText == clo_format ? ".txt" : ".html" ) ); + + fd = VG_(open)(text_file, VKI_O_CREAT|VKI_O_TRUNC|VKI_O_WRONLY, + VKI_S_IRUSR|VKI_S_IWUSR); + if (fd < 0) { + file_err( text_file ); + VGP_POPCC(VgpPrintXPts); + return; + } + + // Header + if (XHTML == clo_format) { + SPRINTF(buf, "\n" + "\n" + "%s\n" + "\n" + "\n", + text_file); + } + + // Command line + SPRINTF(buf, "Command: "); + for (i = 0; i < VG_(client_argc); i++) + SPRINTF(buf, "%s ", VG_(client_argv)[i]); + SPRINTF(buf, "\n%s\n", maybe_p); + + if (clo_heap) + pp_all_XPts(fd, alloc_xpt, heap_ST, total_ST); + + sk_assert(fd >= 0); + VG_(close)(fd); + + VGP_POPCC(VgpPrintXPts); +} + +/*------------------------------------------------------------*/ +/*--- Finalisation ---*/ +/*------------------------------------------------------------*/ + +static void +print_summary(ULong total_ST, ULong heap_ST, ULong heap_admin_ST, + ULong stack_ST) +{ + VG_(message)(Vg_UserMsg, "Total spacetime: %,ld ms.B", total_ST); + + // Heap -------------------------------------------------------------- + if (clo_heap) + VG_(message)(Vg_UserMsg, "heap: %s", + make_perc(heap_ST, total_ST) ); + + // Heap admin -------------------------------------------------------- + if (clo_heap_admin) + VG_(message)(Vg_UserMsg, "heap admin: %s", + make_perc(heap_admin_ST, total_ST)); + + sk_assert( VG_(HT_count_nodes)(malloc_list) == n_heap_blocks ); + + // Stack(s) ---------------------------------------------------------- + if (clo_stacks) + VG_(message)(Vg_UserMsg, "stack(s): %s", + make_perc(stack_ST, total_ST)); + + if (VG_(clo_verbosity) > 1) { + sk_assert(n_xpts > 0); // always have alloc_xpt + VG_(message)(Vg_DebugMsg, " allocs: %u", n_allocs); + VG_(message)(Vg_DebugMsg, "zeroallocs: %u (%d%%)", n_zero_allocs, + n_zero_allocs * 100 / n_allocs ); + VG_(message)(Vg_DebugMsg, " frees: %u", n_frees); + VG_(message)(Vg_DebugMsg, " XPts: %u (%d B)", n_xpts, + n_xpts*sizeof(XPt)); + VG_(message)(Vg_DebugMsg, " bot-XPts: %u (%d%%)", n_bot_xpts, + n_bot_xpts * 100 / n_xpts); + VG_(message)(Vg_DebugMsg, " top-XPts: %u (%d%%)", alloc_xpt->n_children, + alloc_xpt->n_children * 100 / n_xpts); + VG_(message)(Vg_DebugMsg, "c-reallocs: %u", n_children_reallocs); + VG_(message)(Vg_DebugMsg, "snap-frees: %u", n_snapshot_frees); + VG_(message)(Vg_DebugMsg, "atmp censi: %u", n_attempted_censi); + VG_(message)(Vg_DebugMsg, "fake censi: %u", n_fake_censi); + VG_(message)(Vg_DebugMsg, "real censi: %u", n_real_censi); + VG_(message)(Vg_DebugMsg, " halvings: %u", n_halvings); + } +} + +void SK_(fini)(Int exit_status) +{ + ULong total_ST = 0; + ULong heap_ST = 0; + ULong heap_admin_ST = 0; + ULong stack_ST = 0; + + // Do a final (empty) sample to show program's end + hp_census(); + + // Redo spacetimes of significant contexts to match the .hp file. + calc_spacetime2(&heap_ST, &heap_admin_ST, &stack_ST); + total_ST = heap_ST + heap_admin_ST + stack_ST; + write_hp_file ( ); + write_text_file( total_ST, heap_ST ); + print_summary ( total_ST, heap_ST, heap_admin_ST, stack_ST ); +} + +VG_DETERMINE_INTERFACE_VERSION(SK_(pre_clo_init), 0) + +/*--------------------------------------------------------------------*/ +/*--- end ms_main.c ---*/ +/*--------------------------------------------------------------------*/ + diff --git a/massif/tests/Makefile.am b/massif/tests/Makefile.am new file mode 100644 index 0000000000..5c3b76b2c9 --- /dev/null +++ b/massif/tests/Makefile.am @@ -0,0 +1,6 @@ +noinst_SCRIPTS = filter_stderr + +EXTRA_DIST = $(noinst_SCRIPTS) \ + true_html.stderr.exp true_html.vgtest \ + true_text.stderr.exp true_text.vgtest + diff --git a/massif/tests/true_html.stderr.exp b/massif/tests/true_html.stderr.exp new file mode 100644 index 0000000000..c18e180c98 --- /dev/null +++ b/massif/tests/true_html.stderr.exp @@ -0,0 +1,6 @@ + + +Total spacetime: +heap: +heap admin: +stack(s): diff --git a/massif/tests/true_html.vgtest b/massif/tests/true_html.vgtest new file mode 100644 index 0000000000..8d568359d5 --- /dev/null +++ b/massif/tests/true_html.vgtest @@ -0,0 +1,2 @@ +prog: ../../tests/true +vgopts: --format=html diff --git a/massif/tests/true_text.stderr.exp b/massif/tests/true_text.stderr.exp new file mode 100644 index 0000000000..c18e180c98 --- /dev/null +++ b/massif/tests/true_text.stderr.exp @@ -0,0 +1,6 @@ + + +Total spacetime: +heap: +heap admin: +stack(s): diff --git a/massif/tests/true_text.vgtest b/massif/tests/true_text.vgtest new file mode 100644 index 0000000000..841ae1f153 --- /dev/null +++ b/massif/tests/true_text.vgtest @@ -0,0 +1,2 @@ +prog: ../../tests/true +vgopts: --format=text