Improving Your PeopleCode

Developer changes can affect how a user interacts with the page. Performance and screen flicker, which occurs whenever the screen refreshes after a server trip, are significant issues.

This chapter discusses how to:

Click to jump to parent topicReducing Trips to the Server

This section discusses how to:

Server trips are bad for performance. Each server trip consumes resources on the application server, slows down the user data entry, and can affect type ahead. Whenever you see an hourglass as you move between fields on a page, it is because the browser is waiting for a server trip to complete.

The larger the component’s buffer (based on the number of record definitions accessed, the number of fields in each record, and the number of rows in each grid or scroll area for each record), the longer each round trip to the server, because of the increased server processing. Server trips also create a distracting page flicker as the page is redisplayed after each server trip.

Deferred mode reduces the user’s time to complete the transaction and conserves application server resources.

The following user interactions cause a trip to the server. Only the first three items in the list are deferred in deferred processing mode.

Each trip goes through the same process of checking security, unpacking the buffers that store the data being processed, processing the service request, generating the HTML for the page to be redisplayed, packing updated buffers, and storing the buffers on the web server. To maximize online performance, minimize server trips.

Click to jump to top of pageClick to jump to parent topicCounting Server Trips

Count the trips to the server to quickly identify transactions that have performance issues. PeopleTools can automatically count these trips by reason (such as, adding a row in a grid or FieldChange PeopleCode) and write the output to a log file.

To turn this feature on, run a debug version of PeopleTools and add the following to the [trace] section of the appserv.cfg file:

showcounters = 1

The output is written to the appsrv.log file.

Click to jump to top of pageClick to jump to parent topicUsing Deferred Mode

Keep components in deferred mode and enable fields for interactive mode only if there is a strong business case.

In order for every field on the component to run in deferred mode, Deferred mode must be selected at the component level, Allow Deferred Processing must be selected for each page in the component, and Allow Deferred Processing must be selected for each field.

PeopleSoft recommends that you continue to code field edits in FieldEdit PeopleCode and field change logic in FieldChange PeopleCode, but set this logic to run in deferred mode. You do not need to move field edits to SaveEdit.

Click to jump to top of pageClick to jump to parent topicHiding and Disabling Fields

Avoid using FieldChange PeopleCode to hide, unhide, enable, or disable elements on the same page, unless the element is triggered by a separate button.

Hiding or unhiding objects and enabling or disabling objects should, as a general rule, be coded in either page Activate PeopleCode, or in FieldChange PeopleCode for objects that are on another page in the component.

Perform cross-validation edits to prevent invalid data combinations from being written to the database for fields that previously would have been hidden or unavailable. If unhiding fields that were previously hidden or unavailable results in making the page confusing, consider designing a longer page, so that users can easily associate related fields.

You can hide or unhide objects or set them to display-only in page Activate PeopleCode before the page is initially displayed, based on setup data, configuration options, or personalization settings. Fields can be set to display-only using PeopleCode by setting the DisplayOnly property for the field to True.

You can hide or unhide fields on another page, or set the fields to display-only, based on the value that a user enters in a field on the current page, as long as that component or field is set up to run in deferred processing mode. In some cases, it may make sense to split transactions across pages to achieve progressive disclosure.

Click to jump to top of pageClick to jump to parent topicUsing the Refresh Button

The Refresh button gives users control of their environment. Clicking the Refresh button forces a trip to the server. PeopleTools then redisplays the page in the browser. This allows the user to:

When the page is redisplayed, the cursor is positioned in the same field it was when the user pressed the Refresh button.

Note. The Refresh button does not refresh the page from the database. It simply causes a server trip so any deferred PeopleCode changes get processed. If there are no deferred changes or the deferred changes do not cause any errors or other changes on the page, it may appear to the user as if nothing happened.

In addition, fields on derived work records are not updated if the user clicks the Refresh button.

Click to jump to top of pageClick to jump to parent topicUpdating Totals and Balances

In some pages, totals or balances are displayed based on data entered into a grid or scroll area. This process should work in deferred mode also, showing the totals or balances as of the last trip to the application server.

Continue to keep any accumulation and balancing logic in FieldChange PeopleCode, but run the field in deferred mode. This enables users to click the Refresh button at any time to see the latest totals based on the data entered. Totals and balances in deferred mode are always updated and displayed after any trip to the application server.

Click to jump to top of pageClick to jump to parent topicUsing Warning Messages

