Saturday, February 27, 2021

Exadata - Oracle Hardware -- SFP Transceiver types & models

Yesterday, one of my collegues made me revisit the following article that I wrote after completing a field operation in an Exadata environment.

Exadata -- How to Connect Oracle Exadata to 10G Networks Using SFP Modules

The subject was about activating SFP modules and the related network on Exadata.. The question was the type/model of the transceivers that I have used while accomplishing the task that I shared in the article..

Actually, it's been a while since I did that job. I did not include that detail in my blog post either, but I could hardly remember that I have used the transceivers customer already had.. The client bought the transceivers from Oracle long time ago, longer than the time I start working there as the lead consultant :)
This was a good question indeed.. So I though about it and made a little research..

I guess the transceiver model was something similar to X2124A-N. So, one can position transceivers that meet similar standards. The general idea is to be compatible with the switch side. But as we have oracle hardware (Exadata) in the picture -- at least on one side, we need to be compatible with the oracle hardware as well - in the first place actually.. So using oracle supplied transceivers is the best idea, but similar transceivers should also work.

Anyways, we ended up with the following document -> 

Oracle's 10 Gigabit Ethernet Transceivers and Cables Frequently Asked Questions

Still the idea given in the paragraph above applies.. As we may not have Oracle switches in the custom environment, I mean as we may have to work with customer supplied 3rd party switches, being compatible with the switch side should be on our focus as well..

That is it.. I wish you a good weekend :)

Monday, February 15, 2021

RDBMS / RAC -- Inteconnect: RDS , IPC , RAC relink/make, skgxpinfo & ORA-27300 - bind_fail

Recently my team reported this issue to me.. An Oracle 11.2.0.4 database couldn't start on an Exadata with a freshly installed RAC Home. Startup was encountering ORA-27300 errors and it was clear that we couldn't bind to a ip:port during initial phase of the instance startup...

ORA-00603: ORACLE server session terminated by fatal error
ORA-27504: IPC error creating OSD context
ORA-27300: OS system dependent operation:bind failed with status: 11
ORA-27301: OS failure message: Resource temporarily unavailable
ORA-27302: failure occurred at: sskgxpsoc

or

ORA-00603: ORACLE server session terminated by fatal error
ORA-27504: IPC error creating OSD context
ORA-27300: OS system dependent operation:bind_fail failed with status: 0
ORA-27301: OS failure message: Error 0
ORA-27302: failure occurred at: skgxpvifconf
ORA-27303: additional information: requested interface X.X.X.X failed bind. Check output from ifconfig command

These errors in the bottom stack can be sskgxpsoc and/or skgxpvifconf and according to the following MOS note, we have some patches already available for these types of issues.

Getting ORA-00603, ORA-27504, ORA-27300, ORA-27301, ORA-27302; with "bind failed" with EAGAIN and "bind_fail failed", on "sskgxpsock" and "skgxpvifconf" (Doc ID 1524444.1)

We have also workaround and it is based on setting cluster_interconnects parameter to the infiniband ip addresses.

In this blog post, I want to go a little bit further on this topic and give you some extra information about RDS, IPC and the related libraries linked or made available for Oracle to use for interconnect and the relation between these types errors and the RDS/IPC interconnect configuıration.

It is all about oracle binaries, the libraries used by them and the options used while relinking/make'ing oracle...

If you relink ioracle with RDS (ipc_rds), then you need to have interfaces that support RDS in your environment and in this kind of an environment, you set the cluster_interconnects and tell oracle -> use them!

Of course we are talking about this environment where there is no functionality like finding available interfaces automatically. I mean, if you don't have an infiniband-like interface, then you need to turn off rds. Oracle just doesn't start even when we set the cluster_interconnect to the IP adresses of a classical tcp/udp ethernet interface. It just can't bind... This is what is seen even on single node environments.

If you are going to use a single node non-rac, you can link the  ioracle with rac_off.. I mean, you can disable the rac option, and this move solves our problem as well.... 
Eventually, relinking with rac_off removes the rds mode. Note that, relinking with rac_on uses the configuration as is...(unless we have an ipc_none -- dummy configuration .. I will touch on this topic later) 

I mean, if your oracle binary is linked with rds, it will be relinked with rds even after you relink your oracle with rac_on.. So rac_off disables the rds mode and enables the ipc_g mode, but rac_on leaves it as is.. (at least in 11.2.0.4 :))

You can understand this by reviewing the related make file or outputs of the relink command;

$ORACLE_HOME/rdbms/lib/ins_rdbms.mk rac_off ioracle
rm -f /u01/dbebs/PROD/db/tech_st/11.2.0/lib/libskgxp11.so
cp /u01/dbebs/PROD/db/tech_st/11.2.0/lib//libskgxpg.so .
/u01/dbebs/PROD/db/tech_st/11.2.0/lib/libskgxp11.so

Note that --> libskgxpg is the non RDS one.. IPC related one.. So rac_off make oracle to use that..

Rds enabled or not; after those make operations; the library name used by Oracle is libskgxp11 .. However; its contents are different.. It is actually based on the relink options used. 

