Upload
muthuraman-al
View
163
Download
5
Embed Size (px)
Citation preview
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 1
BPC 7.X for Net Weaver:
Performance considerations for
BADI UJ_CUSTOM_LOGIC
Applies to:
Business Planning and Consolidation 7.X for Net Weaver. For more information, visit the Enterprise Performance Management homepage.
Summary
This paper describes different aspect of performance relevant topics in the context of customer defined planning function in BPC. It covers different implementation detail aspects of BADI UJ_CUSTOM_LOGIC as well as general guidelines for performance.
Author: Matthias Nutt
Company: SAP (Switzerland) AG
Created on: 13 September 2010
Author Bio
Matthias Nutt works for SAP (Switzerland) AG. He is a professional consultant since 2002 and works in the
area of BW in close co-operation with the development, the RIG and support team. His work spans a variety of roles including project manager, technical project manager, consulting, development, primary support and development support.
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 2
Disclaimer
Any software coding and/or code lines /strings (“Code”) included in this documentation are only examples and are not intended to be used in a productive system environment. The Code is only intended to better
explain and visualize the syntax and phrasing rules of certain coding. SAP does not warrant the correctness and completeness of the Code given herein, and SAP shall not be liable for errors or damages caused by the usage of the Code, except if such damages were caused by SAP intentionally or by its gross negligence.
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 3
Table of Contents
Disclaimer............................................................................................................................................... 2
Best practices for implementing BADI UJ_CUSTOM_LOGIC ..................................................................... 4
Optimizing reading data........................................................................................................................ 4
Optimizing reading data from table CT_DATA ........................................................................................ 4
Optimizing reading data inside the BADI implementation ........................................................................ 4
Writing only relevant data ..................................................................................................................... 5
Optimizing writing data ......................................................................................................................... 5
Passing parameters ............................................................................................................................. 5
Using script logic versus BADI ................................................................................................................. 6
Using complex planning functions versus multiple simple planning function ................................................. 6
Optimizing the use of XDIM_MAXMEMBERS ............................................................................................ 6
Using statistics to analyze performance .................................................................................................... 7
Switching statistics on or off.................................................................................................................. 7
Integrating customer defined UJ_STAT events ...................................................................................... 9 Defining customer defined UJ_STAT events ............................................................................................................................ 9
Using customer defined UJ_STAT events................................................................................................................................. 9
Example code ....................................................................................................................................... 10
Related Content .................................................................................................................................... 12
Copyright .............................................................................................................................................. 13
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 4
Best practices for implementing BADI UJ_CUSTOM_LOGIC
This paper is presenting best practices for BADI UJ_CUSTOM_LOGIC. It covers different areas
optimizing reading data from table CT_DATA
optimizing reading data in general
optimizing writing data
setting of standard parameters QUERY, WRITE
optimizing the use of XDIM_MAXMEMBERS
performance analysis of the BADI using UJ_STAT
using RSDRI query for reading data
Optimizing reading data
From performance perspective it is crucial to read only the needed data from the database. In the language
of BPC this is called limiting the scope. Reading too much data is in general a waist of performance. It is crucial to limit the scope of the planning function as much as possible to achieve optimal performance. Limiting the scope in the context of UJ_CUSTOM_LOGIC means, to use XDIM_MEMBERSET and set it as
restrictive as possible if the parameter QUERY is set to ON. In the case where parameter QUERY is set to OFF, read the data from the shared query engine and restrict the scope as much as possible here as well. Below you can find more information on how to read the data inside the BADI. The next chapter focuses on
reading the data from internal table CT_DATA when QUERY is set to ON.
Optimizing reading data from table CT_DATA
The BADI UJ_CUSTOM_LOGIC offers method IF_UJ_CUSTOM_LOGIC~EXECUTE in interface. This
method features the table parameter CT_DATA. The table is filled if the BADI parameter QUERY is set to ON in the call the BADI.
Example:
*START_BADI ztesting QUERY=ON *END_BADI
BPC 7.X for NetWeaver uses an account model for the data. This implies that lots of read statements are necessary to retrieve the needed information for a certain calculation. In the following example we assume that table CT_DATA contains 100000 entries. Furthermore, the following calculation should be done:
Account “A” = “B” * “C”. For a simple algorithm which loops over all the data and just checks for every record where the account is equal to “A” if the corresponding records with account equal to “B” and “C” exists , this gives the following figures. For a standard table with 100000 records 50000 comparisons are necessary in
average to find a matching record. In this example searching for a matching record needs to be done twice to find the matching records (once to find “B” and once to find “C”), which results in 50000 + 50000 comparisons in average. If the search is done on a sorted table, a binary search can be used. In this case
the algorithm take log(N)/log(2) probes to find a matching record. In our example where N = 100000 this gives approx. 17 probes per account. As we can see searching using binary search is much faster and should be used if reading CT_DATA needs to be done very often. Another interesting alternative is to use
hashed tables, which allow a fast read access to large internal tables. In most cases using hashed tables instead of sorted tables is faster i f reading can be done with the full table key.
The example code at the end of this document shows how a sorted or hashed table can be created and
read. The code copies CT_DATA into a sorted (or hashed) table and uses this table throughout the implementation of a customer implementation.
Optimizing reading data inside the BADI implementation
For performance reasons it is possible to set parameter QUERY to OFF. In this case the data must be read inside the BADI implementation. There are different function modules in function group UJQ_QUERY_ENGINE which can be used. The best option is to use UJQ_RUN_RSDRI_QUERY. But you
need to be sure that an RSDRI query can be used, as it has some minor limitations, e.g. it can only work on leaves. An MDX query (this includes axis queries and cell queries) should be avoided to read large data
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 5
volumes if possible. An AXIS for example checks the data against the master data and the checks if an RSDRI query can be executed. If you are sure that a RSDRI query can be executed, this overhead can be avoided. For more details and other function modules please check the implementation details in function
group UJQ_QUERY_ENGINE.
Writing only relevant data
BPC is locking the data only when writing data back to the DB (this is called “optimistic lock”). That means it
is possible that two planners work on the same planning data without knowing this. If the first planner saves the data to the DB, the second planner is not notified or not aware of it. When the second planner is saving his data finally, BPC still has to write correct data records to the DB. Therefore it re-reads the updated data
record from the DB to compute correct deltas records and write those deltas to the DB. This happens whenever data is stored to the DB in BPC. In our example this happens also when the first planner save the data. The systems re-read the data to calculate correct delta records. In general: a save operation implies re-
reading the up-to-date data from the DB in order to compute correct delta records. The deltas are finally stored in the BW cubes. Depending on the data model, the amount of data in the cube and the amount of data to be written re-reading the data can be time consuming and a BWA server can be valuable.
For performance reasons it is important to write as less data as possible in BPC due to internal processing steps. This reduces the immanent overhead of re-reading the data from the DB and the computation of the correct deltas. On the other hand it must guaranteed that the data integrity is still intact after writing data. If
the computation of a certain keyfigure value is based on other keyfigure value, they need to be in sync. On the other side if the data integrity can be guaranteed e.g. by organizational agreements the amount of data to be written to the DB can be reduced. Because of the reason mentioned above (re-reading, delta
computation) this will lead to performance improvements mostly because re-reading data from DB is limited.
Optimizing writing data
The last chapter showed performance improvements by reading and writing only necessary data. In this
chapter the focus is on an internal technical aspect of writing data. As stated above BPC compares the current data against the data stored in the DB to compute the correct delta values. For internal technical reason the best performance can be achieved if the data which should be written is sorted. The reason for
this is the following: The system splits the data into several smaller packages. Each single package is written separately. If the data is sorted, the probability is much higher that the number of records returned by the re-read statement for the data in a package is smaller. This effect dominates the performance while writing
data. The code in the example shows this in detail.
Furthermore a positive effect can be achieved if the data is not stored in the standard way by the BPC framework, which is reflected by the parameter WRITE set to ON. From a purely performance orientated
standpoint it is better to set WRITE to OFF and save the data internally in the BADI implementation. Saving the data can be done using CL_UJR_WRITE_BACK-> WRITE_BACK_INT( ).
Another improvement can be achieved by setting parameter PACKAGE_SIZE in table UJR_PARAM. The
default value is 40000. Setting this value to a higher number may influence the performance by reducing the number of read and write operations. If for example 100000 records should be written, the system is writing the data in packages of 40000 records. This results in 3 packages. If the package size parameter is changed
to 50000, the system uses only 2 packages to write the data. The actual setting for PACKAGE_SIZE is a trade-off between memory usage and performance. It depends heavily on the application and the system. Nevertheless it may be worth to investigate this in the customer system.
Passing parameters
Calling BADI UJ_CUSTOM_LOGIC can involve passing parameters to the BADI. The standard parameter WRITE and QUERY can be used here. Furthermore customer defined parameters can be used as well. In
some cases it is necessary to pass several parameters to the BADI like in the following example.
*START_BADI ztesting2 PARAM1=VALUE1
PARAM2=VALUE2 PARAM3=VALUE3 PARAM4=VALUE4
QUERY=OFF
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 6
WRITE=OFF *END_BADI
A huge number of parameter may influence the performance of the script logic parser. For performance reasons it is better to use only a small amount of parameters. Nevertheless it is necessary to pass all necessary information to the BADI. The solution to this is straight forward. A generic parameter can be used.
The value of this generic parameter is e.g. a string which contains a list of all parameter-value pairs. This is demonstrated below:
*START_BADI ztesting2
PARAMETERS = PARAM1=VALUE1,PARAM2=VALUE2,PARAM3=VALUE3,PARAM4=VALUE4 QUERY=OFF WRITE=OFF
*END_BADI
Of course it now necessary to parse the parameter and the value string in the BADI, but this is straightforward.
Using script logic versus BADI
In general, a well designed BADI implementation is faster than script logic. Even if the underlying algorithm is the same, a small overhead to parse and prepare the execution of the script logic is needed. This overhead
depends heavily on the script logic itself. How many variables are used, declared and where does the declaration happen, how many functions are declared and where are they declared and so on. A rule of thumb for script logic is to declare everything that can be used later on (like variables, function definitions or
select statements) at the beginning of the script. This improves the performance of the script logic parser. Please note that the script logic is parsed each time it is executed.
Using complex planning functions versus multiple simple planning function
In most cases it is possible to write a more complex planning function which performs the necessary calculations in one step. On the other hand there are some good reasons to reduce the complexity of a planning function by implementing several specialized planning functions. From a performance perspective it
is obvious that a well implemented complex planning function returns the desired results in less time. One reason for this is that intrinsic overhead e.g. given by the BPC framework to start a planning function, reading data or writing data is minimized. Another reason is that special techniques like buffering or storing
intermediate results in internal tables can be used in the complex algorithm, which may improve performance. The drawback is that implementing a complex algorithm takes more time in the design and development phase. Furthermore maintenance of the code is more complex.
Optimizing the use of XDIM_MAXMEMBERS
In terms of performance it is best to avoid XDIM_MAXMEMBERS if possible. Usually XDIM_MAXMEMBERS is used because of memory issues. Not all data can be processed at the same time and a way to process the
data in several steps is needed. A straightforward method to cure this symptom is the use of XDIM_MAXMEMBERS which splits the members of a dimension into different packages and call s the BADI for each single package (please refer to table IT_CV in interface method
IF_UJ_CUSTOM_LOGIC~EXECUTE). This way a workaround for the memory issue is possible in a straightforward, almost non-invasive way. The drawback is that the BADI is called several times, which results in reading and writing data from the DB several times as well. For performance reasons it is crucial to
limit access to the DB to the minimum. Thus the parameter XDIM_MAXMEMBERS should be as big as possible and limit the number of packages to the absolute minimum. Furthermore it should be checked if the memory requirements of the BADI implementation can be limited by a different design of the algorithm in the
BADI implementation to avoid the use of XDIM_MAXMEMBERS. This can be done e.g. by limiting the processed data internally for different processing steps, freeing unused memory as soon as possible, etc.
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 7
Using statistics to analyze performance
Switching statistics on or off
Identifying performance issues usually starts by using transaction UJSTAT. UJSTAT displays runtime
information of certain checkpoints in the BPC ABAP code. To check if recording statics is switched on table UJA_USER_DEF needs to be checked in transaction SE16. The screenshot below shows an example:
It shows that the field DEFAULT_VAL is set to ON which indicates that UJSTAT tracing is switched on. Setting the field DEFAULT_VAL to OFF disables trace generation. Once tracing is switched on the
application can be traced. Changing the value from ON to OFF and vice versa can be done in SE16 directly or via the BPC front-end.
To display the trace statistics start transaction UJSTAT. When taking a close look at the statistics a lot can
be learned about the internal processing. The statistics do not only show the time consumed in a step, they also provide an overview of what is happening in the system. The following screenshot shows for example what happens in which order, when data is written to the DB.
As we can see during the “write back” different steps are executed like “get records range”, “check base
member”, or “check security” (BTW “get_records_range” is the piece of code which determines which data need to be re-read from the DB in order to calculate the correct delta values. Nevertheless the time which is needed to re-read is not displayed here. It is included in “Write Records to infocube”.) If you have
performance issues e.g. while writing data try to identify which step is consuming the most runtime. In the example above 19.3 seconds are needed to check and write 102 data records, 3.3 seconds (or approx. 20%) are spent in “check work status”. The most time is spend in the default logic (approx. 13.7 seconds). In this
case it would be reasonable to inspect the implementation of the default logic in more detail. This is shown in the next screenshot.
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 8
It shows that the shared query engine returns 6673 records and needs 3.9 seconds to do this. Adding some
time for aggregating the data the system needs roughly 4 seconds. This is done in the BADI implementation. The BADI is processed and generates 54280 records which should be written to the DB. To write the data the data needs to be re-read and the delta need to be computed. All records where the delta is zero do not
need to be stored again. In the end the system writes 788 records and the total time to write the data (including re-read the data, compute the delta and finally write the data) sum up to 5.5 seconds. In total we have 4 + 5.5 = 9.5 seconds pure reading and writing time.
These are the standard check points which can be used immediately. The next chapter shows how customer defined UJSTAT events can be defined and used. In the screenshot above some user defined events are already included.
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 9
Integrating customer defined UJ_STAT events
Defining customer defined UJ_STAT events
To use customer events as in the example above they need to be defined in table UJ0_EVENTS (and
UJ0_EVENTST). This can be done using transaction SE16.
The example above shows that the event-id 900000000 is defined. A language dependent text can be maintained in table UJ0_EVENTST. This is not done here and is not necessary, although helpful.
Using customer defined UJ_STAT events
It is possible to define customer based statistic event and include them for example in the BADI code. The last chapter described how to define the event. This chapter shows how to use the events in customer code
(e.g. in a BADI implementation). This allows measuring the performance of different parts of the BADI in a straight forward way in more detail. The following code excerpt shows a simple example.
data: d_session_id type uj_stat_session.
data: lo_context type ref to if_uj_context.
* get user from context
call method cl_uj_context=>get_cur_context
receiving
ro_context = lo_context.
call method cl_uj0_statistics=>register_caller
exporting
i_user_session = lo_context->ds_user-session_id
i_user_ip_addr = lo_context->ds_user-ip_addr
i_user_id = lo_context->ds_user-user_id
i_action_id = '007'
* i_object_name =
i_appset_id = i_appset_id
i_application_id = i_appl_id
receiving
r_stat_session_id = d_session_id.
call method cl_uj0_statistics=>new_event_start
exporting
i_caller_guid = d_session_id
i_event_id = '900000000'.
* do some computation here … and measure the runtime
call method cl_uj0_statistics=>new_event_end
exporting
i_caller_guid = d_session_id.
The call to register_caller registers the caller and returns a new session id which is used in the subsequent
calls to new_event_start and new_event_end. The time is measured between the start and end event. It is also possible to nest different start and end events. Registering the caller needs only be done once. This approach is useful to identify performance bottlenecks e.g. in customer BADI implementation. Different parts
of the BADI can be measured and UJSTAT shows which part takes the most runtime and should be optimized.
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 10
Example code
This section presents some simple lines of code. In the beginning the content of table CT_DATA is copied into a sorted table for faster read access. The next lines show some simple examples for working with the
sorted table. The last part copies the “modified” data back into CT_DATA. This is just a simple example. The code does not have any meaning except for educational reasons. Testing can be done using transaction UJKT.
method if_uj_custom_logic~execute.
data: lr_data type ref to data.
data: l_found type c.
data: lr_table_descr type ref to cl_abap_tabledescr.
data: l_with_measures type uj_flg.
data: lt_key type standard table of string.
field-symbols: <lfs_data> type any.
field-symbols: <lt_data> type any table.
break-point.
* ------------------
* create a sorted or hashed table and copy CT_DATA into this table
* ------------------
try.
create data lr_data like line of ct_data.
assign lr_data->* to <lfs_data>.
* create data lr_data like hashed table of <lfs_data> with unique default key.
create data lr_data like sorted table of <lfs_data> with unique default key.
assign lr_data->* to <lt_data>.
* <lt_data> = ct_data[].
insert lines of ct_data into table <lt_data>.
free ct_data.
catch cx_uj_static_check.
assert 1 = 0. " DUMP
endtry.
* ------------------
* working with that table
* ------------------
* now work with table <lt_data> -
* reading the table is now done using a binary search or
* using the hash key if possible.
* this should improve performance
* example:
* get a record which should be searched in the next statements
loop at <lt_data> assigning <lfs_data>.
exit.
endloop.
if sy-subrc <> 0.
* not value found -> nothing to search
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 11
endif.
* now try to find the record (using the reading from ABAP syntax)
if <lfs_data> is assigned.
read table <lt_data> assigning <lfs_data>
from <lfs_data>.
if sy-subrc = 0.
* found
l_found = 'X'.
else.
* not found
clear l_found.
endif.
endif.
* ------------------
* finally copy the data back to CT_DATA and make sure that CT_DATA is sorted
* ------------------
ct_data = <lt_data>.
free <lt_data>.
lr_table_descr ?= cl_abap_tabledescr=>describe_by_data( <lt_data> ).
if lr_table_descr->table_kind = cl_abap_tabledescr=>tablekind_sorted.
* table is already sorted
else.
sort ct_data.
endif.
endmethod.
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 12
Related Content
Note:
BPC NW 7.5 Collective Note for Performance Improvement
How to paper:
BPC 7.X for NetWeaver: Performance Improvements for BADI UJ_CUSTOM_LOGIC Using Buffer Objects
Performance Improvement Measures in BPC Admin and Office Client
Weblinks on search algorithms:
http://en.wikipedia.org/wiki/Search_algorithm
http://en.wikipedia.org/wiki/Binary_search_algorithm
SAP Documentation on XDIM_MAXMEMBERS
For more information, visit the Enterprise Performance Management homepage.
BPC 7.X for Net Weaver: Performance considerations for BADI UJ_CUSTOM_LOGIC
SAP COMMUNITY NETWORK SDN - sdn.sap.com | BPX - bpx.sap.com | BOC - boc.sap.com
© 2010 SAP AG 13
Copyright
© Copyright 2010 SAP AG. All rights reserved.
No part of this publication may be reproduced or transmitted in any form or for any purpose without the express permission of SAP AG.
The information contained herein may be changed without prior notice.
Some software products marketed by SAP AG and its distributors contain proprietary software components of other software vendors.
Microsoft, Windows, Excel, Outlook, and Pow erPoint are registered trademarks of Microsoft Corporation.
IBM, DB2, DB2 Universal Database, System i, System i5, System p, System p5, System x, System z, System z10, System z9, z10, z 9,
iSeries, pSeries, xSeries, zSeries, eServer, z/VM, z/OS, i5/OS, S/390, OS/390, OS/400, AS/400, S/390 Parallel Enterprise Server, Pow erVM, Pow er Architecture, POWER6+, POWER6, POWER5+, POWER5, POWER, OpenPow er, Pow erPC, BatchPipes, BladeCenter, System Storage, GPFS, HACMP, RETAIN, DB2 Connect, RACF, Redbooks, OS/2, Parallel Sysplex, MVS/ESA, AIX, Intelligent Miner, WebSphere, Netf inity, Tivoli and Informix are trademarks or registered trademarks of IBM Corporation.
Linux is the registered trademark of Linus Torvalds in the U.S. and other countries.
Adobe, the Adobe logo, Acrobat, PostScript, and Reader are either trademarks or registered trademarks of Adobe Systems Incorporated in the United States and/or other countries.
Oracle is a registered trademark of Oracle Corporation.
UNIX, X/Open, OSF/1, and Motif are registered trademarks of the Open Group.
Citrix, ICA, Program Neighborhood, MetaFrame, WinFrame, VideoFrame, and Mult iWin are trademarks or registered trademarks of Citrix Systems, Inc.
HTML, XML, XHTML and W3C are trademarks or registered trademarks of W3C®, World Wide Web Consortium, Massachusetts Institute of Technology.
Java is a registered trademark of Sun Microsystems, Inc.
JavaScript is a registered trademark of Sun Microsystems, Inc., used under license for technology invented and implemented by
Netscape.
SAP, R/3, SAP NetWeaver, Duet, PartnerEdge, ByDesign, SAP Business ByDesign, and other SAP products and services mentioned herein as well as their respective logos are trademarks or registered trademarks of SAP AG in Germany and other countries.
Business Objects and the Business Objects logo, BusinessObjects, Crystal Reports, Crystal Decisions, Web Intelligence, Xcelsius, and
other Business Objects products and services mentioned herein as well as their respective logos are trademarks or registered trademarks of Business Objects S.A. in the United States and in other countries. Business Objects is an SAP company.
All other product and service names mentioned are the trademarks of their respective companies. Data contained in this document serves informational purposes only. National product specif ications may vary.
These materials are subject to change without notice. These materials are provided by SAP AG and its aff iliated companies ("SAP Group") for informational purposes only, without representation or warranty of any kind, and SAP Group shall not be liable for errors or omissions with respect to the materials. The only warranties for SAP Group products and services are those that are set forth in the express warranty statements accompanying such products and services, if any. Nothing herein should be construed as constituting an
additional w arranty.