In deferred mode, FieldEdit PeopleCode errors and warnings are not displayed when a user moves out of the field, but rather on the next trip to the server. This might not occur until the user enters all the data and clicks the Save button.

For FieldEdit error messages running in deferred mode, PeopleTools changes the field to red and positions the cursor to the field in error when it displays the message. This allows the user to associate the error message with a specific field.

For warning messages, however, PeopleTools does not change the field to red or position the cursor. For a user to clearly understand to which field a warning message applies, ensure that warning messages clearly describe the fields affected by the warning.

For example, the warning message “Date out of range” would be confusing if there are seven date fields on the page, since a user could not easily determine which date field needed to be reviewed. Instead, you could include bind variables in the message to show which dates are out of range.

Click to jump to top of pageClick to jump to parent topicUsing the Fastest Algorithm

You should determine which algorithms perform the best and have the smallest elapsed time. Tracing does not provide subsecond level of timing information. Plus, tracing imposes a higher overhead to the runtime environment, which skews the elapsed time reading.

However, you can use the %PerfTime system variable for determining elapsed time. %PerfTime retrieves the local system clock time by making a system call, and the return time is down to the millisecond.

The following example of %PerfTime determines how long a program takes to execute:

&Start = %PerfTime; &results = ""; For &I = 1 To &Count; &GnnwgNumber = GetNextNumberWithGapsCommit(QEORDER_DTL.QE_QTY, 999999, 1, "where QE_ORDER_NBR='GNNWG'"); &results = &results | " : " | &GnnwgNumber; End-For; &End = %PerfTime; &out = "Count = " | &Count | ", total GNNWG time (s) = " | NumberToString("%6.3", Value(&End - &Start));

Click to jump to parent topicUsing Better Coding Techniques for Performance

This section discusses how to:

Click to jump to top of pageClick to jump to parent topicRunning a SQL Trace

Run a SQLTrace and review the transaction for SQL statements that have a long processing time.

The duration column (Dur= ) in a SQL trace displays this information. If the duration is greater than 100 milliseconds, there may be an opportunity to make this SQL statement run faster. Work with your database administrator to tune the SQL.

Click to jump to top of pageClick to jump to parent topicOptimizing SQL

It is better to perform a simple join than to issue two related SQL statements separately.

However, if your transaction requires a very complex SQL statement (for instance, anything that uses correlated subqueries) consider breaking it up into multiple SQL statements. You may get more predictable performance this way.

Click to jump to top of pageClick to jump to parent topicUsing the GetNextNumberWithGaps Function

Many applications use a sequence number as a unique key. The last number used is stored in a common table, and a SQL statement is issued to retrieve the last number used and update the table. This locks the common table until the whole transaction is saved and the unit of work committed.

Instead, consider using the GetNextNumberWithGaps PeopleCode function whenever have gaps in the sequence numbering are acceptable. The function retrieves the last number used, increments it by one, and updates the common table. This is done in a separate unit of work, to minimize the time a database lock is held on the common table.

GetNextNumberWithGaps issues a commit only when issued from the SavePreChange or Workflow event.

Click to jump to top of pageClick to jump to parent topicConsolidating PeopleCode Programs

Consolidate RowInit PeopleCode into one field within the record. This reduces the number of PeopleCode events that need to be triggered. Fewer PeopleCode programs results in fewer PeopleCode objects to manage. Do the same for RowInsert, SaveEdit, SavePreChange, SavePostChange, and Workflow PeopleCode programs.

Click to jump to top of pageClick to jump to parent topicMoving PeopleCode to a Component or Page Definition

Analyze transactions and move PeopleCode that is specific to a component from the record definition to the component or page definition. This eliminates the need to execute conditional statements, such as If %Component = .

This only helps if you are able to move all the PeopleCode in a program from the record to a component or page, and multiple components access that record.

Click to jump to top of pageClick to jump to parent topicSending Messages in the SavePostChange Event

Messages sent online should always be coded in the SavePostChange event. To minimize the time that PeopleTools maintains locks on single-threaded messaging tables, behind-the-scenes logic in the SavePostChange event defers sending the message until just prior to the commit for the transaction.

Click to jump to top of pageClick to jump to parent topicUsing Metadata and the RowsetCache Class

If your application uses data that is common, utilized by a number of users, and yet is fairly static, you may see a performance improvement by using the RowsetCache class.

PeopleTools stores application data in a database cache to increase system performance. The RowsetCache class enables you to access this memory structure, created at runtime, and shared by all users.

Note. Non-base language users may see different performance due to language table considerations.