If we use rds on ( ipc_rds), the library named libskgxpr is used (I mean it is renamed and copied as libskgxp11.. If we use no rds (ipc_g), the library named libskgxpg is used (it is renamed and copied to libskgxp11)

If you want to see what oracle is currently linked to (or more precisely; to see which library oracle is currently configured to use for interconnect related things), you can use the command below ->

[oracle@ebstestdb lib]$ $ORACLE_HOME/bin/skgxpinfo -v
Oracle UDP/IP (generic)

the name skgxp is related to -> system kernel generic interface inter-process communications

skgxpinfo is just a little binary available in Oracle Home for getting that info...

But! How do I check that manually? :)

If rds is off (ipc_g) ->

[oracle@ebstestdb lib]$ strings /u01/dbebs/PROD/db/tech_st/11.2.0/lib/libskgxp11.so |grep -i sskgxphack| awk '{print $16}'

output: skgxpg_rds_off.c -> This means oracle uses ipc_g :)

Actually, we have the following string in libskgxp11.so;

-> comment:Intel(R) C++ Compiler for applications running on Intel(R) 64, Version 10.1 Build 20100527 %s : skgxpg_rds_off.c

If rds is on (ipc_rds)

We can check with the same command that we used above and expect to see sskgxphack.c as the output(rather than kgxpg_rds_off.c)

[oracle@ebstestdb lib]$  strings /u01/dbebs/PROD/db/tech_st/11.2.0/lib/libskgxp11.so |grep -i sskgxphack| awk '{print $16}'

This way we get that info we need manually: ) Note that, we see sskgxphack.c when the rds is on.

Actually, we have the following string in libskgxp11.so; ->comment:Intel(R) C++ Compiler for applications running on Intel(R) 64, Version 10.1 Build 20100527 %s : sskgxphack.c

One last note, we have also an entry named ipc_none in the ins_rdbms.mk (makefile) and this seems to be a dummy driver for IPC. As far as I can see, this ipc_none is used while deinstalling Oracle software using the installer (OUI). Other than that, I don't see a point of using that.. When we relink an oracle binary with rac_on, which is currently have ipc_none configuration, it is automatically configured as ipc_g .. Well, that's the last thing I want to mention on this topic :)

Wednesday, February 10, 2021

RDBMS -- Failed add column operation may cause ORA-14097 & ORA-14278 during Partition Exchange

Recently an interesting issue escalated to me..

Customer was complaining about failed queries on a partitioned table..

The problem was obvious.. This database environment was created using rman duplicate with "SKIP TABLESPACE".. Well, SKIP TABLESPACE options was used for the duplicate, and the problem was caused by the missing datafiles mapped to some partitions of that partitioned table..

Note that, database version was 18C and customer didn't need the data in those problematic partitions.

Before, escalating the issue, customer also tried to add some columns to this table and got ORA-00376, ORA-01111 and ORA-01110.. Actually, this was no suprise.. The datafiles of some partitions were missing, as a result of duplicating this database with SKIP tablespace option.

In this kind of a case, we create a new empty dummy table with the same structure as our original partitioned one, and then we exchange partitions between this new empty table and our partitioned table to get rid of those empty/unneeded partitions which are mapped to missing data files..

However; customer tried to add a column to this table without this partition exchange thing and got those errors mentioned above.

Also, this failed add column action left unused column in the partition table... So because of those unused columns in the table, exchange partition action was encountering "ORA-14278: column type or size mismatch in EXCHANGE SUBPARTITION".

Here is the action plan to workaround this situation;

  • alter session set events '14529 trace name context forever, level 512'
  • CTAS  to create the new empty partitioned table
  • EXCHANGE PARTITION for those problematic partitions between the newly created partitioned table and the original one..

Important  Note: "Level 512" is a must. I see similar blog posts and Oracle Support notes, but level 512 is missing. "In this case", using Level 512 in that event is a must.

Note that; -> event 14529 has been enhanced with level 512 to handle the cases like the following  for exchange partition operations;

unusable columns, invisible columns

fast add of nullable columns with default value

fast add of not nullable columns with default value, long varchar columns, virtual expression columns and so on..

Tuesday, February 9, 2021

OVM - Changing Cluster Heartbeat nw, Questions related to High availability & DR, Comments on bond device problems - LACP activity unknown , LACP suspend-individual

Recently I was asked 4 interesting questions. Questions about OVM... In this blog post, I will share my answers and comment for these questions.. This might help for OVM users.

The first question was about OVM clusters, about cluster heartbeat nework.. Specifically about changing the heartbeat network and having more than one cluster heartbeat network.

This operation is a little costly. Requires VM shutdown etc. If there is an available port in the environment, it can be added to the corresponding heartbeat bond. What is important here is that, both port and switch redundancy in the relevant network path...

We shared the Oracle Support notes below.. These notes may be followed to change the Cluster Heartbeat network;

  • OVM - How to Migrate Cluster HeartBeat Network Channel To A Different Bonded Interface Of An Existing Deployment (Doc ID 2408148.1)
  • How to Move the Heartbeat from One Network to Another in Oracle VM (Doc ID 1995619.1)
  • How to Move the Heartbeat from One Network to Another in OVM3 (Doc ID 1504140.1) – This method may not work on OVM 3.4.. The other documents above seems more promising.

