Skip to main content

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.

Example 132. Example
//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

Example 133. Example
//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.

Example 134. Example
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

Example 135. Example
//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.

Example 136. Example
//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.

Example 137. Example
//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.

Example 138. Examples
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.

Example 139. Examples
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.

Example 140. Example
//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.

Example 141. Example

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.

Example 142. Example: 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.



Example 143. Example: 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.



Example 144. Example: Loop statement
// 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.



Example 145. Example: Other statements

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.

Example 146. Example: Control flow

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.



Example 147. Example: Nesting of the control flow

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.



Example 148. Example: Boolean expression

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.



Example 149. Example: Other statements with an increment

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

Example 150. Example
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.

Example 151. Example
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

Example 152. Example
//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 and CUBE, these functions have an RFC of 0 because none of them call other functions, function blocks, or methods for their calculations.

  • FB1.METH uses FUN and CUBE, which results in an RFC of 2.

  • The function block FB1 itself calls METH and FUN, 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.

Example 153. Example
// 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 of FB_Base and passed on to FB_Child (EXTENDS). The call in FB_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.

Example 154. Example
//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 and k 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>)

Example 155. Example
//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 use c)

  • FB.ACT , FB.METH (both use i)

Table 4. The table shows which variables connect which objects of the FB:

FB

FB.ACT

FB.METH

FB.SecondMETH

FB.SecondMETH

c

0

0

.

FB.METH

0

i

.

.

FB.ACT

0

.

.

.

FB

-

.

.

.





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

Example 156. Example
_san_img_metric_sfc_branch_count.png

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.

Example 157. Example
_san_img_metric_sfc_steps_count.png

The code snippet in SFC has 10 steps.