4.5. Metrics¶
4.5.1. Introduction¶
This page describes metrics that can be calculated by the Axivion Suite. All metrics are generally available for all supported languages, if not specified explicitly otherwise. Metrics measuring specific language features can only produce meaningful results for languages that support said features.
4.5.2. Metrics based on counting Elements of the System¶
This section describes metrics counting different aspects of the system like lines of code, tokens, statements or routine invocations.
4.5.2.1. Metrics based on Line and Token Counts¶
Metric.Lines.LOC
Counts the lines of code of the routine or file. For functions it is starting at the beginning of the signature, and ending at the line where the closing curly brace is located.
Metric.Lines.Code
Counts lines within the function or file that contain source code tokens.
Note
In addition to Metric.Lines.LOC and Metric.Lines.Code, there are
alternative versions of these metrics limited to either function or file scope.
Metric.Lines.Empty
Count of lines within the function or file that are completely empty (whitespaces allowed).
Metric.Lines.Comment
Counts the lines within the function or file that contain a comment.
Metric.Lines.Only_Comment
Counts the lines within the function or file that contain only comments (but no other language tokens).
Note
In addition to Metric.Lines.Comment and Metric.Lines.Only_Comment, there are
alternative versions of these metrics limited to either function or file scope.
Metric.Lines.Fixme, Metric.Lines.Hack, Metric.Lines.Todo, Metric.Lines.XXX
Counts lines that contain one or more times the token Fixme resp. Hack
resp. Todo resp. XXX (inside a comment or as language token). The lookup
itself is case insensitive, so that Fixme, FIXME and fixme are
all accounted for by the Metric.Lines.Fixme metric. The names of the
metric attributes are case sensitive.
The following example illustrates the computation of the line-based and token-based metrics:
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025-2026 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
//
// Example for
// Metric.Lines.LOC
// Metric.Lines.File.LOC
// Metric.Lines.Routine.LOC
// Metric.Lines.Code
// Metric.Lines.Empty
// Metric.Lines.{FIXME, HACK, TODO, XXX}
void doit(int x)
{
//FIXME use correct value here.
int y = 2*x;
while (y > 0)
{
// TODO: add corrective factor
x = x+y;
}
// toDO: condition check
// HACK: use recursion here
doit(x-1);
}
// Metric values for doit:
// Metric.Lines.LOC = 18
// Metric.Lines.Routine.LOC = 18
// Metric.Lines.Code = 9
// Metric.Lines.Empty = 5
// Metric.Lines.FIXME = 1
// Metric.Lines.HACK = 1
// Metric.Lines.Todo = 2
// Metric.Lines.XXX = 0
// Metric.Lines.LOC = 43 (entire file)
// Metric.Lines.File.LOC = 43 (entire file)
Available rules:
LinesOfCodeMetrics: Metrics based on lines of code.
TokenMetrics: Metrics counting specific tokens.
4.5.2.2. Metrics counting Comment sections¶
For counting comment sections the following rules are applied:
Each comment enclosed by
/*and*/is counted as a single comment section.A sequence of
//-comments counts as one comment section if they are not separated by a non-comment element or a newline. So in the following example// This // is // one comment section. int x = 0;
only one comment section is counted; however in the next example
// These // are // two comment sections. int x = 0;
two comment sections are counted, as are in this example:
// These int y = 1; // are // two comment sections. int x = 0;
Metric.Comment.Preceding
Counts the number of comments directly before the routine header that do not share a
line with another non-comment code construct. Roughly speaking, comments that are
located directly before a routine (i.e., no other code is between them and the beginning
of the routine signature) are considered as preceding comments. Preceding preprocessor
conditionals (like #if ..., #else ..., #ifdef ... etc.) between
comments and routines are skipped if a comment is in the line directly above the
conditional. In such a case however, only this immediately preceding comment C
and following comments are considered as preceding comments for following entities; any
comments before the comment C are not considered preceding comments (see the
routines doit_c() and doit_d() in the following examples).
Metric.Comment.Internal
Counts the number of comments within the body of a routine.
Metric.Comment.Density
Computes the comment density (also called comment frequency) of a routine, which roughly
corresponds to the ration between comments before and within the routine and the number
of statements of the routine; however, all comments before the routine are treated as
one comment. Its values coincide with the values of Metric.HIS.COMF. For a
given routine r, let st(r) be the number of statements of the routine.
Let p(r) be the number of comments before the routine, i.e., the value
Metric.Comment.Preceding, and let i(r) be the number of comments
within the routine, i.e., the value Metric.Comment.Internal. Then the
comment density d(r) of routine r is computed as d(r) = (p(r)+i(r))/st(r)
if st(r) > 0. If st(r) = 0, no metric value for the comment density is
annotated for the routine in question.
Consider the following example:
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
//Example for
//Metric.Comment.Preceding,
//Metric.Comment.Internal,
//Metric.Comment.Density (= Metric.HIS.COMF)
int doit_a()
{
return 1;
}
int x = 1; // global variable
/* Another comment */
int doit_b()
{
// a comment
int local_x = 1; /* another one */
/* a third */
int y = 0;
y = local_x + 1;
// fourth comment
// continued
return y;
}
#define A 1
/* description of another_function() */
#if A
int doit_c()
{
int x = 1;
// Comment
int y = 2;
return x+y;
}
#endif
#if B
/* A description */
#else
/* more description */
int doit_d()
{
int x = 1;
// Comment
int y = 2;
return 1;
}
#endif
The computation of metric values for Metric.Comment.Preceding and
Metric.Comment.Internal can be influenced by options of the
corresponding rules.
If the metrics are computed using the default settings, we obtain the
following values for the metrics:
Routine
doit_a(): it has 0 as value forMetric.Comment.Preceding. The initial comment is counted as a header comment in the default settings and thus is not counted as a preceding comment of the function. The value forMetric.Comment.Internalis 0, since no comments are within the routine body. This gives also a value of 0 for the metricMetric.Comment.Density. The value ofMetric.Lines.Commentis 0, as is the value ofMetric.Lines.Only_Comment.Routine
doit_b(): it has 1 as value forMetric.Comment.Preceding, since the comment/* Another comment */is counted. The value ofMetric.Comment.Internalis 4, and soMetric.Comment.Densityis 1.25, since there are four statements in the function body. The value ofMetric.Lines.Commentis 4, and the value ofMetric.Lines.Only_Commentis 3.Routine
doit_c(): it has 1 as value forMetric.Comment.Preceding. The conditional#if Adoes not separate the comment from the signature of the routine. Its value forMetric.Comment.Internalis 1, and so the value ofMetric.Comment.Densitysums up to (1+1)/3 = 2/3, since there are three statements in the function body. The value ofMetric.Lines.Commentis 1, as is the value ofMetric.Lines.Only_Comment.Routine
doit_d(): it has 2 as value forMetric.Comment.Preceding. Only the comment directly above#elseand the comment between#elseand the signature ofdoit_d()are counted as preceding comments. Its value forMetric.Comment.Internalis 1, and so the value ofMetric.Comment.Densitysums up to (2+1)/3 = 1, since there are three statements in the function body. The value ofMetric.Lines.Commentis 1, as is the value ofMetric.Lines.Only_Comment.
Available rules:
CommentMetrics: Comment based metrics.
4.5.2.3. Statement-Based Metrics¶
Metric.Number_Of_Statements
Count of all statements in routine.
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
Metric.Number_Of_Gotos
Count of goto-statements in routine. Corresponds to the metric
Metric.HIS.GOTO.
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
Metric.Number_Of_Returns
Count of return statements in routine. Corresponds to the metric
Metric.HIS.RETURN.
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
In the following we give an example for all three statement-based metrics:
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
// Example for
// Metric.Number_Of_Statements
// Metric.Number_Of_Gotos (= Metric.HIS.GOTO)
// Metric.Number_Of_Returns (= Metric.HIS.RETURN)
int doit(int x)
{
if (x <= 1)
// +1 Metric.Number_Of_Statements
{
return 1;
// +1 Metric.Number_Of_Statements
// +1 Metric.Number_Of_Returns
}
L1:
x -= 2;
// +1 Metric.Number_Of_Statements
if (x > 2)
// +1 Metric.Number_Of_Statements
{
goto L1;
// +1 Metric.Number_Of_Statements
// +1 Metric.Number_Of_Gotos
}
return x;
// +1 Metric.Number_Of_Statements
// +1 Metric.Number_Of_Returns
}
// Metric.Number_Of_Statements = 6
// Metric.Number_Of_Returns = 2
// Metric.Number_Of_Gotos = 1
Available rules:
Metric-NumberOfStatements: Number of Statements.
Metric-NumberOfGotos: Number of Gotos.
Metric-NumberOfReturns: Number of Returns.
4.5.2.4. Metrics Based on Routine Invocations¶
Metric.Number_Of_Invocations
Counts number of different calls within a routine.
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
Metric.Number_Of_Calling_Routines
Counts for a given routine R the number of distinct routines that call R.
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
Metric.Number_Of_Called_Routines
Counts for a given routine R the number of distinct routines that R calls.
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
Metric.Number_Of_Parameters
Number of formal parameters for a given routine R. Note that this number
includes types in parameters lists that are not actually represented in the dependency
graph, such as primitive types like int.
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
We give an example using four different functions:
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
// Example for Metric.Number_Of_Invocations,
// Metric.Number_Of_Calling_Routines
// Metric.Number_Of_Called_Routines
void a()
{}
// Metric.Number_Of_Invocations = 0
// Metric.Number_Of_Calling_Routines = 2 (c() and main())
// Metric.Number_Of_Called_Routines = 0
void b()
{}
// Metric.Number_Of_Invocations = 0
// Metric.Number_Of_Calling_Routines = 1 (c())
// Metric.Number_Of_Called_Routines = 0
void c()
{
a();
b();
a();
}
// Metric.Number_Of_Invocations = 3 (2 times a(), one time b())
// Metric.Number_Of_Calling_Routines = 1 (main())
// Metric.Number_Of_Called_Routines = 2 (a() and b())
int main(void)
{
a();
a();
c();
a();
}
// Metric.Number_Of_Invocations = 4 (3 times a(), one time c())
// Metric.Number_Of_Calling_Routines = 1 (artificial entry call)
// Metric.Number_Of_Called_Routines = 2 (a() and c())
Available rules:
CallgraphMetrics: Metrics based on routine calls.
Metric-NumberOfParameters: Number of Parameters.
4.5.3. Fan Metrics¶
Metric.Fan.In.Call
Number of incoming edges of type
Call.
Metric.Fan.Out.Call
Number of outgoing edges of type
Call.
The rules Metric-Fan.In.Call and Metric-Fan.Out.Call both support
calculating the metrics for a different edge types by changing the edge_type option.
Setting edge_type=T, where T is a valid edge type, will also change the name
of the reported metric to Metric.Fan.In.T and Metric.Fan.Out.T respectively.
Metric.StabilityIndex
Measures the stability of components based on their fan-in and fan-out values.
Available rules:
FanMetrics: Metrics to count the number of incoming or outgoing edges.
4.5.4. Metrics based on Code Complexity¶
This section describes metric to measure source code complexity.
4.5.4.1. Metrics for Nesting¶
Metric.Maximum_Nesting
Counts the maximum nesting level of syntactic levels inside a routine.
Metric.Maximum_Extended_Nesting
Metric.Maximum_Extended_Nesting differs from
Metric.Maximum_Nesting by its treatment of else if-statements:
Metric.Maximum_Extended_Nesting increases the nesting level by one for
each else if-statement.
The following example illustrates the computation of the nesting metrics:
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
// Example for
// Metric.Maximum_Nesting,
// Metric.Maximum_Extended_Nesting
int doit(int x)
{
// Nesting depth 1
if (x >= 1)
{
// Nesting depth 2
while (x > 0)
{
// Nesting depth 3
// (extremal value for Maximum_Nesting)
x--;
}
return x;
}
else if (x <= -5)
{
// Nesting depth 3 for Maximum_Extended_Nesting
// Nesting depth 2 for Maximum_Nesting
return 2;
}
else if (x <= -2)
{
// Nesting depth 4 for Maximum_Extended_Nesting (extremal value)
// Nesting depth 2 for Maximum_Nesting
return 2;
}
else
{
// Nesting depth 2
return 1;
}
}
// Metric.Maximum_Nesting = 3
// Metric.Maximum_Extended_Nesting = 4
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
Available rules:
Metric-MaximumNesting: Maximum Nesting.
Metric-MaximumExtendedNesting: Maximum Extended Nesting.
4.5.4.2. Variants of Cyclomatic (McCabe) Complexities¶
Metric.McCabe_Complexity
Computes the number of nodes in the control flow graph of a routine that have more than one outgoing edge.
For nodes originating in switch-statements, each nonempty case section
is counted. It is also known as cyclomatic complexity; its value is identical to the
one given by metric Metric.HIS.vG.
Roughly speaking, the value Metric.McCabe_Complexity is computed by
summing up
the number of
if-statements,the number of loop statements (e.g.
forandwhile),the number of conditional (
?) operators,the number of
catchclauses, andthe number of nonempty
case-sectionsplus the value one.
So Metric.McCabe_Complexity is always greater zero.
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
Metric.Extended_McCabe_Complexity
Metric.Extended_McCabe_Complexity differs from
Metric.McCabe_Complexity in its treatment of short circuit logical
operators: it also interprets these operators as resulting in control flow nodes having
more than one outgoing edge and therefore adds also the number of shortcut operators (
&& and ||) to the value.
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
Metric.Switch_Complexity
A third variant, Metric.Switch_Complexity, does not count short circuit
logical operators. In addition, the case-sections of a
switch-section are not counted. Instead, each occurring
switch-statement as a whole increases the value of
Metric.Switch_Complexity by one.
Consider the following example:
void some_function(void)
{
if (a && b) {
switch (c) {
case 1: /* ... */ break;
case 2: /* ... */ break;
case 3: /* ... */ break;
default: /* ... */ ;
}
}
while (a || b) {
// ...
}
}
Here the value of Metric.Switch_Complexity is 4, the value of
Metric.McCabe_Complexity is 7, and the value of
Metric.Extended_McCabe_Complexity is 9.
Another example, explaining the computation of Metric.McCabe_Complexity in
more detail:
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
// Example for
// cyclomatic complexity
// (= Metric.McCabe Complexity = Metric.HIS.VG),
// Metric.Extended_McCabe_Complexity,
// Metric.Switch_Complexity
int foo(int x, int y)
{
// start value = 1 (1 path)
while (x > y) // +1: 2 successors: to loop body and exit
{
if (x) // +1: 2 successors, then and (implicit) else
{
x = x+1;
if (y) // +1: 2 successors, then and else
{
x++;
}
else
{
x--;
}
}
}
y = (x > 0) ? 1 : 2; // + 1: 2 successors (like if)
switch(x)
{
case 1: // + 1: one nonempty switch case
break;
case 2: // +0: this is just a fallthrough, does not count
case 3: // +1: this is a nonempty switch case
break;
case 4: // +1: one nonempty switch case
break;
default: // +1: default case
break;
}
// +1 ONLY for Metric.Switch_Complexity (for entire switch statement)
x = y && x; // +1 ONLY for Metric.Extended_McCabe_Complexity
// Note: the shortcut operator ("&&" or "||") actually leads
// to a branching control flow section,
// however, Metric.McCabe_Complexity does not count
// this branching, only Metric.Extended_McCabe_Complexity does.
// Metric.McCabe_Complexity = 1+1+1+1+1+(1+1+1+1) = 9
// Metric.Extended_McCabe_Complexity = 1+1+1+1+1+(1+1+1+1)+1 = 10
// Metric.Switch_Complexity = 1+1+1+1+1+1 = 6
return x;
}
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
Available rules:
Metric-McCabeComplexity: McCabe Cyclomatic Complexity.
Metric-ExtendedMcCabe: Extended Cyclomatic Complexity.
Metric-SwitchComplexity: Switch Complexity.
4.5.4.3. Other Control-Flow-Based Metrics¶
Metric.NPath
Computes the number of maximal acyclic execution paths within a function. It corresponds
to the metric Metric.HIS.PATH. The rules for each syntactical construct
can be found in “NPATH: a measure of execution path complexity and its applications”
(Brian A. Nejmeh, Communications of the ACM, Volume 31 Issue 2, Feb. 1988).
Caution
The values of this metric can be very high and will be cut off at 2147483647
(the highest possible value for any integer metric in the RFG). See also
Metric.LogNPath_Floor resp. Metric.LogNPath_Ceiling for
logarithmic representations of NPATH.
A function consisting of only a simple if-statement, like in the following
example:
void f(int input)
{
if (input) { printf("A"); }
}
has an Metric.NPath value of 2, corresponding to two possible maximal
acyclic paths in the function: one printing A and the other not printing
anything at all. Consider the following function:
void g(int input1, int input2)
{
if (input1) { printf("1"); }
if (input2) { printf("2"); }
}
Here the value of Metric.NPath is 4: One maximal acyclic execution path
prints 1, 2, another prints 1, another one prints 2, and
one path does not print anything at all.
The following example shows how the metric value can be computed by examining each code
construct (referred to as NP in the comments):
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
// Example for
// Metric.NPATH (= Metric.HIS.PATH)
void doit_1(int test1, int test2, int test3, int test4)
{
if(test1)
{}
else
{}
// NP = NP(if-range) + NP(if-expr) + NP(else-range) = 1+0+1 = 2;
if(test1)
{}
else
{}
// NP = OLD_NP * (NP(if-range) + NP(if-expr) + NP(else-range))
// = 2 * 2 = 4
if (test1)
{}
// NP = OLD_NP * (NP(if-range) + 1) = 4 * 2 = 8
if (test1)
{}
// NP = OLD_NP * (NP(if-range) + 1) = 8 * 2 = 16
if (test1)
{
if (test1)
{}
// NP for inner part: 2
}
// NP = OLD_NP * (NP(if-range) + 1) = 16 * (2+1) = 48
// Metric.NPATH = 48
}
For computing Metric.NPath for a statement if expr { then-part }
else { else-part }, the metric values obtained for the expr, then-part,
else-part have to be summed up. For two subsequent statements statement1
statement2, the metric values for statement1 and statement2 have to be
multiplied (in the example, the metric value for all statements before the current
statement is referred to as OLD_NP. Therefore, many branching statements like
if-statements or loops lead to a large value of Metric.NPath.
Metric.LogNPath_Floor, Metric.LogNPath_Ceiling
If n denotes the number of maximal acyclic execution paths within a function
(see Metric.NPath), the metric
Metric.LogNPath_Floorcomputes log₂(n) rounded down to the nearest integer andMetric.LogNPath_Ceilingcomputes log₂(n) rounded up to the nearest integer.
If n surpasses a value of approximately 10³⁰⁸, the value
2147483647 is stored as value for both Metric.NPath.LogNPath_Floor
and Metric.LogNPath_Ceiling.
Metric.CognitiveComplexity
Measures complexity counting on the use of Control-Flow influencing statements with consideration of the depth of their nesting. Also taking recursive calls into account.
Note
The metric values might be influenced by configuration values. Please refer to Options influencing the concrete metric values for an example.
Available rules:
Metric-NPath: NPath.
Metric-LogNPathCeiling: LogNPath_Ceiling.
Metric-LogNPathFloor: LogNPath_Floor.
Metric-CognitiveComplexity: Cognitive Complexity. (C/C++ only)
4.5.5. Metrics for Object-Oriented-Systems¶
This section describes metrics specifically designed for Object-Oriented-Systems.
Metric (Attribute) |
Description |
|---|---|
Lack of cohesion in methods (LCOM) ( |
Count of function member pairs of a class that have no similarity regarding the common use of data members. |
Lack of cohesion in methods (LCOM) ( |
Alternative version of LCOM based on the definition by Hitz and Montazeri. |
Lack of cohesion in non-static methods (LCOMs) ( |
Count of function member pairs of a class that have no similarity regarding the common use of data members. Do not count static data members. |
Coupling between objects (CBO) ( |
Number of classes that are coupled to a class C, i.e., the size of the union set of classes that are using fields and methods of C and the classes that are used by C’s fields and methods (so both directions count here). |
Coupling between objects (excluding ancestors) (CBOa) ( |
Like CBO but ancestor classes of a class are not taken into account. |
Coupling between objects (excluding statics) (CBOs) ( |
Like CBO but static data members are not taken into account when looking at the dependencies to and from other classes. |
Data Abstraction Coupling (DAC) ( |
Number of attributes in a class that have another class as their type. |
Response for a class (RFC) ( |
The response of a class is the count of methods of the class itself and all methods/functions called by the methods of the class (i.e., we take one level of the call tree into account). Inherited but not overridden methods are not taken into account; the potential fan out generated by calls to virtual functions is also not taken into account. |
Depth of Inheritance (DIT) ( |
DIT of a class is the length of the longest path from class to the root in the inheritance hierarchy. Classes that do not inherit anything have a DIT of 0. |
Weighted Methods of a Class (WMC) ( |
Sum over all weighted defined or overridden methods of a class where 1 / the metric |
Number of Child Classes (NOCC) ( |
Number of classes that directly inherit from a given class. Example: class A {};
class B {};
class C : A, B {};
class D : A {};
class E : C, D {};
For this example, class A has NOCC of 2 (classes C and D), class B has NOCC of 1 (class C is its only child), classes C and D have a NOCC of 1 (class E is their only child). Class E has a NOCC of 0 (no children at all). |
Number Of Ancestors (NOA) ( |
Number of classes that a given class directly or indirectly inherits from. For the example code at NOCC, class A and B have no ancestors, so NOA is 0. Class C has ancestors A and B, so NOA is 2. Class D has only one ancestor A, so NOA is 1. Class E has ancestors A, B, C, and D, so NOA is 4. |
Number of Parent Classes (NOPC) ( |
Number of classes that a given class directly inherits from. For the example code at NOCC, classes A and B have no parent classes, class D has one direct parent (class A), and classes C and E have two direct parents. |
Number of Added Methods (NOMA) ( |
Number of function members (including operators, constructors, and destructors) that a given class adds. |
Number of Methods Overridden (NOMO) ( |
Number of function members that a given class overrides. |
Number of Invocations (NIVOC) ( |
Calculates the number of routines called inside a routine. |
Available rules:
OOPMetrics: Metrics for object-oriented systems.
4.5.6. Metrics based on common Rulesets¶
This section describes metrics from common rulesets like Halstead and HIS.
4.5.6.1. Halstead Metrics¶
Halstead metrics have been introduced in “Elements of software science” (M. H. Halstead, Elsevier, 1977). All Halstead metrics are computed using the following four base metrics:
Metric.Halstead.Total_Operators:the total number of occurring operators within a code entity, which we abbreviate by N₁,Metric.Halstead.Total_Operands:the total number of occurring operands within a code entity, which we abbreviate by N₂,Metric.Halstead.Different_Operators:the number of distinct operators within a code entity:, which we abbreviate by η₁,Metric.Halstead.Different_Operands:the total number of occurring operands within a code entity, which we abbreviate by η₂.
The terms operands and operators are not precisely defined in the literature, and so
it is to the implementor to find a suitable interpretation for them. This has to be
taken into account when e.g. using a threshold for one of the metrics. Most importantly,
the Axivion Suite considers “ {“, “ }“, “ (“, ”
)“, “ [“, “ ] as well as “ ;“ and “ ,”
as (distinct) operators. So, the code piece
void foo()
{
if (1) {} /* A comment. */
}
contains
the operators “
{“ (2 times), “}“ (2 times), “(“ (2 times), “)“ (2 times), “if“ (1 time), “void“ (1 time)and the operands “
1“ (1 time) and “foo(1 time)”.
Therefore N₁ = 10, N₂ = 2, η₁=6, η₂=2.
The Axivion Suite works on token-level to compute the number of operators and operands; each token is considered an operator, an operand, or neither. More precisely, the following entities are counted as operands:
literals, in particular character literals, float literals, integer literals, string literals, and
identifier.
Operators comprise all other tokens with the exception of preprocessor directives (like
#if etc.). Comments are not taken into account at all.
The operator_blacklist option of the HalsteadMetrics rulegroup can be used to
to configure tokens that should not be treated as operators by the Halstead metrics.
The following metrics are computed using the base metrics above for files, types, and routines:
Metric.Halstead.Difficulty, defined by D := η₁/2 · N₂/η₂,Metric.Halstead.Length(program length), defined by N := N₁ + N₂,Metric.Halstead.Vocabulary, defined by η:= η₁ + η₂,Metric.Halstead.Volume, defined by V := N · log₂(η),Metric.Halstead.Effort, defined by E := D · V,Metric.Halstead.Vocabulary_Frequency[1], defined by F := N/η.
Available rules:
HalsteadMetrics: Halstead complexity metrics.
4.5.6.2. HIS Metrics¶
The HIS Source Code metrics (version 1.3.1), a document published by the Hersteller
Initiative Software, describes a collection of
metrics together with recommendations for their admissible ranges. In the following we
call these metrics HIS metrics. They are widely used, particularly in the automotive
industry. We describe which HIS metrics the Axivion Suite supports, and how HIS metrics
can be mapped to base metrics computed by the Axivion Suite. For example,
the COMF metric corresponds to the base metric
Metric.Comment.Density. The value
of Metric.HIS.COMF at a fixed RFG node n is the same as the value
of Metric.Comment.Density of n. Unfortunately, the definition of
HIS metrics in the reference document is not too precise, so sometimes one has to choose
between two possible base metrics.
HIS metric |
Corresponding base metric |
Alternative attribute name |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HIS metric ap_cg_cycle:
This metric counts the number of call-graph recursions. Its value is given in the HIS metrics report.
HIS metrics NOMV and NOMVPR:
These metrics count the numbers of style violations reported for rules of the MISRA:C standard.
Differential HIS metrics:
These HIS metrics compute differences between two versions of the software. They are not yet supported by the Axivion Suite .
Available rules:
HISMetrics: Hersteller Initiative Software metrics
4.5.6.3. Code-Quality-Management Metrics¶
The Code-Quality-Management metrics, henceforth referred to as CQM metrics, are defined in the 2006 book of the same name by Dr. Frank Simon, Dipl.-Inform Olaf Seng and Dipl.-Inform. Thomas Mohaupt. The metrics offer a comprehensive list to measure various source code characteristics based around complexity and maintainability.
Due to the nature of the metrics, they are partially implemented as Stylecheck rules to report the indivitual violations.
Available rules:
CQMMetrics: Code-Quality-Management metrics. (Rules requiring an IR are C/C++ only.)
CQM: Code-Quality-Management Stylechecks. (Rules requiring an IR are C/C++ only.)
4.5.7. Configuration Options¶
The following configuration options are common to almost all metric rules with the exception of a few special rules that include results from other sources or count the number of violations.
4.5.7.1. Common Configuration Options¶
Base view (
base_view_name): The view on which the metric is calculated on and values are stored as node attributes in.Minimal value (
min_value) and Maximum value (max_value): The range of allowed values for the metric. All values outside of this range are reported as metric findings.Excluded node types (
excluded_node_types): RFG node types the metric should not report findings on.Report all values (
report_all_values): Whether all metric values should be reported or only ones outside the range betweenmin_valueandmax_value.Display name (
display_name): Description of the metric shown in the dashboard.RFG metric (
rfg_metric_name): Name of the node attribute storing the metric value in the RFG. Split at each.and common parts are merged together in the RFG.
4.5.7.2. Options influencing the concrete metric values¶
Several configuration values have an impact on how code constructs are counted for particular metrics. In this section we describe these effects in detail.
Value Metrics.opaque_macros:
if this configuration value is set to true, elements of opaque macro invocations are excluded from the computation for the following metrics:
Metric.Number_Of_Statements(also namedMetric.HIS.STMT),Metric.Number_Of_Gotos(also namedMetric.HIS.GOTO) ,Metric.Number_Of_Returns(also namedMetric.HIS.RETURN),Metric.Number_Of_Invocations,Metric.McCabe_Complexity(also namedMetric.HIS.VG) /Metric.Extended_McCabe,Metric.Switch_Complexity,Metric.CognitiveComplexity,Metric.Maximum_NestingorMetric.Maximum_Extended_Nesting(also namedMetric.HIS.LEVEL), andMetric.NPath(also namedMetric.HIS.PATH).
A macro is opaque if it is included in one of the globbing patterns given by the
rule option Metrics.macro_library_patterns. If that option
is left empty, all macros are considered opaque. Consider the following example:
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
int global = 1;
#define INVOKE(x) { if (global>x) { f1(); } else { return 1; }}
void f1() {}
int main(void)
{
INVOKE(20);
INVOKE(30);
return 0;
}
If INVOKE is an opaque macro and
Metrics.opaque_macros is set to true, the metric
values for main() are computed similar to the following program:
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
int global = 1;
extern void _invoke(int x);
void f1() {}
int main(void)
{
_invoke(20);
_invoke(30);
return 0;
}
// Metric values:
// Metric.Number_Of_Statements / Metric.HIS.STMT = 3
// Metric.Number_Of_Gotos / Metric.HIS.GOTO = 0
// Metric.Number_Of_Returns / Metric.HIS.RETURN = 1
// Metric.Number_Of_Invocations = 2
// Metric.McCabe_Complexity / Metric.HIS.VG = 1
// Metric.Switch_Complexity = 1
// Metric.Maximum_Nesting = 1
// Metric.Maximum_Extended_Nesting / Metrich.HIS.LEVEL = 1
// Metric.NPATH / Metric.HIS.PATH =
I.e., the invocation of opaque macros within a routine is interpreted as a call, and
therefore the code within the macro body is not considered. If INVOKE is not an
opaque macro or Metrics.opaque_metrics is set to false
(the default), the metric values for main() is computed as for the following
program:
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
int global = 1;
void f1() {}
int main(void)
{
{ if (global>20) { f1(); } else { return 1; }};
{ if (global>30) { f1(); } else { return 1; }};
return 0;
}
// Metric values:
// Metric.Number_Of_Statements / Metric.HIS.STMT = 9
// Metric.Number_Of_Gotos / Metric.HIS.GOTO = 0
// Metric.Number_Of_Returns / Metric.HIS.RETURN = 3
// Metric.Number_Of_Invocations = 2
// Metric.McCabe_Complexity / Metric.HIS.VG = 3
// Metric.Switch_Complexity = 3
// Metric.Maximum_Nesting = 3
// Metric.Maximum_Extended_Nesting / Metrich.HIS.LEVEL = 3
// Metric.NPATH / Metric.HIS.PATH = 4
Here all macro invocations have been replaced by the macro expansion.
Value Architecture-Dependencies.macro_options.represent_macros :
if this configuration value is false (the default),
neither Macro_Invocation edges nor
nodes of type Macro are generated within the RFG.
This impacts the metric values of
Metric.Number_Of_Calling_Routines(also namedMetric.HIS.CALLING), andMetric.Number_Of_Called_Routines(also namedMetric.HIS.CALLS).
Consider the following example:
// Copyright (C) 2025 Axivion GmbH
// Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
#define INVOKE f1()
void f1() {}
int main(void)
{
INVOKE;
return 0;
}
and the following two scenarios:
if
Architecture-Dependencies.macro_options.represent_macrosis set to false, no node of typeMacro_Invocationis created for the macro invocation in line 7, instead aStatic_Calledge is created betweenmain()andf1(). Therefore in this case,Metric.Number_Of_Called_Routinesis 1 formainandMetric.Number_Of_Calling_Routinesis 1 forf1().if
Architecture-Dependencies.macro_options.represent_macrosis set to true, line 7 is represented by aMacro_Invocationedge to a node of typeMacroin the RFG. There is no outgoing call edge frommain()tof1(), and soMetric.Number_Of_Called_Routinesis 0 formainandMetric.Number_Of_Calling_Routinesis 0 forf1().
4.5.8. Metric Results¶
Metric values are generally stored as attributes in the RFG on the Nodes a metric applies to, i.e.
Metric.Lines.Routine.LOC can be found on
Routine nodes,
but not on Nodes of type
File or
Class.
Metric rules can also report findings that can be viewed in the “Metrics” view in the dashboard denoted
by the
symbol and the prefix MV.
By default only findings outside the range between the min_value and max_value options are reported
by setting report_all_values=true for the individual metric rule or a whole metric rulegroup.
And additionally Analysis.message_format.metric.show_all_values has to be enabled as well.
Note
Some metrics are only calculated for the whole system and their values are stored as attributes of the special System RFG node.
4.5.8.1. Suppressing Metric Findings¶
Metric
MV findings can be suppressed via comments
in code. By default, the comment should start with AXIVION Metric or
AXIVION Metrics, but this can be configured.
For more information, see Justifications and Suppressions with Code Annotations.
4.5.9. Metric Value Propagation¶
Metrics propagation refers to the process of aggregating metric values upwards along the hierarchy of enclosing RFG nodes.
Meaning that metric values are propagated upwards and aggregated at enclosing nodes, so that a
Module node
may have the Metric.Lines.Routine.LOC.sum attribute for the sum of Metric.Lines.Routine.LOC
from all routines inside it.
4.5.9.1. Configuration Options¶
The following configuration options are present on all metric rules to configure the propagation.
Hierarchy Edge (
hierarchy_edge_name): The type of edges the aggregation follows in the hierarchy view.Hierarchy View (
hierarchy_view_name): The view used to propagate the metric values. Can be any view that shares its nodes with the base view a metric is calculated on.Propagate (
propagate): Whether propagation should be performed.Report propagated values (
report_propagated_values): Whether propagated metric values should be reported or only stored in the RFG.
4.5.9.2. Propagated Metric Values¶
The propagation will add the following attributes to the RFG for each metric present.
Average (
*.avg): The average metric value over all covered nodes. Calculated withsum / cnt.Count (
*.cnt): The number of nodes the propagation covers.Maximum (
*.max): The maximum metric value from all covered nodes.Minimum (
*.min): The minimum metric value from all covered nodes.Standard Deviation (
*.std): The standard deviation by means of the sum of squares and count. Calculated withsqrt(max(0, 1 / (cnt - 1) * (ssum - 1 / cnt * sum * sum))).Sum (
*.sum): The sum of all metric values from all covered nodes.Sum of Squares (
*.ssum): The sum of squared metric values from all covered nodes.
This example shows how the propagation to the
Module level looks like for the 3 routines:
// Copyright (C) 2026 The Qt Company GmbH, a subsidiary of The Qt Group
// https://www.qt.io
// Example for Metric.Lines.Routine.LOC and metric propagation
// Metric.Lines.Routine.LOC = 3
int doit_a(int a, int b) {
return a + b;
}
// Metric.Lines.Routine.LOC = 7
int doit_b(int a) {
if(a == 0) {
return 1;
} else {
return a * doit_b(a-1);
}
}
// Metric.Lines.Routine.LOC = 9
int doit_c(int a) {
if(a == 0){
return 0;
} else if(a == 1) {
return 1;
} else {
return (doit_c(a-1) + doit_c(a-2));
}
}
// Propagated values on the file node:
// Metric.Lines.Routine.LOC.avg = 6.333333
// Metric.Lines.Routine.LOC.cnt = 3
// Metric.Lines.Routine.LOC.max = 9
// Metric.Lines.Routine.LOC.min = 3
// Metric.Lines.Routine.LOC.ssum = 139
// Metric.Lines.Routine.LOC.std = 3.055050
// Metric.Lines.Routine.LOC.sum = 19