About having a heartbeat channel with more than one network;

We shared that; there was a change on OVVM 3.3.x that prevented having multiple networks with the cluster heartbeat role on a single server. So adding additional networking to the Cluster Heartbeat will probably not work. Add job will get an error like the following;->

“Cannot add Ethernet device: eth1 on oraclevm, to network: hearbeat, because server: oraclevm, already has cluster network”. 

Even if VM Manager shows added, we think it will not be added in the background .. Of course you can test. 

Second question was related with the general steps for having a DR for OVM environments.

As for implementing DR, we shared the following ateam document.. Method explained in that document is applicable for this task (ocfourse if you have the necessary infrastructure to support the methods that is used in that document)

https://www.ateam-oracle.com/oracle-vm-storage-repository-replication-for-on-premise-fusion-applications-disaster-recovery

Third question was about the reason why auto switchover not work.. I mean when one of the VM nodes in an OVM cluster crashes or fails, the relevant VM guests do not switch to the other standing node (they don't start from the other node, although we see that the server pool is clustered)

Analysis showed that this situation was an expected behavior. Although there was an OVM cluster, the Enable High Availability checkbox was not selected for the relevant VMs.

As for the solution; we recommended that the relevant checkbox be marked for the relevant VMs.

Following diagram shows that decision mechanism clearly..

Ref:  https://docs.oracle.com/cd/E27300_01/E27309/html/vmusg-svrpool-ha.html

To automatically configure the server pool cluster and enable HA in a server pool, select the Clustered Server Pool check box when you create or edit a server pool. See Section 6.7, “Creating a Server Pool” and Section 6.8.3, “Editing a Server Pool” for more information on creating and editing a server pool.

To enable HA on a virtual machine, select the Enable High Availability check box when you create or edit a virtual machine. See Section 7.7, “Creating a Virtual Machine” and Section 7.9.2, “Editing a Virtual Machine” for more information on creating and editing a virtual machine.


The last question was about bonding-LACP configuration.. The problem was a suspended port.. The port to which one of the slaves of a bond with a Bonding-LACP configuration was suspended..

Error messages in the logs on the switch side -> "LACP activity unknown", "LACP suspend-individual".

Bonding configuration and syslogd messages of OVM have been checked. There was no misconfiguration.. There wasn't any log record that could directly cause this problem.

The problem was solved by restarting the eth devices of the related bonds that were suspended. (example commands: ifdown eth7, ifup eth7)

After these moves, we observed that the ports of those interfaces were not suspended again.

We recommended the following;

In case problem reoccurs, a detailed analysis on the switch side should be done. Disabling the "LACP suspend-individual" setting should also be considered.

This setting is already shown disabled in some MOS notes related to Exadata ;

* Configure Exadata X8M Backup 40gbe ZS5-2 and ZS5-4 (Doc ID 2698913.1)
* Set Up and Configure Exadata X8M Backup with ZFS Storage ZS7-2 (Doc ID 2635423.1)

This environment was not an Exadata, but OVM is similar to Oracle Linux, which is what we have in Exadata.

Also, Redhat have some documents on the same subject, recommending the same solution ->

* LACP Linux Bonds not working properly on Cisco Nexus 9000-series switches

https://access.redhat.com/solutions/3702541

* Resolution: Disable LACP suspend-individual on the Cisco access port

As far as we can see, some switches also have these suspend-individual bugs.

At the end of the day,  those suspend individual messages didn't appear again.. We suggested  the setting -> "no lacp suspend-individual" on the switch side.. ( if the problem reappears in the future)

Sunday, February 7, 2021

RDBMS -- Analyze AWR reports (part by part /section by section) using Python

I like dealing with Python nowadays. By using Python (I used to use perl for these types of things), I am trying to address some issues, especially in environments with Oracle products.  
In this blog post, I will share a python code (it is not finished yet.. it is not final) that can be used to read AWR reports (AWR reports in text format).. 
This code is written just for making a POC, for showing how we can read AWR reports section by section, and load the data that we read into the arrays and use that AWR data for making recommendations. 

The comments in the code are already clear, so you can understand what we are doing here..
Basically, we read a 11.2.0.4 AWR report, certain parts of it.. Then we populate our arrays.. Once our arrays are populated with AWR data, we check some performance indicators and make our comments and give our recommendations accordingly.

Here is the output of a demo run.. The system we analyze here had a hard parse problem.


#!/usr/bin/python
"""
AWR ANALYZER -- work in progress by Erman Arslan
Creation date : 07.02.2021
"""
#import modules

import re

#declarations

ins_load_array_2d=[]
all_lines=[]
load_profile_array=[]
cpu_core_count_array=[]
instance_efficiency_array=[]
dbtime_elapsed_array=[]
timed_model_array=[]

pattern1= re.compile("Load Profile", re.IGNORECASE)
pattern2= re.compile("Instance Efficiency Percentages", re.IGNORECASE)
pattern3= re.compile("~~~~~~~~~~~~~~", re.IGNORECASE)


pattern4= re.compile("Host Name", re.IGNORECASE)
pattern5= re.compile("Snap Id", re.IGNORECASE)
pattern6= re.compile("---------------- ", re.IGNORECASE)


pattern7= re.compile("Instance Efficiency Percentages", re.IGNORECASE)
pattern8= re.compile("Top 10 Foreground Events by Total Wait Time", re.IGNORECASE)
pattern9= re.compile("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", re.IGNORECASE)

pattern10= re.compile("End Snap:", re.IGNORECASE)
pattern11= re.compile("Instance Efficiency Percentages", re.IGNORECASE)
pattern12= re.compile("~~~~~~~~~~~~~~", re.IGNORECASE)
pattern13= re.compile("Time Model Statistics", re.IGNORECASE)
pattern14= re.compile("Operating System Statistics", re.IGNORECASE)
pattern15= re.compile("------------------------------------------------------", re.IGNORECASE)

#functions

def read_awr(startword,endword,ignoreword):
   concantrated_lines=[]
   ins_load_string=""
   i=0
   global ins_load_array_2d
   ins_load_array_2d=[]
   for single_line in all_lines:
       i=i+1
       if startword.search(single_line) != None:
         concantrated_lines.append(single_line.rstrip('\n'))
         for i in range(i,len(all_lines)):
          if endword.search(all_lines[i]) != None:
            break
          if endword.search(all_lines[i]) == None and ignoreword.search(all_lines[i]) == None:
            ins_load_string += all_lines[i]
   ins_load_array=re.split('\n',ins_load_string.strip())
   for k in ins_load_array:
     temp_array=re.split(r'\s{2,}',k.strip())
     ins_load_array_2d.append(temp_array)


#MAIN.. OUR PROGRAM START HERE
awr_file='/home/oracle/awr_analyzer/awr_report.txt'
my_awr_file=open(awr_file, 'r')
for line in my_awr_file:
  all_lines.append(line)

print "\nThis is just a DEMO! It is like POC for our ability to analyze AWR reports programatically!\n"
print "\nAnalyzing awr report file->", awr_file,"\n"

#reading the load profile section
read_awr(pattern1,pattern2,pattern3)
load_profile_array= ins_load_array_2d

#reading the cpu core counts section
read_awr(pattern4,pattern5,pattern6)
cpu_core_count_array=ins_load_array_2d

#reading the Instance Efficiency Section

read_awr(pattern7,pattern8,pattern9)
instance_efficiency_array=ins_load_array_2d

#Reading DB Time and Elapsed Section

read_awr(pattern10,pattern11,pattern12)
dbtime_elapsed_array=ins_load_array_2d

#Reading Time Model Section
read_awr(pattern13,pattern14,pattern15)
timed_model_array=ins_load_array_2d

#####################################################################
#We populated our array.. 
#Now let's check some the performance indicator values stored on those arrays 
#and make some recommendations
######################################################################

#Getting values from time model section

total_db_cpu_time_temp=[string for string in timed_model_array if "DB CPU" in string]
total_db_parse_time_temp=[string for string in timed_model_array if "parse time elapsed" in string]
total_db_hardparse_time_temp=[string for string in timed_model_array if "hard parse elapsed time" in string]
total_sqlexecute_time_temp=[string for string in timed_model_array if "sql execute elapsed time" in string]
total_db_cpu_time=total_db_cpu_time_temp[0][1]
total_db_parse_time=total_db_parse_time_temp[0][1]
total_db_hardparse_time=total_db_hardparse_time_temp[0][1]
total_sqlexecute_time=total_sqlexecute_time_temp[0][1]

#Getting values from instance efficiency section

execute_to_parse=instance_efficiency_array[3][1]
parse_cpu_to_parse_elapsed=instance_efficiency_array[4][1]

#Getting values from load profile section
hard_parses=load_profile_array[13][1]
total_parses=load_profile_array[12][1]
total_executed_sql=load_profile_array[16][1]

#Getting values from db time elapsed section

elapsed_time=dbtime_elapsed_array[0][1]
db_time=dbtime_elapsed_array[1][1]
elapsed_time=elapsed_time.replace(' (mins)','')
db_time=db_time.replace(' (mins)','')

#Getting values from cpu core count section
core_count=cpu_core_count_array[0][2]


#Making some comments/recommendations
print "\nCOMMENTS and RECOMMENDATIONS"
print "--------------------------------"
if float(db_time) > float(elapsed_time)*float(core_count)*float(0.75):
  print "-Note : We have",core_count," core/cores in this machine.\n*DB Time is pushing the limits.. Higher than %75 of the total resources..\n -Note : DB Time is the total Oracle CPU consumption plus + sum of non-idle wait time.\n "

if float(hard_parses)>float(total_parses)/10:
  print "*We are doing lots of hard parses.. Hard parse / Total parse ratio is  %", format(float(hard_parses)/float(total_parses)*100,'.2f')
  print "*Total parse / Execute ratio is %", format(float(total_parses)/float(total_executed_sql)*100,'.2f')
  print "*We spent %",parse_cpu_to_parse_elapsed," of our parse time on the CPU..."
  print "*Parsing takes place in %",100-float(execute_to_parse)," of the executions.."
  print "*%",format(float(total_db_hardparse_time)/float(total_sqlexecute_time)*100,'.2f')," of total sql elapsed time is spent on hard parsing..\n->Fixing this hard parse issue will have a positive effect on response time."

Monday, February 1, 2021

Erman Arslan's Oracle Forum -- January 2021 - "Questions and Answers Series"

Question: How much time do you spend/lose?

Answer: Well, how much time I gain? :) 