See RowsetCache Class.

Click to jump to top of pageClick to jump to parent topicSetting MaxCacheMemory

PeopleTools stores application data in a memory cache to increase system performance. However, too large a cache can leave insufficient available memory on your system, which leads to reduced performance.

Use this setting to specify the maximum size of the memory cache. PeopleTools prunes the cache to keep it within the specified size, and places the pruned data in a disk cache instead. Because using a disk cache can also reduce performance, the default setting might not be optimal for your application. You can adjust this setting to achieve the best trade-off between speed and available memory.

See Cache Settings.

Click to jump to parent topicWriting More Efficient Code

Follow these steps to write more efficient PeopleCode:

  1. Declare all variables.

    One of the conveniences of PeopleCode is that you do not have to declare your variables before you use them. The variable is assigned a type of ANY, taking on the type of the value it is assigned. However, if you use this feature, you lose type-checking at compile time, which can lead to problems at runtime.

    When you validate or save PeopleCode, watch for autodeclared messages and consider adding declarations to your program.

  2. Declare variable types specifically.

    Most of the time, you know a variable's type, so you should declare the variable of that type to begin with.

    For example, if you know that a particular variable is going to be an Integer value, declare it to be Integer in the first place. You can get much better runtime performance. It is particularly effective for loop control variables but since an integer has limited range (up to 9 or 10 digits), it must be used judiciously

  3. Watch references.

    In PeopleCode function calls, parameters are passed by reference; a reference to the value is passed instead of the value itself. If you are passing a reference to a complex data structure, such as a rowset object or an array, passing by reference saves significant processing.

    Watch out for unexpected results, though. In the following code, the function Test changes the value of &Str after the function call.

    Function Test(&Par as String) &Par = "Surprise"; end-function; Local String &Str = "Hello"; Test(&Str); /* now &Str has the value "surprise" */

  4. Put Break statements in your Evaluate statements.

    In an Evaluate statement, the When clauses continue to be evaluated until an End-evaluate or a Break statement is encountered.

    If you have an Evaluate statement with a number of When clauses, and you only expect one of them to match, put a Break statement following the likely clause. Otherwise, all the subsequent When clauses are evaluated. Your program is still correct, but it is inefficient at runtime, particularly if you have a large number of When clauses, and the Evaluate statement is in a loop.

  5. Govern your state.

    One of the key features in PeopleSoft Pure Internet Architecture is that the application server is stateless. When required, the state of your session is bundled up and exchanged between the application server and the web server.

    For example, on a user interaction, the whole state, including your PeopleCode state, has to be serialized to the web server. Then, once the interaction has completed, that state is deserialized in the application server so that your application can continue.

    To improve efficiency:

  6. Isolate common expressions.

    The PeopleCode compiler is not an optimizing compiler, unlike some current compilers for languages such as C++. For example, the PeopleCode compiler does not do common subexpression analysis. So, sometimes, if you have a complicated bit of PeopleCode that is used often, you can isolate the common expression yourself. This can make your code look cleaner, and make your code faster, especially if it is in a loop.

    In this example, notice how the common subexpression is broken out:

    /*---- For this customer, setup time on B is influenced by *---- the machine flavors of A. */ &r_machine = &rs(&idB.GetRecord(Record.MACHINE_INFO); If (&typeA = "F") And (&typeB == "U") Then &r_machine.SETUP_TIME.Value = 50; Else &r_machine.SETUP_TIME.Value = 10; End-If;

    The compiler has to evaluate each occurrence of the expression, even though it would only execute it once.

    Here’s another example. Notice that once &RS and &StartDate are created, they can be used repeatedly in the loop, saving significant processing time.

    &RS = GetRowset(); &StartDate = GetField(PSU_CRS_SESSN.START_DATE).Value; For &I = 1 To &RS.ActiveRowCount &RecStuEnroll = &RS.GetRow(&I).PSU_STU_ENROLL; &Course = &RecStuEnroll.COURSE; &Status = &RecStuEnroll.ENROLL_STATUS; &PreReqStart = &RS.GetRow(&I).PSU_CRS_SESSN.START_DATE.Value; If &Course.Value = "1002" And (&Status.Value = "ENR" Or &Status.Value = "CMP") Then If &PreReqStart < &StartDate Then &Completed = True; Break; End-If; End-If; End-For;

  7. Avoid implicit conversions.

    The most common implicit conversion is from a character string to a number and vice versa. You might not be able to do anything about this, but by being aware of it, you might be able to spot opportunities for performance improvement.

    In the following example, two character strings are converted into numeric values before the difference is taken. If this code was in a loop, and one of the values did not change, there would be a significant performance gain to do the conversion once, as the second statement illustrates.

    &Diff = &R1.QE_EMPLID.Value - &R2.QE_EMPID.Value; &Original = &R1.QE_EMPLID.Value; . . . &Diff = &Original - &R2.QE_EMPID.Value;

  8. Choose the right SQL style.

    In certain cases, use SQLExec, as it only returns a single row. In other cases, you could benefit greatly by using a SQL object instead, especially if you can plan to execute a statement more than once with different bind parameters. The performance gain comes from compiling the statement once and executing it many times.

    For instance, code that uses SQLExec might look like this:

    While (some condition) . . .set up &Rec SQLExec("%Insert(:1)", &rec); /* this does a separate tools parse of the sql and db compile of the statement and execute each time */ End-while;

    The following code rewrites the previous example to use the new SQL object:

    Local SQL &SQL = CreateSQL("%Insert(:1)"); While (some condition) . . .Setup &Rec &Sql.Execute(&Rec); /* saves the tools parse and db compile on the SQL statement and the db setup for the statement */ end-while;

    SQL objects also have the ReuseCursor property, which can be used for further performance gains.

    See ReuseCursor.

  9. Tighten up loops.

    Examine loops to see if code can be placed outside the loop.

    For example, if you are working with file objects and your file layout does not change, there is no reason to set the file layout every time you go through the loop reading lines from the file. Set the file layout once, outside the loop.

  10. Set objects to NULL when they will no longer be accessed.

    Once you are finished with an object reference, especially one with a global or component scope, assign it to NULL to get rid of the object. This allows the runtime environment to clean up unused objects, reducing the size of your PeopleCode state.

  11. Improve your application classes

    Simple properties (without get/set) are much more efficient than method calls. Be clear in your design what need to be simple properties, properties with get/set, and methods. Never make something a method that really should be a property.

    Analyze your use of properties implemented with get/set. While PeopleCode properties are in a sense "first class" properties with more flexibility in that you can run PeopleCode to actually get and set their values, make sure you actually need get and set methods. If all you have is a normal property which is more of an instance variable then avoid get/set methods. In the example below (without the strikethrough!) by having get/set for the property SomeString you have made it much more inefficient to get/set that property since every property reference has to run some PeopleCode. Often this can creep in when properties are designed to be flexible at the beginning and never subsequently analyzed for whether getters/setters were really needed after all.

    class Test ... property String SomeString get set; end-class; get SomeString return &SomeString; end-get; set SomeString &SomeString = &NewValue; end-set;

Click to jump to top of pageClick to jump to parent topicWriting More Efficient Code Examples

The following are examples of writing more efficient code.

Click to jump to parent topicSearching PeopleCode for SQL Injection

SQL injection is a technique that enables users to pass unintended SQL to an application. SQL injection is usually caused by developers who use string-building techniques to generate SQL that is subsequently executed.

PeopleSoft recommends you search your PeopleCode for SQL injection vulnerabilities.

To search for potential SQL injection vulnerabilities:

  1. Open PeopleSoft Application Designer.

  2. Select Edit, Find In. .

  3. From the Find In dialog box, select SQL Injection in PeopleCode as the find type.

    This only finds potential vulnerabilities.

  4. Review flagged PeopleCode programs.

    Vulnerable PeopleCode programs allow unvalidated user input concatenated to SQL.

    See Using the Find In Feature.

The following functions and methods provide a way for SQL to be submitted to the database, and are therefore subject to SQL injection vulnerabilities:

Look at the following PeopleCode as an example:

rem Retrieve user input from the name field; &UserInput = GetField(Field.NAME).Value; SQLExec("SELECT NAME, PHONE FROM PS_INFO WHERE NAME='" | &UserInput | "'", &Name, &Phone);

The code is meant to enable the user to type in a name and get the person's phone number. In the example, the developer expects that the user will input data such as Smith, in which case the resulting SQL would look like this:


However, if the user specified "Smith' OR AGE > 55 --", the resulting SQL would look like this:


Note the use of the comment operator (--) to ignore the trailing single quotation mark placed by the developer's code. This would allow a devious user to find everyone older than 55.

Click to jump to top of pageClick to jump to parent topicPreventing SQL Injection

Use the following approaches to avoid SQL injection vulnerabilities:

See Also