Metrics
Detailed description of metrics provided by CODESYS Static Analysis
Tip
The Code size, Variable size, Stack size, and Number of calls metrics are reported only for POUs from libraries which are integrated in the project.
Metric: Code size (number of bytes)
Categories: Informative, Efficiency
Number of bytes which a function block contributes to the application code
The number also depends on the code generator. For example, the code generator for ARM processors generally generates more bytes than the code generator for x86 processors.
Metric: Variable size (number of bytes)
Categories: Informative, Efficiency
Size of the static memory which is used by the object
For function blocks, this is the size which is used for an instance of the function block (which may include memory gaps, depending on the memory alignment). For programs, functions, and global variable lists, this is the sum of the size of all static variables.
Example
FUNCTION FUN1 : INT VAR_INPUT a,b : INT; END_VAR VAR c,d : INT; END_VAR VAR_STAT f,g,h : INT; END_VAR
The function has 3 static variables of type INT
(f
, g
, and h
), each of which requires 2 bytes of memory. As a result, FUN1
has a variable size of 6 bytes.
Metric: Stack size (number of bytes)
Categories: Informative, Efficiency, Reliability
Number of bytes which are required for calling a function or a function block
Input variables and output variables are aligned to the memory. This can create a gap between these variables and the local variables. This gap is counted.
Return values of called functions which do not fit into a register, are pushed onto the stack. The largest of these values determines the additional allocated memory, which also counts. Functions or function blocks which are called within the POUs under consideration have their own stack frame. Therefore, the memory for such calls does not count.
Depending on the code generator used, intermediate results of calculations also use the stack. These results are not counted.
//Declaration FUNCTION FUN1 : INT VAR_INPUT a,b : INT; END_VAR VAR c,d,e : INT; END_VAR VAR_STAT f,g,h : INT; END_VAR //Implementation c := b; d := a; e := a+b;
Assumption: For the calculation, assume a CODESYS Control Win which uses the x86 code generator.
The above example has a caller size of 8 bytes: 4 bytes for the two INT inputs
and 4 bytes for the return value. The device has a stack alignment of 4 bytes, so that there is a gap of 2 bytes. The caller size is 8 bytes: three local variables with 2 bytes each plus the 2 byte gap for stack alignment. As a result, the total stack size of FUN1
is 16 bytes.
VAR_STAT
is not stored on the stack and therefore does not increase the stack size of a POU.
Metric: Number of calls (Calls)
Category: Informative
Number of calls of the POU under Program unit
//Declaration PLC_PRG PROGRAM PLC_PRG VAR myFB : FB1; END_VAR //Implementation myFB(b := FALSE);
//Declaration FB1 FUNCTION_BLOCK FB1 VAR_INPUT b : BOOL; END_VAR VAR i : INT; END_VAR //Implementation METH(i); IF b THEN METH(i*i); END_IF
//Declaration FB1.METH METHOD METH : BOOL VAR_INPUT i : INT; END_VAR //Implementation METH := i >= 42;
If PLC_PRG
is called in a task, then this call is also counted.
FB1
has exactly one call (in PLC_PRG
).
METH
has two calls, both in FB1
.
Metric: Number of calls from tasks (Tasks)
Categories: Maintainability, Reliability
Number of tasks (Tasks) where the POU specified under Program unit is called
In the case of function blocks, the number of tasks is counted, in which the function block itself or any function block in the inheritance tree of the function block is called.
In the case of methods and actions, the number of tasks is displayed, in which the (parent) function block is called.
FUNCTION_BLOCK FB //...
FUNCTION_BLOCK FB2 EXTENDS FB //...
FUNCTION_BLOCK FB3 EXTENDS FB2 //...
Each function block is called in its own PROGRAM
. Each PROGRAM
has its own task.
The Called in tasks metric returns in 1 for FB3
and 2 for FB2
because the calls from FB3
and FB2
are counted. The metric results in 3 for FB
because in this case the calls from FB3
, FB2
, and FB
are counted.
Metric: Number of global variables used (Globals)
Categories: Maintainability, Reusability
Number of used global variables in the POU under Program unit
//GVL VAR_GLOBAL gvla : INT; gvlb : INT; gvlc : INT; END_VAR
/PRG declaration PROGRAM PRG VAR x : INT := GVL.gvlc; y : INT; END_VAR //PRG implementation x := GVL.gvla; y := GVL.gvla*GVL.gvlb;
The PRG
program uses 3 variables from GVL
: gvla
, gvlb
, and gvlc
.
Metric: Number of direct address accesses (IOs)
Categories: Reusability, Maintainability
Number of direct address accesses (IOs) in the implementation of the object.
//Declaration PROGRAM PRG VAR xVar : BOOL:= %IX0.0; // +1 direct address access byVar : BYTE; END_VAR //Implementation xVar := %IX0.0; // +1 direct address access %QX0.0 := xVar; // +1 %MX0.1 := xVar; // +1 %MB1 := byVar; // +1
The example has 5 direct address accesses.
Metric: Number of local variables (Locals)
Categories: Informative, Efficiency
Number of variables declared in the VAR
area of the POU. Inherited variables are not counted.
//Declaration FUNCTION_BLOCK FB VAR_INPUT END_VAR VAR_OUTPUT END_VAR VAR i,j,k,l : INT; m,n,o : BOOL; END_VAR
In the function block, 7 local variables are declared.
Metric: Number of input variables (Inputs)
Categories: Maintainability, Reusability
Default upper limit for the corresponding SA0166 rule: 10
Number of variables declared in VAR_INPUT
of the program unit. Inherited input variables are not counted.
FUNCTION_BLOCK FB VAR_INPUT i : INT; r : REAL; END_VAR
In the function block, 2 input variables are declared: i
and r
.
METHOD METH : BOOL VAR_INPUT j : INT; l : LREAL; END_VAR
The method has 2 inputs: j
and l
Metric: Number of output variables (Outputs)
Categories: Maintainability, Reusability
Default upper limit for the corresponding SA0166 rule: 10
Number of variables in VAR_OUTPUT
of the program unit
In the case of function blocks, this is the number of custom output variables (VAR_OUTPUT
). In the case of methods and functions, this is the number of custom output variables plus one if they have a return value. The return value is also counted. Inherited output variables are not counted.
A high number of output variables is an indication of a violation of the principle of unique responsibility.
FUNCTION_BLOCK FB VAR_OUTPUT i : INT; // +1 output r : REAL; // +1 output END_VAR
The function block has 2 output variables: i
and r
METHOD METH : BOOL VAR_INPUT j : INT; l : LREAL; END_VAR
The method has 3 outputs: METH
, j
, and l
METHOD METH1 // +0 outputs (no return type) VAR_OUTPUT ar : ARRAY[0..10] OF INT; // +1 output l : LREAL; // +1 output END_VAR
The METH1
method has 2 outputs: ar
and i
Metric: NOS – Number Of Statements
Category: Informative
Number of statements in the implementation of a function block, function, or method
Statements in the declaration, empty statements, or pragmas are not counted.
//Declaration: FUNCTION POU : BOOL VAR_INPUT END_VAR VAR c : INT := 100; // statements in the declaration are not counted END_VAR VAR_OUTPUT test : INT; i : INT; END_VAR //Implementation: IF TRUE THEN //if statement: +1 test := 0; // +1 END_IF WHILE test = 1 DO //while statement: +1 ; // empty statements do not add to the statement count END_WHILE FOR c := 0 TO 10 BY 2 DO //for statement: +1 i := i+i; // +1 END_FOR {text 'simple text pragma'} //pragmas are not counted test := 2; //+1
The example has 6 statements.
Metric: Percentage of comment
Category: Maintainability
Percentage of comments in source code
This number is calculated according to the following formula:
Percentage = 100 * <characters in comments> / <sum of characters in source code and characters in comments>
Multiple consecutive spaces in the source code are counted as one space, which prevents high weighting of indented source code. For empty objects (no source code and no comments), a percentage of 0 is returned.
Declaration part:
FUNCTION_BLOCK FB //comments in the declaration are counted, as well VAR_TEMP hugo : INT; END_VAR
Implementation:
hugo := hugo + 1; //Declaration: 40 letters non comment; 50 letters comment //Implementation: 13 letters non comment; 152 letters comment // 100 * 202 / 255 -> 79% comments
The calculation of the percentage 100 * 202 / 255 returns 79%.
Metric: Complexity (McCabe)
Category: Testability
Recommended upper limit: 10
The cyclomatic complexity according to McCabe is a measure of the readability and testability of source code. It is calculated by counting the number of binary branches in the control flow of the POU. However, cyclomatic complexity penalizes high branching because high branching increases the number of test cases required for high test coverage.
IF
statement// every POU has an initial cyclomatic complexity of 1, since it has at least 1 branch IF b1 THEN // +1 for the THEN branch ; ELSIF b2 THEN // +1 for the THEN branch of the IF inside the else ; ELSE IF b3 OR b4 THEN // +1 for the THEN branch ; END_IF END_IF
The code snippet has a cyclomatic complexity of 4.
CASE
statement// every POU has an initial cyclomatic complexity of 1, since it has at least 1 branch CASE a OF 1: ; // +1 2: ; // +1 3,4,5: ; // +1 ELSE // the ELSE statement does not increase the cyclomatic complexity ; END_CASE
The code snippet has a cyclomatic complexity of 4.
// every POU has an initial cyclomatic complexity of 1, since it has at least 1 branch WHILE b1 DO // +1 for the WHILE loop ; END_WHILE REPEAT // +1 for the REPEAT loop ; UNTIL b2 END_REPEAT FOR a := 0 TO 100 BY 2 DO // +1 for the REPEAT loop ; END_FOR
The code snippet has a cyclomatic complexity of 4.
The following statements also increase the cyclomatic complexity:
//Declaration FUNCTION FUN : STRING VAR_INPUT condition_return : BOOL; condition_jmp : BOOL; END_VAR VAR END_VAR //Implementation // every POU has an initial cyclomatic complexity of 1, since it has at least 1 branch JMP(condition_jmp) lbl; //Conditional jumps increase the cyclomatic complexity by 1 FUN := 'u'; RETURN(condition_return); //Conditional returns increase the cyclomatic complexity by 1, too lbl: FUN := 't';
The code snippet has a cyclomatic complexity of 3.
Metric: Cognitive Complexity
Category: Maintainability
Default upper limit for the corresponding SA0178 rule: 20
Cognitive complexity is a measure of the readability and understandability of source code as introduced by Sonarsource™ in 2016. However, it penalizes heavy nesting of the control flow and complex Boolean expressions. Cognitive complexity is calculated only for structured text implementations.
The following examples show how cognitive complexity is calculated.
Tip
The Show Cognitive Complexity for Current Editor command can be used to additionally display the increments for structured text.
Statements which manipulate control flow increase the cognitive complexity by 1
IF TRUE THEN // +1 cognitive complexity ; END_IF WHILE TRUE DO //+1 cognitive complexity ; END_WHILE FOR i := 0 TO 10 BY 1 DO //+1 cognitive complexity ; END_FOR REPEAT //+1 cognitive complexity ; UNTIL TRUE END_REPEAT
The code snippet has a cognitive complexity of 4.
When nesting the control flow, an increment of 1 is added for each level of nesting.
IF TRUE THEN //+1 cognitive complexity WHILE TRUE DO //+2 (+1 for the loop itself, +1 for the nesting inside the IF) FOR i := 0 TO 10 BY 1 DO //+3 (+1 for the FOR loop itself, +2 for the nesting inside the WHILE and the IF) ; END_FOR END_WHILE REPEAT //+2 (+1 for the loop itself, +1 for the nesting inside the IF) ; UNTIL TRUE END_REPEAT END_IF
The code snippet has a cognitive complexity of 8.
Because Boolean expressions play a major role in understanding source code, they are also taken into account when calculating cognitive complexity.
Understanding Boolean expressions which are associated with the same Boolean operator is not as difficult as understanding a Boolean expression which contains alternating Boolean operators. Therefore, any chain of identical Boolean operators in an expression increases cognitive complexity.
b := b1; //+0: a simple expression, containing no operators, has no increment
The simple expression without an operator has an increment of 0.
b := b1 AND b2; //+1: one chain of AND operators
The expression with an AND
link has an increment of 1.
b := b1 AND b2 AND b3; //+1: one more AND, but the number of chains of operators does not change
The expression has one more AND
. But since it is the same operator, the number of the chain formed with identical operators does not change.
b := b1 AND b2 OR b3; //+2: one chain of AND operators and one chain of OR operators
The expression has a chain of AND
operators and a chain of OR
operators. This results in an increment of 2.
b := b1 AND b2 OR b3 AND b4 AND b5; //+3
The code snippet has an increment of 3.
b := b1 AND NOT b2 AND b3; //+1: the unary NOT operator is not considered in the cognitive complexity
The unary operator NOT
is not considered in cognitive complexity.
Structured text has additional statements and expressions which change the control flow.
The following statements are penalized with an increment of cognitive complexity:
aNewLabel: x := MUX(i, a,b,c); //+1 for MUX operator y := SEL(b, i,j); //+1 for SEL operator JMP aNewLabel; //+1 for JMP to label
EXIT
and RETURN
statements do not increase cognitive complexity.
Metric: DIT – Depth of Inheritance Tree
Category: Maintainability
Number of inheritances until a function block is reached which does not extend any other function block
FUNCTION_BLOCK MyBaseFB // ... FUNCTION_BLOCK AChildFB EXTENDS MyBaseFB // ... FUNCTION_BLOCK AGrandChildFB EXTENDS AChildFB // ...
MyBaseFB
has a DIT of 0 because it is itself a function block which does not extend any other function block.
For AChildFB
, the DIT is 1 because one step is required to get to MyBaseFB
.
AGrandChildFB
has a DIT of 2: One step is needed to AChildFB
and another to MyBaseFB
.
Metric: NOC – Number Of Children
Categories: Reusability, Maintainability
Number of function blocks which extend the given basic function block. Function blocks which indirectly extend a basic function block are not counted.
FUNCTION_BLOCK MyBaseFB // ... FUNCTION_BLOCK AChildFB EXTENDS MyBaseFB // ... FUNCTION_BLOCK AGrandChildFB EXTENDS AChildFB // ...
MyBaseFB
has only one (1) child object: AChildFB
, which in turn has the one child object, AGrandChildFB
. AGrandChildFB
has no child objects.
Metrics: RFC – Response For class
Categories: Maintainability, Reusability
Number of different POUs, methods, or actions which are called and therefore generate a response of the POU specified under Program unit
//Declaration FB1 FUNCTION_BLOCK FB1 VAR d,x,y : INT; END_VAR //Implementation x := METH(d+10); y := FUN(42, 0.815);
//Declaration FB1.METH METHOD METH : INT VAR_INPUT i : INT; END_VAR //Implementation METH := FUN(CUBE(i), 3.1415);
//Declaration CUBE FUNCTION CUBE : INT VAR_INPUT i : INT; END_VAR //Implementation CUBE := i*i*i;
//Declaration Function FUN FUNCTION FUN : INT VAR_INPUT a : INT; lr : LREAL; END_VAR //Implementation FUN := LREAL_TO_INT(lr*10)*a;
Starting with
FUN
andCUBE
, these functions have an RFC of 0 because none of them call other functions, function blocks, or methods for their calculations.FB1.METH
usesFUN
andCUBE
, which results in an RFC of 2.The function block
FB1
itself callsMETH
andFUN
, which increases its RFC by 2.For FB1, its METH method also has to be taken into account. METH uses FUN and CUBE. FUN has already been added to the RFC. Therefore, only the use of CUBE in METH increases the RFC for FB1 to 3
Metric: CBO – Coupling Between Objects
Categories: Maintainability, Reusability
Default upper limit for the corresponding SA0179 rule: 30
Number of other function blocks which are instantiated and used in a function block
A function block with a high coupling between objects is likely to be involved in many different tasks and therefore violates the principle of unique responsibility.
// Declaration FUNCTION_BLOCK FB_Child EXTENDS FB_Base objects // +0 for EXTENDS VAR_INPUT END_VAR VAR_OUTPUT END_VAR VAR i_fb1 : FB1; // +1 instantiated here i_fb2 : FB2; // +1 instantiated here END_VAR //Implementation i_fb3(); // +0 instantiated in FB_Base, no increment for call
The extension of a function block does not increase the coupling between objects.
i_fb3
is instantiated in the implementation ofFB_Base
and passed on to FB_Child (EXTENDS
). The call inFB_Child
does not increase the coupling between the objects.The CBO of
FB_Child
is 2.
Metric: Complexity of reference (Elshof)
Categories: Efficiency, Maintainability, Reusability
Complexity of the data flow of a POU
The referencing complexity is calculated according to the following formula:
<number of variables used> / <number of variable accesses>
Only variable accesses in the implementation part of the POU are considered.
//Declaration PROGRAM PRG VAR i, j : INT; k : INT := GVL.m; b, c : BOOL; myFB : FB; END_VAR //Implementation myFB(paramA := b); // +3 accesses (myFB, paramA and b) i := j; // +2 accesses (i and j) j := GVL.d; // +2 accesses (j and GVL.d)
Referencing complexity in the code snippet results:
6 number of variables used / 7 number of variable accesses = 0.85
Caution:
c
andk
are not used and therefore do not count as "variables used".The assignment
k : INT := GVL.m
is not counted because it is part of the declaration of the program.
Metric: Lack of Cohesion Of Methods – LCOM
Lack of Cohesion Of Methods – LCOM
Categories: Maintainability, Reusability
The cohesion between function blocks, their actions, transitions, and methods describes whether or not they access the same variables.
The lack of cohesion of methods describes how strongly the objects of a function block are connected to each other. The lower the lack of cohesion, the stronger the connection between the objects.
Function blocks with a high lack of cohesion are likely to be involved in many different tasks and therefore violate the principle of unique responsibility.
The metric is calculated according to the following formula:
MAX(0, <number of object pairs without cohesion> - <number of object pairs with cohesion>)
//Declaration FUNCTION_BLOCK FB VAR_INPUT a : BOOL; END_VAR VAR_OUTPUT END_VAR VAR i,b : BOOL; END_VAR //Implementation i := 42; //FB.ACT i:= 0;
//FB.METH Declaration METHOD METH : BOOL VAR_INPUT c : BOOL; END_VAR //Implementation METH := c; i := 1;
//FB.SecondMETH Declaration METHOD SecondMETH : INT VAR_INPUT END_VAR //Implementation SecondMETH := SEL(c,3,4);
Object pairs without connection (4 pairs):
FB,
FB.ACT
FB
,FB.METH
FB.ACT
,FB.SecondMETH
FB.METH
,FB.SecondMETH
Object pairs with connection (2 pairs):
FB
,FB.SecondMETH
(both usec
)FB.ACT
,FB.METH
(both usei
)
FB |
|
|
| |
---|---|---|---|---|
|
| 0 | 0 | . |
| 0 |
| . | . |
| 0 | . | . | . |
| - | . | . | . |
Metric: Number of SFC branches
Categories: Testability, Maintainability
Number of alternative and parallel branches of a POU of the SFC (sequential function chart) implementation language
![]() |
The above code snippet in SFC has 4 branches: 3 alternative branches and 1 parallel branch
Metric: Number of SFC steps
Category: Maintainability
Number of steps in a POU in SFC (sequential function chart)
Only the steps are counted which are contained in the POU programmed in SFC. Steps are not counted which are in the implementations of actions or transitions called in POUs.
![]() |
The code snippet in SFC has 10 steps.