Remember, you can ask questions and get remote support using my forum.
Just click on the link named "Erman Arslan's Oracle Forum is available now.
Click here to ask a question", which is available on the main page of Erman Arslan's Oracle Blog -- or just use the direct link:



Come on, let's see what we've been up to this month. (Do not forget to read the blog posts too :)

Sunday, January 31, 2021

EBS -- Using Python to catch and terminate runaway forms runtime processes / Runaway Forms Runtime Handler

 Whenever I need to develop some tools to ease dev ops or administration, I usually use perl and some bash scripts to do the job.. Sometimes, when I need to go a little more deep, I use C and when I need some GUI in the client-server mode, I use.. (when I need to write a TUI, I use dialog uility in Linux as well).. It's been a long time since I used Python. As far as I remember, the last time I used python was in my senior year at university.

So, today I wanted to make a change and wrote a python program to detect runaway forms processes (frmweb processes that don't have a database session but  have high cumulative cpu time) and terminate them.. 

Actually, I could almost do the same thing with 5-10 lines of perl, python or bash code. But! My goal was to actually prepare something to help friends in the oracle world who are thinking of programming a system and have an eye on Python. So, I wrote this program a little longer and in tutorial mode. In this context, I tried to use as many features as possible. ( functions, logging, 2d array-like objects, iterations on array, oracle db connection, environment variable handling inside the code and so on)

Reports bugs to -> rman.arslan@gmail.com :)

