1. The GNAT Metrics Tool gnatmetric
The gnatmetric
tool is a utility for computing various program metrics. It
takes an Ada source file as input and generates a file containing the metrics
data as output. Various switches control which metrics are reported.
gnatmetric
is a project-aware tool, as detailed in
https://docs.adacore.com/live/wave/gnat_ugn/html/gnat_ugn/gnat_ugn/gnat_utility_programs.html#tool-specific-packages-in-project-files
The gnatmetric
command has the form
$ gnatmetric [ switches ] { filename }
where:
switches
specify the metrics to compute and define the destination for the outputEach
filename
is the name of a source file to process. ‘Wildcards’ are allowed, and the file name may contain path information. If nofilename
is supplied, then theswitches
list must contain at least one--files
switch (see Other gnatmetric Switches). Including both a--files
switch and one or morefilename
arguments is permitted.Note that it is no longer necessary to specify the Ada language version;
gnatmetric
can process Ada source code written in any version from Ada 83 onward without specifying any language version switch.
The following subsections describe the various switches accepted by
gnatmetric
, organized by category.
1.1. Configuration in GPR file
The project file package that can specify gnatmetric
switches is named
Metrics
.
It supports setting the Default_Switches ("Ada")
attribute for modifying the tool
command-line default arguments for the project.
Note
Switches additionaly provided on the command-line may override these default arguments.
package Metrics is
for Default_Switches ("Ada") use
("--generate-xml-output", -- Generate an XML output file
" --xml-file-name", XML_File_Name, -- Set XML file name
"--lines-all"); -- Report all the line metrics
end Metrics;
The GPR file will be automatically used by gnatmetric
as a set of default
switches (See all the possible switches below).
You can also specify external variables with the -X
switch
gnatmetric -P prj.gpr -XVariable="Value..."
This allows you to have a set of predefined GNATmetric presets to choose from using scenario variables computed from the external variables in your GPR file, or to have a default GNATmetric configuration when running it through GNATcheck (see https://docs.adacore.com/live/wave/lkql/html/gnatcheck_rm/generated/predefined_rules.html#metrics-related-rules).
1.2. Output File Control
gnatmetric
has two output formats. It can generate a textual
(human-readable) form, and also XML. By default only textual output is
generated.
When generating the output in textual form, gnatmetric
creates for each Ada
source file a corresponding text file containing the computed metrics, except
for the case when the set of metrics specified by gnatmetric parameters
consists only of metrics that are computed for the whole set of analyzed
sources, but not for each Ada source. By default, the name of the file
containing metric information for a source is obtained by appending the
.metrix
suffix to the name of the input source file. If not otherwise
specified and no project file is specified as gnatmetric
option this file
is placed in the same directory as where the source file is located. If
gnatmetric
has a project file as its parameter, it places all the
generated files in the object directory of the project (or in the project
source directory if the project does not define an object directory). If
--subdirs
option is specified, the files are placed in the
subrirectory of this directory specified by this option.
All the output information generated in XML format is placed in a single file.
By default the name of this file is metrix.xml
. If not otherwise
specified and if no project file is specified as gnatmetric
option this
file is placed in the current directory.
Some of the computed metrics are summed over the units passed to
gnatmetric
; for example, the total number of lines of code. By default this
information is sent to stdout
, but a file can be specified with the
--global-file-name
switch.
The following switches control the gnatmetric
output:
--generate-xml-output
Generate XML output.
--generate-xml-schema
Generate XML output and an XML schema file that describes the structure of the XML metric report. This schema is assigned to the XML file. The schema file has the same name as the XML output file with
.xml
suffix replaced with.xsd
.
--no-text-output
Do not generate the output in text form (implies
-x
).
--output-dir=output_dir
Put text files with detailed metrics into
output_dir
.
--output-suffix=file_suffix
Use
file_suffix
, instead of.metrix
in the name of the output file.
--global-file-name=file_name
Put global metrics into
file_name
.
--xml-file-name=file_name
Put the XML output into
file_name
(also implies--generate-xml-output
).
--short-file-names
Use ‘short’ source file names in the output. (The
gnatmetric
output includes the name(s) of the Ada source file(s) from which the metrics are computed. By default each name includes the absolute path. The--short-file-names
switch causesgnatmetric
to exclude all directory information from the file names that are output.)--wide-character-encoding=e
Specify the wide character encoding method for the input and output files.
e
is one of the following:8 - UTF-8 encoding
b - Brackets encoding (default value)
1.3. Disable Metrics For Local Units
gnatmetric
relies on the GNAT compilation model – one compilation unit per
one source file. It computes line metrics for the whole source file, and it
also computes syntax and complexity metrics for the file’s outermost unit.
By default, gnatmetric
will also compute all metrics for certain kinds of
locally declared program units:
subprogram (and generic subprogram) bodies;
package (and generic package) specs and bodies;
task object and type specifications and bodies;
protected object and type specifications and bodies.
These kinds of entities will be referred to as eligible local program units, or simply eligible local units, in the discussion below.
Note that a subprogram declaration, generic instantiation, or renaming declaration only receives metrics computation when it appear as the outermost entity in a source file.
Suppression of metrics computation for eligible local units can be obtained via the following switch:
--no-local-metrics
Do not compute detailed metrics for eligible local program units.
1.4. Specifying a set of metrics to compute
By default all the metrics are reported. The switches described in this subsection allow you to control, on an individual basis, whether metrics are reported. If at least one positive metric switch is specified (that is, a switch that defines that a given metric or set of metrics is to be computed), then only explicitly specified metrics are reported.
1.4.1. Line Metrics Control
For each source file, and for each of its eligible local program units,
gnatmetric
computes the following metrics:
the total number of lines;
the total number of code lines (i.e., non-blank lines that are not comments)
the number of comment lines
the number of code lines containing end-of-line comments;
the comment percentage: the ratio between the number of lines that contain comments and the number of all non-blank lines, expressed as a percentage
the number of empty lines and lines containing only space characters and/or format effectors (blank lines)
the average number of code lines in subprogram bodies, task bodies, entry bodies and statement sequences in package bodies
gnatmetric
sums the values of the line metrics for all the files
being processed and then generates the cumulative results. The tool
also computes for all the files being processed the average number of
code lines in bodies.
You can use the following switches to select the specific line metrics to be reported.
--lines-all
Report all the line metrics
--no-lines-all
Do not report any of line metrics
--lines
Report the number of all lines
--no-lines
Do not report the number of all lines
--lines-code
Report the number of code lines
--no-lines-code
Do not report the number of code lines
--lines-comment
Report the number of comment lines
--no-lines-comment
Do not report the number of comment lines
--lines-eol-comment
Report the number of code lines containing end-of-line comments
--no-lines-eol-comment
Do not report the number of code lines containing end-of-line comments
--lines-ratio
Report the comment percentage in the program text
--no-lines-ratio
Do not report the comment percentage in the program text
--lines-blank
Report the number of blank lines
--no-lines-blank
Do not report the number of blank lines
--lines-average
Report the average number of code lines in subprogram bodies, task bodies, entry bodies and statement sequences in package bodies.
--no-lines-average
Do not report the average number of code lines in subprogram bodies, task bodies, entry bodies and statement sequences in package bodies.
--lines-spark
Report the number of lines written in SPARK.
--no-lines-spark
Do not report the number of lines written in SPARK.
1.4.2. Syntax Metrics Control
gnatmetric
computes various syntactic metrics for the
outermost unit and for each eligible local unit:
- LSLOC (‘Logical Source Lines Of Code’)
The total number of declarations and the total number of statements. Note that the definition of declarations is the one given in the reference manual:
“Each of the following is defined to be a declaration: any basic_declaration; an enumeration_literal_specification; a discriminant_specification; a component_declaration; a loop_parameter_specification; a parameter_specification; a subprogram_body; an entry_declaration; an entry_index_specification; a choice_parameter_specification; a generic_formal_parameter_declaration.”
This means for example that each enumeration literal adds one to the count, as well as each subprogram parameter.
- Maximal static nesting level of inner program units
According to Ada Reference Manual, 10.1(1):
“A program unit is either a package, a task unit, a protected unit, a protected entry, a generic unit, or an explicitly declared subprogram other than an enumeration literal.”
- Maximal nesting level of composite syntactic constructs
This corresponds to the notion of the maximum nesting level in the GNAT built-in style checks (see https://docs.adacore.com/live/wave/gnat_ugn/html/gnat_ugn/gnat_ugn/building_executable_programs_with_gnat.html#style-checking).
- Number of formal parameters
Number of formal parameters of a subprogram; if a subprogram does have parameters, then numbers of “in”, “out” and “in out” parameters are also reported. This metric is reported for subprogram specifications and for subprogram instantiations. For subprogram bodies, expression functions and null procedures this metric is reported if the construct acts as a subprogram declaration but is not a completion of previous declaration. This metric is not reported for generic and formal subprograms.
For the outermost unit in the file, gnatmetric
additionally
computes the following metrics:
- Public subprograms
This metric is computed for package specs. It is the number of subprograms and generic subprograms declared in the visible part (including the visible part of nested packages, protected objects, and protected types).
- All subprograms
This metric is computed for bodies and subunits. The metric is equal to a total number of subprogram bodies in the compilation unit. Neither generic instantiations nor renamings-as-a-body nor body stubs are counted. Any subprogram body is counted, independently of its nesting level and enclosing constructs. Generic bodies and bodies of protected subprograms are counted in the same way as ‘usual’ subprogram bodies.
- Public types
This metric is computed for package specs and generic package declarations. It is the total number of types that can be referenced from outside this compilation unit, plus the number of types from all the visible parts of all the visible generic packages. Generic formal types are not counted. Only types, not subtypes, are included.
Along with the total number of public types, the following types are counted and reported separately:
Abstract types
Root tagged types^ (abstract, non-abstract, private, non-private). Type extensions are *not counted
Private types (including private extensions)
Task types
Protected types
- All types
This metric is computed for any compilation unit. It is equal to the total number of the declarations of different types given in the compilation unit. The private and the corresponding full type declaration are counted as one type declaration. Incomplete type declarations and generic formal types are not counted. No distinction is made among different kinds of types (abstract, private etc.); the total number of types is reported.
By default, all the syntax metrics are reported. You can use the following switches to select specific syntax metrics.
--syntax-all
Report all the syntax metrics
--no-syntax-all
Do not report any of syntax metrics
--declarations
Report the total number of declarations
--no-declarations
Do not report the total number of declarations
--statements
Report the total number of statements
--no-statements
Do not report the total number of statements
--public-subprograms
Report the number of public subprograms in a compilation unit
--no-public-subprograms
Do not report the number of public subprograms in a compilation unit
--all-subprograms
Report the number of all the subprograms in a compilation unit
--no-all-subprograms
Do not report the number of all the subprograms in a compilation unit
--public-types
Report the number of public types in a compilation unit
--no-public-types
Do not report the number of public types in a compilation unit
--all-types
Report the number of all the types in a compilation unit
--no-all-types
Do not report the number of all the types in a compilation unit
--unit-nesting
Report the maximal program unit nesting level
--no-unit-nesting
Do not report the maximal program unit nesting level
--construct-nesting
Report the maximal construct nesting level
--no-construct-nesting
Do not report the maximal construct nesting level
--param-number
Report the number of subprogram parameters
--no-param-number
Do not report the number of subprogram parameters
1.4.3. Contract Metrics Control
--contract-all
Report all the contract metrics
--no-contract-all
Do not report any of the contract metrics
--contract
Report the number of public subprograms with contracts
--no-contract
Do not report the number of public subprograms with contracts
--post
Report the number of public subprograms with postconditions
--no-post
Do not report the number of public subprograms with postconditions
--contract-complete
Report the number of public subprograms with complete contracts
--no-contract-complete
Do not report the number of public subprograms with complete contracts
--contract-cyclomatic
Report the McCabe complexity of public subprograms
--no-contract-cyclomatic
Do not report the McCabe complexity of public subprograms
1.4.4. Complexity Metrics Control
For a program unit that is an executable body (a subprogram body (including
generic bodies), task body, entry body or a package body containing its own
statement sequence) gnatmetric
computes the following complexity metrics:
McCabe cyclomatic complexity;
McCabe essential complexity;
maximal loop nesting level;
extra exit points (for subprograms);
The McCabe cyclomatic complexity metric is defined in https://www.mccabe.com/pdf/mccabe-nist235r.pdf
According to McCabe, both control statements and short-circuit control forms should be taken into account when computing cyclomatic complexity. For Ada 2012 we have also take into account conditional expressions and quantified expressions. For each body, we compute three metric values:
the complexity introduced by control statements only, without taking into account short-circuit forms (referred as
statement complexity
ingnatmetric
output),the complexity introduced by short-circuit control forms only (referred as
expression complexity
ingnatmetric
output), andthe total cyclomatic complexity, which is the sum of these two values (referred as
cyclomatic complexity
ingnatmetric
output).
The cyclomatic complexity is also computed for Ada 2012 expression functions. An expression function cannot have statements as its components, so only one metric value is computed as a cyclomatic complexity of an expression function.
The origin of cyclomatic complexity metric is the need to estimate the number
of independent paths in the control flow graph that in turn gives the number of
tests needed to satisfy paths coverage testing completeness criterion.
Considered from the testing point of view, a static Ada loop
(that is, the
loop
statement having static subtype in loop parameter specification) does
not add to cyclomatic complexity. By providing --no-static-loop
option a user may specify that such loops should not be counted when computing
the cyclomatic complexity metric
The Ada essential complexity metric is a McCabe cyclomatic complexity metric
counted for the code that is reduced by excluding all the pure structural Ada
control statements. An compound statement is considered as a non-structural if
it contains a raise
or return
statement as it subcomponent, or if it
contains a goto
statement that transfers the control outside the operator.
A selective accept
statement with a terminate
alternative is considered
a non-structural statement. When computing this metric, exit
statements are
treated in the same way as goto
statements unless the -ne
option
is specified.
The Ada essential complexity metric defined here is intended to quantify the extent to which the software is unstructured. It is adapted from the McCabe essential complexity metric defined in https://www.mccabe.com/pdf/mccabe-nist235r.pdf but is modified to be more suitable for typical Ada usage. For example, short circuit forms are not penalized as unstructured in the Ada essential complexity metric.
When computing cyclomatic and essential complexity, gnatmetric
skips the
code in the exception handlers and in all the nested program units. The code of
assertions and predicates (that is, subprogram preconditions and
postconditions, subtype predicates and type invariants) is also skipped.
By default, all the complexity metrics are reported. For more fine-grained control you can use the following switches:
--complexity-all
Report all the complexity metrics
--no-complexity-all
Do not report any of the complexity metrics
--complexity-cyclomatic
Report the McCabe Cyclomatic Complexity
--no-complexity-cyclomatic
Do not report the McCabe Cyclomatic Complexity
--complexity-essential
Report the Essential Complexity
--no-complexity-essential
Do not report the Essential Complexity
--loop-nesting
Report maximal loop nesting level
--no-loop-nesting
Do not report maximal loop nesting level
--complexity-average
Report the average McCabe Cyclomatic Complexity for all the subprogram bodies, task bodies, entry bodies and statement sequences in package bodies. The metric is reported for whole set of processed Ada sources only.
--no-complexity-average
Do not report the average McCabe Cyclomatic Complexity for all the subprogram bodies, task bodies, entry bodies and statement sequences in package bodies
--no-treat-exit-as-goto
Do not consider
exit
statements asgoto
s when computing Essential Complexity
--no-static-loop
Do not consider static loops when computing cyclomatic complexity
--extra-exit-points
Report the extra exit points for subprogram bodies. As an exit point, this metric counts
return
statements and raise statements in case when the raised exception is not handled in the same body. In case of a function this metric subtracts 1 from the number of exit points, because a function body must contain at least onereturn
statement.--no-extra-exit-points
Do not report the extra exit points for subprogram bodies
1.4.5. Coupling Metrics Control
Coupling metrics measure the dependencies between a given entity and other entities in the program. This information is useful since high coupling may signal potential issues with maintainability as the program evolves.
gnatmetric
computes the following coupling metrics:
object-oriented coupling, for classes in traditional object-oriented sense;
unit coupling, for all the program units making up a program;
control coupling, reflecting dependencies between a unit and other units that contain subprograms.
Two kinds of coupling metrics are computed:
fan-out coupling (‘efferent coupling’): the number of entities the given entity depends upon. This metric reflects how the given entity depends on the changes in the ‘external world’.
fan-in coupling (‘afferent’ coupling): the number of entities that depend on a given entity. This metric reflects how the ‘external world’ depends on the changes in a given entity.
Object-oriented coupling metrics measure the dependencies between a given class (or a group of classes) and the other classes in the program. In this subsection the term ‘class’ is used in its traditional object-oriented programming sense (an instantiable module that contains data and/or method members). A category (of classes) is a group of closely related classes that are reused and/or modified together.
A class K
‘s fan-out coupling is the number of classes that K
depends
upon.
A category’s fan-out coupling is the number of classes outside the category
that the classes inside the category depend upon.
A class K
‘s fan-in coupling is the number of classes that depend upon
K
.
A category’s fan-in coupling is the number of classes outside the category that
depend on classes belonging to the category.
Ada’s object-oriented paradigm separates the instantiable entity (type) from the module (package), so the definition of the coupling metrics for Ada maps the class and class category notions onto Ada constructs.
For the coupling metrics, several kinds of modules that define a tagged type or an interface type – library packages, library generic packages, and library generic package instantiations – are considered to be classes. A category consists of a library package (or a library generic package) that defines a tagged or an interface type, together with all its descendant (generic) packages that define tagged or interface types. Thus a category is an Ada hierarchy of library-level program units. Class coupling in Ada is referred to as ‘tagged coupling’, and category coupling is referred to as ‘hierarchy coupling’.
For any package serving as a class, its body and subunits (if any) are considered together with its spec when computing dependencies, and coupling metrics are reported for spec units only. Dependencies between classes mean Ada semantic dependencies. For object-oriented coupling metrics, only dependencies on units treated as classes are considered.
Similarly, for unit and control coupling an entity is considered to be the
conceptual construct consisting of the entity’s specification, body, and any
subunits (transitively). gnatmetric
computes the dependencies of all these
units as a whole, but metrics are only reported for spec units (or for a
subprogram body unit in case if there is no separate spec for the given
subprogram).
For unit coupling, dependencies are computed between all kinds of program units. For control coupling, the dependencies of a given unit are limited to those units that define subprograms. Thus control fan-out coupling is reported for all units, but control fan-in coupling is only reported for units that define subprograms.
The following simple example illustrates the difference between unit coupling and control coupling metrics:
package Lib_1 is function F_1 (I : Integer) return Integer; end Lib_1; package Lib_2 is type T_2 is new Integer; end Lib_2; package body Lib_1 is function F_1 (I : Integer) return Integer is begin return I + 1; end F_1; end Lib_1; with Lib_2; use Lib_2; package Pack is Var : T_2; function Fun (I : Integer) return Integer; end Pack; with Lib_1; use Lib_1; package body Pack is function Fun (I : Integer) return Integer is begin return F_1 (I); end Fun; end Pack;
If we apply gnatmetric
with the --coupling-all
option to these
units, the result will be:
Coupling metrics: ================= Unit Lib_1 (C:\\customers\\662\\L406-007\\lib_1.ads) control fan-out coupling : 0 control fan-in coupling : 1 unit fan-out coupling : 0 unit fan-in coupling : 1 Unit Pack (C:\\customers\\662\\L406-007\\pack.ads) control fan-out coupling : 1 control fan-in coupling : 0 unit fan-out coupling : 2 unit fan-in coupling : 0 Unit Lib_2 (C:\\customers\\662\\L406-007\\lib_2.ads) control fan-out coupling : 0 unit fan-out coupling : 0 unit fan-in coupling : 1
The result does not contain values for object-oriented coupling because none of the argument units contains a tagged type and therefore none of these units can be treated as a class.
The Pack
package (spec and body) depends on two units – Lib_1
and
Lib_2
– and so its unit fan-out coupling is 2. Since nothing depends on
it, its unit fan-in coupling is 0, as is its control fan-in coupling. Only one
of the units Pack
depends upon defines a subprogram, so its control fan-out
coupling is 1.
Lib_2
depends on nothing, so its fan-out metrics are 0. It does not define
any subprograms, so it has no control fan-in metric. One unit (Pack
)
depends on it , so its unit fan-in coupling is 1.
Lib_1
is similar to Lib_2
, but it does define a subprogram. Its control
fan-in coupling is 1 (because there is one unit depending on it).
When computing coupling metrics, gnatmetric
counts only dependencies
between units that are arguments of the gnatmetric
invocation. Coupling
metrics are program-wide (or project-wide) metrics, so you should invoke
gnatmetric
for the complete set of sources comprising your program. This
can be done by invoking gnatmetric
with the corresponding project file and
with the -U
option.
By default, all the coupling metrics are reported. You can use the following switches to select specific syntax metrics.
--coupling-all
Report all the coupling metrics
--tagged-coupling-out
Report tagged (class) fan-out coupling
--tagged-coupling-in
Report tagged (class) fan-in coupling
--hierarchy-coupling-out
Report hierarchy (category) fan-out coupling
--hierarchy-coupling-in
Report hierarchy (category) fan-in coupling
--unit-coupling-out
Report unit fan-out coupling
--unit-coupling-in
Report unit fan-in coupling
--control-coupling-out
Report control fan-out coupling
--control-coupling-in
Report control fan-in coupling
1.5. Other gnatmetric
Switches
Additional gnatmetric
switches are as follows:
--version
Display copyright and version, then exit disregarding all other options.
--help
Display usage, then exit disregarding all other options.
-P file
Indicates the name of the project file that describes the set of sources to be processed. The exact set of argument sources depends on other options specified, see below. An aggregate project is allowed as the file parameter only if it has exactly one non-aggregate project being aggregated.
-U
If a project file is specified and no argument source is explicitly specified (either directly or by means of
-files
option), process all the units of the closure of the argument project. Otherwise this option has no effect.-U main_unit
If a project file is specified and no argument source is explicitly specified (either directly or by means of
-files
option), process the closure of units rooted atmain_unit
. Otherwise this option has no effect.
-Xname=value
Give external variable
name
the valuevalue
in the argument project. Has no effect if no project is specified.
--RTS=rts-path
Specifies the default location of the runtime library. Same meaning as the equivalent
gnatmake
flag (see https://docs.adacore.com/live/wave/gnat_ugn/html/gnat_ugn/gnat_ugn/building_executable_programs_with_gnat.html#running-gnatmake).
--subdirs=dir
Use the specified subdirectory of the project objects file (or of the project file directory if the project does not specify an object directory) for tool output files. Has no effect if no project is specified as tool argument r if
--no-objects-dir
is specified.
--files=file
Take as arguments the files listed in text file
file
. Text filefile
may contain empty lines that are ignored. Each nonempty line should contain the name of an existing file. Several such switches may be specified simultaneously.
--ignore=filename
Do not process the sources listed in a specified file.
--verbose
Verbose mode;
gnatmetric
generates version information and then a trace of sources being processed.
--quiet
Quiet mode.
If a project file is specified and no argument source is explicitly specified
(either directly or by means of -files
option), and no -U
is specified, then the set of processed sources is all the immediate units of
the argument project.
1.5.1. Legacy Switches
Some switches have a short form, mostly for legacy reasons, as shown below.
-x
--generate-xml-output
-xs
--generate-xml-schema
-nt
--no-text-output
-d output-dir
--output-dir
-o file-suffix
--output-suffix
-og file-name
--global-file-name
-ox file-name
--xml-file-name
-sfn
--short-file-names
-We
--wide-character-encoding=e
-nolocal
--no-local-metrics
-ne
--no-treat-exit-as-goto
-files filename
--files
-v
--verbose
-q
--quiet