In the preparation phase of our S/4 migration we wanted to identify unused custom code. While there are several different reports from SAP, none of them fulfilled our requirements, so we created an own report to detect unused code.
First of all you need to have SCMON enabled for a rather long period (SAP suggests to have it active for at least 13 months). We created a snapshot of the SCMON via SUSG and exported the snapshot to our development system. The usage data is the basis of our report. We combined this with a remote existence check (to check if the object is also in our productive system).
1. Get all relevant objects to check
This select will get us all objects to check along with needed meta data:
1
2
3
4
5
6
7
8
9
10
11
12
13
| " get the relevant programs/classes/function groups - Local system
SELECT DISTINCT object, tadir~obj_name, progdir~subc AS psubc, progdir~cdat AS pcdat, tcode
FROM tadir
LEFT OUTER JOIN progdir
ON progdir~name EQ tadir~obj_name
LEFT OUTER JOIN tstc
ON tstc~pgmna EQ tadir~obj_name
INTO TABLE @DATA(lt_objname)
WHERE object IN @s_objtyp
AND tadir~obj_name IN @s_objnam
AND tadir~delflag EQ @abap_false
AND tadir~genflag EQ @abap_false
AND tadir~devclass IN @s_devc.
|
2. Check in productive system
If we have a remote RFC system, we will check those objects for existence in the destination:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| METHOD get_remote_objects.
DATA: lf_where_clause TYPE string,
lf_message TYPE char1024.
DATA: lt_options TYPE TABLE OF rfc_db_opt,
lt_temp TYPE TABLE OF rfc_db_opt,
lt_fields TYPE TABLE OF rfc_db_fld,
lt_data TYPE TABLE OF t_remote_tadir.
APPEND ` delflag EQ '' ` TO lt_options.
APPEND ` AND genflag EQ '' ` TO lt_options.
" object type
IF s_objtyp[] IS NOT INITIAL.
lt_temp = morph_selopt_to_rfc( if_fieldname = 'object' it_selopt = s_objtyp[] ).
IF lt_temp IS NOT INITIAL.
APPEND ` AND ` TO lt_options.
APPEND LINES OF lt_temp TO lt_options.
ENDIF.
ENDIF.
" object name
IF s_objnam[] IS NOT INITIAL.
lt_temp = morph_selopt_to_rfc( if_fieldname = 'obj_name' it_selopt = s_objnam[] ).
IF lt_temp IS NOT INITIAL.
APPEND ` AND ` TO lt_options.
APPEND LINES OF lt_temp TO lt_options.
ENDIF.
ENDIF.
" package
IF s_devc[] IS NOT INITIAL.
lt_temp = morph_selopt_to_rfc( if_fieldname = 'devclass' it_selopt = s_devc[] ).
IF lt_temp IS NOT INITIAL.
APPEND ` AND ` TO lt_options.
APPEND LINES OF lt_temp TO lt_options.
ENDIF.
ENDIF.
APPEND VALUE #( fieldname = 'OBJECT' ) TO lt_fields.
APPEND VALUE #( fieldname = 'OBJ_NAME' ) TO lt_fields.
" get objects
CALL FUNCTION 'RFC_READ_TABLE'
DESTINATION p_rfcdes
EXPORTING
query_table = 'TADIR'
TABLES
options = lt_options
fields = lt_fields
data = lt_data
EXCEPTIONS
system_failure = 1 MESSAGE lf_message
communication_failure = 2 MESSAGE lf_message
OTHERS = 3.
LOOP AT lt_data ASSIGNING FIELD-SYMBOL(<s_data>).
INSERT <s_data> INTO TABLE gt_remote_objects.
ENDLOOP.
ENDMETHOD.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| METHOD morph_selopt_to_rfc.
DATA: lf_where TYPE string,
lf_bit TYPE so_text,
lf_len TYPE int4.
DATA lt_selopt TYPE TABLE OF ddshselopt.
LOOP AT it_selopt ASSIGNING FIELD-SYMBOL(<s_selopt>).
DATA(ls_selopt) = CORRESPONDING ddshselopt( <s_selopt> ).
ls_selopt-shlpfield = if_fieldname.
APPEND ls_selopt TO lt_selopt.
ENDLOOP.
CALL FUNCTION 'F4_CONV_SELOPT_TO_WHERECLAUSE'
IMPORTING
where_clause = lf_where
TABLES
selopt_tab = lt_selopt.
CHECK lf_where IS NOT INITIAL.
DO.
IF lf_where IS INITIAL.
EXIT.
ENDIF.
CLEAR lf_len.
lf_len = strlen( lf_where ).
IF lf_len <= 72.
APPEND lf_where TO rt_result.
EXIT.
ELSE.
lf_bit = lf_where.
lf_len = 71.
DO 71 TIMES.
IF lf_bit+lf_len(1) = ' '.
EXIT.
ENDIF.
lf_len = lf_len - 1.
ENDDO.
APPEND lf_bit(lf_len) TO rt_result.
lf_where = lf_where+lf_len.
ENDIF.
ENDDO.
ENDMETHOD.
|
3. Get SCMON data and create the result
In the last step we get the SCMON data and build up a result for the ALV grid:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
| LOOP AT lt_objname ASSIGNING FIELD-SYMBOL(<s_objname>).
DATA(lf_remote) = abap_false.
" CHECK if available in remote
READ TABLE gt_remote_objects WITH KEY object = <s_objname>-object
obj_name = <s_objname>-obj_name
TRANSPORTING NO FIELDS.
IF sy-subrc EQ 0.
lf_remote = abap_true.
ENDIF.
CASE <s_objname>-object.
WHEN 'PROG'.
" Report/Include
CHECK <s_objname>-psubc IN s_psubc.
CHECK <s_objname>-pcdat IN s_pcdat.
IF <s_objname>-psubc EQ '1'.
" Program will be used as is
LOOP AT get_usage_data_prog( <s_objname>-obj_name ) ASSIGNING FIELD-SYMBOL(<s_outtab>).
<s_outtab>-cdat = <s_objname>-pcdat.
<s_outtab>-remote = lf_remote.
<s_outtab>-tcode = <s_objname>-tcode.
SELECT SINGLE text
FROM trdirt
INTO @<s_outtab>-title
WHERE name EQ @<s_objname>-obj_name
AND sprsl EQ @sy-langu.
IF sy-subrc NE 0.
SELECT SINGLE text
FROM trdirt
INTO @<s_outtab>-title
WHERE name EQ @<s_objname>-obj_name.
ENDIF.
APPEND <s_outtab> TO gt_outtab.
ENDLOOP.
ELSE.
" Include will be "extracted" to the main program
LOOP AT get_usage_data_include( <s_objname>-obj_name ) ASSIGNING <s_outtab>.
<s_outtab>-cdat = <s_objname>-pcdat.
<s_outtab>-remote = lf_remote.
<s_outtab>-tcode = <s_objname>-tcode.
APPEND <s_outtab> TO gt_outtab.
ENDLOOP.
ENDIF.
WHEN 'CLAS'.
" Class
LOOP AT get_usage_data_clas( <s_objname>-obj_name ) ASSIGNING <s_outtab>.
<s_outtab>-cdat = <s_objname>-pcdat.
<s_outtab>-remote = lf_remote.
APPEND <s_outtab> TO gt_outtab.
ENDLOOP.
WHEN 'FUGR'.
" Function group
LOOP AT get_usage_data_fugr( <s_objname>-obj_name ) ASSIGNING <s_outtab>.
<s_outtab>-cdat = <s_objname>-pcdat.
<s_outtab>-remote = lf_remote.
APPEND <s_outtab> TO gt_outtab.
ENDLOOP.
WHEN OTHERS.
CONTINUE.
ENDCASE.
ENDLOOP.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| METHOD get_usage_data_prog.
DATA(lt_data) = get_usage_data_objects(
iv_name = iv_name
iv_type = 'PROG'
it_subobjects = VALUE #( ( type = 'PROG' name = iv_name ) ) ).
CHECK lt_data IS NOT INITIAL.
SORT lt_data BY last_used DESCENDING.
READ TABLE lt_data INTO DATA(ls_data) INDEX 1.
APPEND ls_data TO rt_data.
ENDMETHOD.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| METHOD get_usage_data_include.
DATA: lt_subobjects TYPE tt_subobject.
SELECT master, include
FROM wbcrossi
INTO TABLE @DATA(lt_wbcrossi)
WHERE name EQ @iv_name.
LOOP AT lt_wbcrossi ASSIGNING FIELD-SYMBOL(<s_wbcrossi>).
IF <s_wbcrossi>-include EQ <s_wbcrossi>-master.
" Include and master the same -> just add
APPEND VALUE #( type = 'PROG' name = <s_wbcrossi>-master ) TO lt_subobjects.
ELSE.
" have a look if the include is part of a function module (User exit)
DATA(lf_funcname) = VALUE rs38l_fnam( ).
DATA(lf_group) = VALUE rs38l_area( ).
DATA(lf_include) = CONV progname( <s_wbcrossi>-include ).
CALL FUNCTION 'FUNCTION_INCLUDE_INFO'
CHANGING
funcname = lf_funcname
group = lf_group
include = lf_include
EXCEPTIONS
OTHERS = 6.
IF sy-subrc EQ 0.
APPEND VALUE #( type = 'FUGS' name = lf_group subtype = 'FUNC' subname = lf_funcname ) TO lt_subobjects.
ELSE.
" Just add
APPEND VALUE #( type = 'PROG' name = <s_wbcrossi>-master ) TO lt_subobjects.
ENDIF.
ENDIF.
ENDLOOP.
DATA(lt_data) = get_usage_data_objects( iv_name = iv_name iv_type = 'PROG' it_subobjects = lt_subobjects ).
CHECK lt_data IS NOT INITIAL.
SORT lt_data BY last_used DESCENDING.
READ TABLE lt_data INTO DATA(ls_data) INDEX 1.
APPEND ls_data TO rt_data.
ENDMETHOD.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| METHOD get_usage_data_clas.
DATA: lt_subobjects TYPE tt_subobject.
SELECT methodname
FROM tmdir
INTO TABLE @DATA(lt_methods)
WHERE classname EQ @iv_name
AND methodname NE @space.
LOOP AT lt_methods ASSIGNING FIELD-SYMBOL(<s_method>).
APPEND VALUE #( type = 'CLAS' name = iv_name subtype = 'METH' subname = <s_method>-methodname ) TO lt_subobjects.
ENDLOOP.
APPEND LINES OF get_usage_data_objects( iv_name = iv_name iv_type = 'CLAS' it_subobjects = lt_subobjects ) TO rt_data.
ENDMETHOD.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| METHOD get_usage_data_fugr.
DATA: lt_subobjects TYPE tt_subobject.
DATA(lf_groupname) = |SAPL{ iv_name }|.
SELECT funcname
FROM tfdir
INTO TABLE @DATA(lt_functions)
WHERE pname EQ @lf_groupname.
LOOP AT lt_functions ASSIGNING FIELD-SYMBOL(<s_function>).
APPEND VALUE #( type = 'FUGR' name = iv_name subtype = 'FUNC' subname = <s_function>-funcname ) TO lt_subobjects.
ENDLOOP.
APPEND LINES OF get_usage_data_objects( iv_name = iv_name iv_type = 'FUGR' it_subobjects = lt_subobjects ) TO rt_data.
ENDMETHOD.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| METHOD get_usage_data_objects.
DATA: lr_subtype TYPE RANGE OF scmon_proctype,
lr_subname TYPE RANGE OF scmon_procname.
LOOP AT it_subobjects ASSIGNING FIELD-SYMBOL(<s_subobject>).
DATA(ls_outtab) = VALUE t_outtab( ).
lr_subtype = VALUE #( ).
lr_subname = VALUE #( ).
CASE iv_type.
WHEN 'PROG'.
ls_outtab-type = 'PROG'.
ls_outtab-parent = <s_subobject>-name.
ls_outtab-name = iv_name.
WHEN 'CLAS'.
ls_outtab-type = 'METH'.
ls_outtab-parent = <s_subobject>-name.
ls_outtab-name = <s_subobject>-subname.
APPEND VALUE #( sign = 'I' option = 'EQ' low = <s_subobject>-subtype ) TO lr_subtype.
APPEND VALUE #( sign = 'I' option = 'EQ' low = <s_subobject>-subname ) TO lr_subname.
WHEN 'FUGR'.
ls_outtab-type = 'FUNC'.
ls_outtab-parent = <s_subobject>-name.
ls_outtab-name = <s_subobject>-subname.
APPEND VALUE #( sign = 'I' option = 'EQ' low = <s_subobject>-subtype ) TO lr_subtype.
APPEND VALUE #( sign = 'I' option = 'EQ' low = <s_subobject>-subname ) TO lr_subname.
ENDCASE.
IF ls_outtab-parent(4) EQ 'SAPL'.
ls_outtab-parent = ls_outtab-parent+4.
ENDIF.
SELECT SINGLE devclass
FROM tadir
INTO @ls_outtab-package
WHERE obj_name EQ @ls_outtab-parent
AND object IN ('PROG', 'FUGR', 'FUGS', 'CLAS').
SELECT MAX( last_used ), MAX( counter )
FROM susg_v_data
INTO (@ls_outtab-last_used, @ls_outtab-executions)
WHERE usgid EQ @gf_usgid
AND obj_type EQ @<s_subobject>-type
AND obj_name EQ @<s_subobject>-name
AND sub_type IN @lr_subtype
AND sub_name IN @lr_subname.
APPEND ls_outtab TO rt_data.
ENDLOOP.
ENDMETHOD.
|
Using this report we were able to identify lots of unsed programs (and even programs which never arrived on our productive server).