I shared the code/script in the next paragraphs of this arcticle (Under the heading of RUNAWAY FORMS RUNTIME HANDLER), but first let's see an example output that the code generates when we execute it.. Note that, you also see the logfile that the code produces below;


We have some features and I listed there here -> 
  • Handle LD_LIBRARY_PATH environment variable
  • Oracle DB Connectivity 
  • Get Apps password as an input without showing it in the command line.
  • Batch and interactive modes.
  • Connect to DB once and use that connection while iterating our 2d array.
  • Identifying runaway forms processes (having a process id, but not having a db session & having a high cumulative cpu time. >2h
  • Parsing ps output, storing in an 2d array. 
  • Logging -- date + findings/actions

RUNAWAY FORMS RUNTIME HANDLER:

#!/usr/bin/python
#First things first; we tell our shell -> use python to interpret and run this script.. This way, we don't need to write pyhton in front of the script name --  everytime we execute this script
#usage : ./runaway_frmweb_handler.py
#we run the script with the forms server OS user / EBS application owner OS user.. ex: applmgr

"""
There may be some cases, where we have forms processes left over and running for a long time without doing anything.
In most of the  cases, we see them spinning on the CPU and we identify them with their huge CPU usage -- time.
This script is used to identify and kill those types of forms processers in order to decrease the unncessary load on the application servers.
Tested on EBS R12 , but it needs to be tested more..
Script Creation Date : 24.01.2021
Last Modification Date : 27.01.2021
Lang: python
Author: Erman Arslan

Features:
Handle LD_LIBRARY_PATH environment variable
Oracle DB Connectivity 
Get Apps password as an input without showing it in the command line.
Batch and interactive modes.
Connect to DB once and use that connection while iterating our 2d array.
Identifying runaway forms processes (having a process id, but not having a db session & having a high cumulative cpu time. >2h
Parsing ps output, storing in an 2d array. 
Logging -- date + findings/actions
"""

######################################################
############## IMPORTING MODULES ##################
######################################################

"""
We import the modules that we need use in our code. import command is similar to #include in C/C++ ..
We could also have imported the required objects only.. (rather than importing the whole module) - Using from <module_name> import <names>
Note that, we also import numpy. Numpy provides a high-performance multidimensional array object, and tools for working with these arrays.
In order to have numpy in our server, we installed pip and then using pip, we installed numpy.
By using the as keyword, we give numpy an alternate name and we use that alternate name in our code.(just to make easier for us to write the name, we use np as an alternate way here..
 
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py
pip install numpy
"""

import subprocess
import os
import sys
import cx_Oracle
import numpy as np
from datetime import datetime
from datetime import date
import logging
import getpass

####note that, we set the LD_LIBRARY_PATH to the instant client directory. We handle it.. We set it to the directoy where oracle client libraries reside.

if ('LD_LIBRARY_PATH' not in os.environ or '/oracle_client/instantclient_12_1' not in os.environ['LD_LIBRARY_PATH']):
   os.environ['LD_LIBRARY_PATH'] = '/oracle_client/instantclient_12_1'
   try:
    os.execv(sys.argv[0], sys.argv)
   except Exception, exc:
    print 'Failed re-exec:', exc
    sys.exit(1)

"""
We install cx_Oracle module, as well.. pip install cx_Oracle==7.3
for EBS 12.1, we install 64 bit oracle client, as we have 32 bit oracle homes in apps nodes..  If we dont have 64 bit oracle client in place,  get the following error:
cx_Oracle.DatabaseError: DPI-1047: Cannot locate a 64-bit Oracle Client library: "libclntsh.so: wrong ELF class: ELFCLASS32". See https://oracle.github.io/odpi/doc/installation.html#linux for help

instantclient-basic-linux.x64-12.1.0.2.0.zip
[root@ebstestdb oracle_client]# unzip instantclient-basic-linux.x64-12.1.0.2.0.zip
Archive:  instantclient-basic-linux.x64-12.1.0.2.0.zip
  inflating: instantclient_12_1/adrci
  inflating: instantclient_12_1/BASIC_README
  inflating: instantclient_12_1/genezi
  inflating: instantclient_12_1/libclntshcore.so.12.1
  inflating: instantclient_12_1/libclntsh.so.12.1
  inflating: instantclient_12_1/libipc1.so
  inflating: instantclient_12_1/libmql1.so
  inflating: instantclient_12_1/libnnz12.so
  inflating: instantclient_12_1/libocci.so.12.1
  inflating: instantclient_12_1/libociei.so
  inflating: instantclient_12_1/libocijdbc12.so
  inflating: instantclient_12_1/libons.so
  inflating: instantclient_12_1/liboramysql12.so
  inflating: instantclient_12_1/ojdbc6.jar
  inflating: instantclient_12_1/ojdbc7.jar
  inflating: instantclient_12_1/uidrvci
  inflating: instantclient_12_1/xstreams.jar

We also soft link the library cd /oracle_client/instantclient_12_1; ln -s libclntsh.so.12.1 libclntsh.so
Note that, we already imported the cx_Oracle module above..
"""

######################################################
############## FUNCTION DEFINITIONS ################
######################################################

# We define our kill, db_conn and a db_check functions just to use some functions in python :)
# Note that, we create a single connection and use that connection while iterating our 2d array..

def SIGKILL_func(forms_pid):
 kill_cmd='kill -9 ' + forms_pid
 os.system(kill_cmd)

def db_conn(apps_pass):
 EBS_tns = cx_Oracle.makedsn('ebstestdb', '1555', service_name='TEST') # if needed, place an 'r' before any parameter in order to address special characters such as '\'.
 global conn
 conn = cx_Oracle.connect(user=r'APPS', password=apps_pass, dsn=EBS_tns)

def db_check(forms_db_pid):
 process_check_query = "select PROCESS from v$session where PROCESS=",forms_db_pid
 process_check_query = ''.join(process_check_query)
 c = conn.cursor()
 c.execute(process_check_query)
 c.fetchone() #we try the fetch one record to populate the c.rowcount properly..
 if (c.rowcount == 0):
  print "This process has no db session, so it is ok to be killed"
  logging.info('This process has no db session, so it is ok to be killed')
  return "killable"
 else:
  c.execute(process_check_query)
  for row in c:
   if(row[0]==forms_db_pid):
    logging.info('This process has db session, so we should not kill it.')
    print "This process has db session, so we should not kill it."
    return "Not killable"
   else:
    logging.info('This is weird')
    print "This is weird.."
    return "Not killable"

######################################################
############## WE START HERE  #######################
######################################################

#We first check our command line arguments and exit if we don't like the command line..

if (len(sys.argv) > 2 ):
 print "Wrong argument given..\n Usage : runaway_frmweb_handler.py or runaway_frmweb_handler.py batch"
 quit()
elif ( len(sys.argv) == 2 ):
  if (sys.argv[1]!="batch"):
   print "Wrong argument given..\n Usage : runaway_frmweb_handler.py or runaway_frmweb_handler.py batch"
   quit()
  else:
   print "Running in batch mode."
else:
 print "Running in interactive mode."


#We start logging to file here... We will log our findings and actions in /tmp/runaway_frmweb_handler.log.

import logging
logging.basicConfig(filename='/tmp/runaway_frmweb_handler.log', filemode='w',encoding='utf-8',format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p', level=logging.INFO)
logging.info('Script started')


#We also get our apps password here. We get apps password  without displaying it in the shell..
apps_password= getpass.getpass("Enter your APPS password: ")

#We connect to the database
db_conn(apps_password)

"""
We build our command to check Linux process, which have high cumulative cpu time..
Cumulative CPU time, "[DD-]hh:mm:ss" format. (alias time).
That cumulative CPU time we get from command corresponds to the TIME+ value that we see in top command output..
Ofcouse we get process ids as well.. We use process ids to kill those runaway processes..
Note that, we execute our command using subprocess call, we get the output and we manipulate the output array with numpy.reshape.
"""

cmd = ['ps -eo pid,cputime,euser,ucmd | grep `whoami` |grep -v grep  | grep frmweb | awk {\'print $1" "$2\'}']
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
process_to_kill_counter=0
o,e = proc.communicate()
ps_array=o.decode('ascii')
ps_array=ps_array.split()
import numpy as np
ps_array_2d = np.reshape(ps_array, (len(ps_array)/2, 2))
array_length=len(ps_array)/2

"""
We check our array and get the process ids of the runaway forms processes.. -if there are any...
We ask if it is okay to  kill those processes that we identify.
That is it.
"""

"""
Note that, if we see 4th digit, I mean if we see a value in the day files of the cumulative cpu time, then we directly consider that process runaway.
If we don't have a 4th digit, we check -> if ( cputime_hour_count>1 ), and decide.. 
We treat a forms process with a cumulative CPU time of more than 2 hours as a runaway. We still get user's confirmation before doing anything..
"""
if (array_length <= 0):
 print "There are no forms processes running"
 logging.info('There are no forms process running, I quit.')
 quit()
for x in range(array_length):
 process_id = ps_array_2d[x][0]
 try:
  cputime = datetime.strptime(ps_array_2d[x][1], '%d-%H:%M:%S')
  print "Found a runaway process with a cumulative cpu time > 1 day ->", process_id
  logging.info('Found a runaway process with a cumulative cpu time > 1 day -> %s', process_id)
  if (db_check(process_id)=="killable"):
   process_to_kill_counter += 1
   if (len(sys.argv) != 2):  # we already control the cmd line arguments in the beginning, so if we are here then it means all arguments are correct, so it is sufficient to check the length..
    answer_input= raw_input("Do you want me to kill it? Y or N :")
    if (answer_input=="Y"):
      logging.info ('Approved! Killing Process id: %s',process_id)
      print "Killing Process id : ", process_id
      SIGKILL_func(process_id)
    else :
      logging.info('Disapproved! I will leave it running')
      print "Okay.. I will leave it running"
   else :
    logging.info ('I m in batch mode, so approved! Killing Process id: %s',process_id)
    print "Killing Process id : ", process_id
    SIGKILL_func(process_id)
 except ValueError:
  cputime = datetime.strptime(ps_array_2d[x][1], '%H:%M:%S')
  cputime_hour_count=cputime.hour
  if ( cputime_hour_count>1 ):
   print "Found a runaway process with a cumulative cpu time > 2h ->", process_id
   logging.info('Found a runaway process with a cumulative cpu time > 2h -> %s',process_id)
   if (db_check(process_id)=="killable"):
    process_to_kill_counter += 1
    if (len(sys.argv) != 2): # we already control the cmd line arguments in the beginning, so if we are here then it means all arguments are correct, so it is sufficient to check the length.. 
     answer_input= raw_input("Do you want me to kill it? Y or N :")
     if (answer_input=="Y"):
      logging.info('Approved! Killing Process id : %s', process_id)
      print "Killing Process id : ", process_id
      SIGKILL_func(process_id)
     else :
      logging.info('Disapproved! I will leave it running')
      print "Okay.. I will leave it running"
    else :
     logging.info ('I m in batch mode, so approved! Killing Process id: %s',process_id)
     print "Killing Process id : ", process_id
     SIGKILL_func(process_id)
if ( process_to_kill_counter==0 ):
 logging.info('No runaway form processes to kill')
 print "No runaway form processes to kill"
conn.close() #we close our database connection at the end

RDBMS-- Parsing the listener log with a straight forward in-database method / by taking that jdbc_url anomaly into consideration :)

Remember; years ago, I shared a method for reading the listener log files in a scriptized way.. This type of a method can be used during security checks, or during a migration project (to see the connections, to know by which applications the connections to the database are made..) 

I want to remind you again; in migration projects, this type of an analysis makes our job easier especially when bundling .(when deciding the migration bundles)

You can review that blog post -> https://ermanarslan.blogspot.com/2016/03/rdbms-listener-logs-jdbc-parsing.html

Today, I give you another method for reading and parsing the listener log files. This one is purely an in-database method, and it is pretty straight forward.

I want to mention that, we handle the listener log lines which include jdbc_url correctly.. As you may already know; HOST parameter in the CONNECT_STRING shows __jdbc__ when the client connects to the database using the Jdbc thin driver. So, in order to determine the the real host name of these jdbc clients, we should use the info recorded in the PROTOCOL_INFO.

Let's see;

First, we create a database directory to reach the listener trace/log file from the database..

create or replace directory LISTENER_LOG as '/u01/dbebs/PROD/db/tech_st/11.2.0/admin/PROD_ebstestdb/diag/tnslsnr/ebstestdb/prod/trace';

We create an external table to read the listener log file using the database directory we just created. (we use our listener log file name in the location caluse..)

create table listener_log_ea ( line varchar2(4000))organization external (type oracle_loader default directory LISTENER_LOG access parameters (records delimited by newline nobadfile nologfile nodiscardfile fields ldrtrim missing field values are null reject rows with all null fields (line char (40000)))location ('prod.log'))reject limit unlimited;

 Lastly we query the external table by taking that jdbc anomaly into consideration :)

with details as
(
select last_value(tstamp ignore nulls) over ( order by tstamp ) as tstamp,
substr(host,1,instr(host,')')-1) host,
substr("USER",1,instr("USER",')')-1) "USER"
from
( select
case when line like '__-___-____ __:__:__ %' then to_date(substr(line,1,20),'DD-MON-YYYY HH24:MI:SS') end tstamp,
case when line like '%HOST=__jdbc__%' then substr(line,instr(line,'(ADDRESS=(PROTOCOL=tcp)(HOST=')+29) else
case when line like '%HOST=%' then substr(line,instr(line,'HOST=')+5) end
end host,
case when line like '%USER=%' then substr(line,instr(line,'USER=')+5) end "USER"
from listener_log_ea
)
)
select *
from details where host is not null

That's it.. That "with" query can be modified according to the needs and the characteristics of the environment.. After all, you got the point..

Wednesday, January 27, 2021

RDBMS -- DBMS_IJOB.CHANGE_ENV - Change Log_user, Priv_user, Schema_user and NLS_ENV of a DBMS_JOB -- without the need to recreate.

Here is a quick tip for the ones using DBMS_JOB.

Using DBMS_IJOB (undocumented), we can manipulate dbms jobs.
Ofcouse, DBMS_JOB is the best known interface for manipulating jobs, but DBMS_IJOB let us even change the LOG_USER ,PRIV_USER and SCHEMA_USER of a dbms job. In this way, we don't need to recreate the job for such a change. 

Here is a demo;

SQL> DECLARE
X NUMBER;
BEGIN
begin
SYS.DBMS_JOB.SUBMIT
(
job => X
,what => 'null;'
,next_date => to_date('01.27.2021 16:59:35','mm/dd/yyyy hh24:mi:ss')
,interval => 'TRUNC(SYSDATE+1)'
,no_parse => FALSE);
exception
when others then
begin
raise;
end;
end;
END;
/
PL/SQL procedure successfully completed.


SQL> select job,log_user,priv_user,schema_user,interval from dba_jobs where what='null;';

JOB  LOG_USER   PRIV_USER   SCHEMA_USER     INTERVAL
286         SYS              SYS                   SYS               TRUNC(SYSDATE+1)

SQL> begin
for j in (select * from dba_jobs where job = 286)
loop
dbms_ijob.change_env(j.job, 'APPS', 'APPS', 'APPS', j.nls_env);
end loop;
commit;
end;
/
PL/SQL procedure successfully completed.

SQL> select job,log_user,priv_user,schema_user,interval from dba_jobs where what='null;'

JOB  LOG_USER   PRIV_USER   SCHEMA_USER     INTERVAL
286         APPS            APPS                  APPS               TRUNC(SYSDATE+1)

Thursday, January 21, 2021

EBS 12.2 19C DB environments -- Suggestions for some issues (including performance related ones) / based on a real life story

In my previous blog posts, I made some suggestions for dealing with problematic discoverer reports, which may have performance issues especially after 19C database upgrades. In fact, we recently worked in a production EBS environment where there were some serious performance problems.(performance problem after the upgrade.) 

I was like a post-upgrade consultant in that project and with the intense work we have done, we have made the environment work with acceptable performance in a short time.

I continue to share some of the problems we encountered in this type of a consultant work and the solutions or workarounds that have been implemented (in a short time and in pressure) to make the system acceptable in terms of functionality, continuity and performance .

Today I want to touch on 3 specific issues .. (which I also posted on my twitter account recently)

*Issue : We also saw occasional instance terminations in EBS 12.2 19C environments. Espically under high load.. Solution -> Patch 29838337: XDB STRESS TEST - HIT ORA 600 [KJBLREPLAY: DUP] (latest RU can also be considered)

*Issue: We saw continious updates on AR_TRX_BAL_SUMMAR_MAIN_SUM ... This updates were increase the load of the database nodes dramatically.. (although, the database was running on Exadata)

UPDATE AR_TRX_BAL_SUMMARY MAIN_SUM SET RECEIPTS_AT_RISK ...
Those looped updates place a serious load on the system.

So here is the recommendation; If you don't use Credit Management and Advanced Collections applications, these updates may be disabled.. See MOS note - 2445550.1

*Issue : Reverse Journals were taking a lot of time to complete..This was related with Reverse Journals/GLPREV - performance and we created a custom index on GL_JE_HEADERS for that..

Index ON GL_JE_HEADERS (ACCRUAL_REV_JE_HEADER_ID)

We have seen the benefit... Index on (ACCRUAL_REV_PERIOD_NAME, LEDGER_ID) -> can also increase the perf. of automatic reversal.. (also see Patch 29464680) 

-----------------------------------------------

Below, I  share the solution methods for different problems experienced in this environment, as well.

*In case you need to manage the database load manually ; read the following blog post -> 

http://ermanarslan.blogspot.com/2021/01/ebs-122-configuring-application.html

*Read the following blog post for Discoverer - related quick win that we have implemented in this environment-> 

http://ermanarslan.blogspot.com/2020/12/ebs-122-rdbms-optimizerfeaturesenable.html

That 's it for today :)  I hope, this will be helpful to you.