Who's online

There are currently 0 users and 20 guests online.

Recent comments

Oakies Blog Aggregator

Less calls…more performance

In various programming environments, a common metaphor is to open a cursor on the database (a REF CURSOR in Oracle parlance), return that cursor handle to the calling environment, and then that cursor is used to fetch or “fill” a data object, which may map to a grid on screen, or just to an array in memory.

And that’s totally fine – its an effective means to obtain a result set from the database to the calling environment.

For example, a typical PLSQL routine might look like:

PROCEDURE my_proc(p_resultset IN OUT sys_refcursor) IS
   OPEN p_resultset FOR
     SELECT col1, col2, col3, ...
     FROM   my_table
     WHERE  status = 'Unprocessed';

That’s fine when you want a set of rows, and luckily, most programming environments have evolved to the state nowadays where they will perform that retrieval with an array fetch, rather than single row fetches.

Because as a result, we’ve become attuned to always using the REF CURSOR method, so we tend to use this method for all database access.

But what about when we are returning a single row?  For example, if the SQL in the example above was:

SELECT col1, col2, col3, ...
FROM   my_table
WHERE  primary_key_col = 123;

then perhaps fetching from a ref cursor is not the most appropriate course of action. 

Why ?  Because it (typically) will require the programmer to issue two calls to the database:

  • call the PL/SQL routine to open the cursor
  • issue a local “fetch” call, which will then visit the database again to fetch the results

[Depending on the environment, there may even be a third call to close the cursor to ensure cursors dont leak and consume endless amounts of memory]

When retrieving a single row, perhaps think about the efficiency gains of wrapping the SQL within a PLSQL program and pass parameters in and out accordingly. The example above might then become something like:

PROCEDURE my_proc(p_prim_key IN int, p_col1 OUT int, p_col2 OUT int, p_col3 OUT int) IS
     SELECT col1, col2, col3, ...
     INTO   p_col1, p_col2, p_col3
     FROM   my_table
     WHERE  primary_key_col = 123;

The whole operation is done with a single call, and you get the PLSQL cursor caching benefit for free as well.

For high volume calls, this might be a significant performance boost.  Let’s face it – nowadays there is a good chance that your database server is just insanely fast at processing data, whether it be fetching it or storing it.  The speeds and core counts just continue to boggle the mind.  But all of that will count for little if you are jumping back and forth across the network between your middle tier program and the database.  Less calls = less trips = more performance.

Enhancing A Moving Art Project to Beginning Robotics with Raspberry Pi

Back on August 7th, the RMOUG’s Quarterly Education Workshop, I walked everyone through using a Pibrella board to create an art or flyer project that was powered with a python script and a motor.

Today, we’re going to take that same code and reuse it with a simple robotics kit to control power and rotation speeds/time to the Insectoid.  This kit, puchased from the Go! store in Flat Irons Crossing mall, was $16 and can be built in less than an hour, even for a small child. If you’re interested, you can also purchase the kit online at Amazon, but it was cheaper for me in the store to get him than online…


It requires only a small screw driver and is powered by a AAA 1.5 volt battery.  With the power required to the motor, I recommend leaving the battery wired in and the device off, (Raspberry Pi will be glad to supplement the power and you’ll know if you’ve overwhelmed your power requirements to a secondary project just by the sudden cycle of the OS,  so keep this in mind….)

After following the instructions and assembling the gears, the body and legs to complete the robotic insectoid, test it out with the battery and power switch.  Once he tests successfully, then comes the fun part of wiring him to the Pibrella board to control him from our Raspberry Pi.

Using red and black female/male breadboard jumper wires like the ones below-


Create a long black and long red “extension” to connect the insectoid to the Pibrella board.  Now, undo the cap that is connecting the battery and motor wires to the top of the Insectoid.  If the battery is wired into the motor wire in a way that makes the motor wire inseparable from it, re-wrap the battery wire to the motor wire at the base, leaving just the motor battery straight.  Take the motor battery, matching black to black and red to red, inserting the motor wire into the female jumper wire “extension” and ensure it’s tight.  You shouldn’t need any electrical tape to secure it and it should look like the following picture when you’re finished.


You can re-secure the connections under the cap after you’ve added the connection to the female jumper, (as seen for the red, (negative) connector.

Once this is done, it’s time to “recycle” our python script.  Log into the Raspberry Pi and open up a Terminal window.  Review your Python script from the earlier project OR just create a new python script, (.py) in your favorite text editor and type in the following-

 import pibrella
 import time
while true:

You must have a Pibrella card connected to your Raspberry Pi or you’ll need to follow instructions for controlling a motor with GPIO commands, etc. that can be found here.

The Python code does the following:

  1. imports the Pibrella module to use the Pibrella board, (you can do this with a bread board and the Raspberry Pi directly, just love the Pibrella, makes it so much easier.)
  2. import the TIME module to instruct our code how long to perform a specific function.
  3. Then-  after the button on the Pibrella is pushed
    1. turn the pibrella GPIO on and the wires attached are to run the motor.
    2. for TEN seconds
    3. Then shut off and break out.

You execute the script as we do any of our python scripts, using Python 3-

>sudo python3 .py

A video of the run with the robotics project can be seen on YouTube.

The next step is to enhance our script and have our robot do more than just walk forward and/or stop at a certain time.  We’ll want to build out a robot to have a Raspberry Pi control board ON BOARD and control his movements to turn, go in reverse and so forth.

Until next time-



Tags:  ,





Copyright © DBA Kevlar [Enhancing A Moving Art Project to Beginning Robotics with Raspberry Pi], All Right Reserved. 2015.

When Is An Idle Wait Not An Idle Wait?

 In general, performance tuning is quite straightforward. You work out how much time is spent doing what, and follow the numbers to the things that are taking the most time. If you are fortunate to the have the Diagnostics Pack on Enterprise Edition of Oracle you Active Session History will report the active database sessions. If you can measure the overall response time of a batch or application server you have merely to deduct the DB reported in ASH, and difference is client/middleware time.
While that is an over-simplification, it is substantially true. The rest of this blog is about an exception to the general rule.

A War Story 

I've been working with a system that makes extensive, and probably excessive, use of database links. There is a constellation of 3 Oracle databases and there are database links between them. There is also a soon-to-be legacy DB2 database, and one of the Oracle database uses Oracle Transparent Gateway to create Heterogeneous Database Links over which the application running on Oracle references data in the DB2 database.
During a load test, nearly all of the 64 application server processes are reported as busy handling requests, yet ASH reports than usually less than 2 database sessions are concurrently busy. You would expect to see 60 application server processes consuming CPU, but CPU does not appear to be as highly utilised as we should expect.

Application Server Activity
Database Activity

An application SQL trace reports that while the majority of the time is indeed spent on client CPU, about 20% is spent on a few SQL statements that reference synonyms that refer to tables or views in a DB2 database. Why didn't these show up in ASH?

A Simple Test Script 

I wrote a simple test script that just executes the SQL statements that refer to the DB2 objects, but it collects AWR snapshots at the beginning and end of the script, and it also enables Oracle session trace. 

#eeeeee; border: 0px solid #000000; font-family: courier new; font-size: 85%; overflow: auto; padding-left: 4px; padding-right: 4px; width: 95%;">set verify on head on timi on feedback on lines 200 trimspool on
spool otg_db2

variable b1 VARCHAR2(10)
variable b2 VARCHAR2(10)

:b1 := '123456789';
:b2 := '42';

ALTER SESSION SET tracefile_identifier='OTG_DB2';

EXEC dbms_workload_repository.create_snapshot;
EXEC dbms_application_info.set_action('OTG_DB2');
EXEC dbms_monitor.session_trace_enable;

FROM db2_object1 a
WHERE a.field1 = :b1 AND a.field2 = :b2
FROM db2_object2 a
WHERE a.field1 = :b1 AND a.field2 = :b2

exec dbms_application_info.set_action('NONE');
exec dbms_monitor.session_Trace_disable;
exec dbms_workload_repository.create_snapshot;

select sample_id, sample_time, sql_id, sql_plan_hash_value, sql_plan_line_id, sql_exec_id, event
from v$active_session_history
where action = 'OTG_DB2';

spool off

The first interesting output is that although some of the queries on DB2 objects took several seconds, I didn't get any rows in the query on ASH buffer for this session.
So, now let's look at the trace file. This is the output for the longest SQL statement having profiled it in TKPROF. We can see that nearly all the time is spent on an event called HS message to agent.

#eeeeee; border: 0px solid #000000; font-family: courier new; font-size: 85%; overflow: auto; padding-left: 4px; padding-right: 4px; width: 95%;">SELECT …
FROM db2_object1 a
WHERE a.field1 = :b1 AND a.field2 = :b2

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.04 0 0 1 0
Fetch 1 0.00 2.73 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 0.00 2.77 0 0 1 0

Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 35

Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
Disk file operations I/O 1 0.00 0.00
DFS lock handle 1 0.00 0.00
rdbms ipc reply 2 0.00 0.00
HS message to agent 5 2.73 2.77
SQL*Net message to client 1 0.00 0.00
SQL*Net message from client 1 0.00 0.00

And this is the corresponding section of the raw trace. The execution took just under 42ms, and then the fetch took 2.73 seconds.

#eeeeee; border: 0px solid #000000; font-family: courier new; font-size: 85%; overflow: auto; padding-left: 4px; padding-right: 4px; width: 95%;">PARSING IN CURSOR #4579254328 len=171 dep=0 uid=35 oct=3 lid=0 tim=14984112395546 hv=1291470523 ad='700010b2204a518' sqlid='xxxxxxxxxxxxx'
FROM db2_object1 a
WHERE a.field1 = :b1 AND a.field2 = :b2
PARSE #4579254328:c=106,e=172,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=0,tim=14984112395545
WAIT #4579254328: nam='Disk file operations I/O' ela= 23 FileOperation=8 fileno=0 filetype=8 obj#=-1 tim=14984112395633
WAIT #4579254328: nam='DFS lock handle' ela= 5205 type|mode=1146617861 id1=3569868743 id2=0 obj#=-1 tim=14984112401137
WAIT #4579254328: nam='rdbms ipc reply' ela= 75 from_process=46 timeout=900 p3=0 obj#=-1 tim=14984112401291
WAIT #4579254328: nam='rdbms ipc reply' ela= 710 from_process=46 timeout=900 p3=0 obj#=-1 tim=14984112402047
WAIT #4579254328: nam='HS message to agent' ela= 3464 p1=0 p2=0 p3=0 obj#=-1 tim=14984112405590
WAIT #4579254328: nam='HS message to agent' ela= 38459 p1=0 p2=0 p3=0 obj#=-1 tim=14984112444724
WAIT #4579254328: nam='HS message to agent' ela= 17 p1=0 p2=0 p3=0 obj#=-1 tim=14984112444791
EXEC #4579254328:c=932,e=49215,p=0,cr=0,cu=1,mis=1,r=0,dep=0,og=1,plh=0,tim=14984112444865
WAIT #4579254328: nam='SQL*Net message to client' ela= 2 driver id=1650815232 #bytes=1 p3=0 obj#=-1 tim=14984112444958
WAIT #4579254328: nam='HS message to agent' ela= 2730045 p1=0 p2=0 p3=0 obj#=-1 tim=14984115175093
WAIT #4579254328: nam='HS message to agent' ela= 8 p1=0 p2=0 p3=0 obj#=-1 tim=14984115175165
FETCH #4579254328:c=206,e=2730295,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=0,tim=14984115175285
WAIT #4579254328: nam='SQL*Net message from client' ela= 895 driver id=1650815232 #bytes=1 p3=0 obj#=-1 tim=14984115176264
CLOSE #4579254328:c=10,e=16,dep=0,type=0,tim=14984115176343

Across the whole of my test I have 29 waits on this event totalling 5.3s. Why I am not seeing this event in ASH?

#eeeeee; border: 0px solid #000000; font-family: courier new; font-size: 85%; overflow: auto; padding-left: 4px; padding-right: 4px; width: 95%;">OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 9 0.00 0.00 0 0 0 0
Execute 10 0.00 0.21 0 0 1 3
Fetch 7 0.00 5.10 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 26 0.01 5.32 0 0 1 3

Misses in library cache during parse: 3
Misses in library cache during execute: 4

Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 9 0.00 0.00
SQL*Net message from client 9 0.00 0.00
Disk file operations I/O 9 0.00 0.00
DFS lock handle 1 0.00 0.00
rdbms ipc reply 2 0.00 0.00
HS message to agent 29 2.73 5.30
library cache pin 2 0.00 0.00

If I generate an ASH report on the two snapshots created in this test and look at the Foreground events I might start to see why. AWR reports 29 waits totalling 5 seconds, so that agrees. However, note that this is at the bottom of the report with 'SQL*Net message from client' and there is no '%DB Time'.

AWR Foreground Events

We (should) know that 'SQL*Net message from client' is an idle event. So we can understand that there are 806 seconds reported on this event in this AWR report because there were 151 sessions connect to the database, most of which were mostly idle for the 6 seconds between the AWR snapshots. That is perfectly normal.
However, HS message to agent is classed as an idle wait, although the user was not idle. During this event the application is not busy, it is waiting for the Oracle database to respond, which is in turn waiting for the remote non-Oracle database to respond.
 This is in contrast to Oracle-to-Oracle (homogeneous) database links which report time to 'SQL*Net message from dblink' which is not an idle wait, but is part of the 'Network' wait class. I think the difference is an anomaly.
The next question is how much time my application spent on this event. For that I must look at the AWR data, either by generating an AWR report that corresponds to the load test, or directly in dba_hist_system_event.

Waits from AWR snspshots


  1. I am not the first person to call this out, just the latest person to get caught by it. It may be an idle wait, but the user is waiting for the database to respond. 
    Database Link Wait Event Wait Class
    Heterogeneous HS message to agent Idle
    Homogeneous SQL*Net message from dblink Network
  2. I got as far as starting to write an e-mail to a colleague asking for help, but before sending it I realised that I could do more to find out what was happening. Perhaps just the act of try to explain the problem helped me to think more clearly about it.

Oaktable World 2015 San Francisco, Oct 26 & 27

Agenda for Oaktable World 2015, located at Creativity Museum, is

 time Monday Oct 26 Tuesday Oct 27
8  8:30 – Welcome with Mogens  Toon Koppelaars – hash joins and Bloom filters
9 Mark W. Farnham – Minimizing the concurrency footprint of transactions with Logical Units of Work stored in PL/SQL Kyle Hailey – Virtual Data

Kerry Osborne – SQL Translation Framework

Kellyn Pot’Vin-Gorman, AWR Warehouse Trending and Analysis with OBIEE
11 Greg Rahn – Taming JSON with SQL: From Raw to Results

Marco Gralike – Improving XML performance with the 12c In-Memory Column Store

12 ted talks ted talks

Dan Norris – Exadata Database Machine Security

Cary Millsap – Performance

2  John Beresniwicz – AWR Ambiguity: What do do when the numbers don’t add up?

Gwen Shapira – Real-time data integration

3 Kevin Closson – Modern Platform Topics for Modern DBAs

Alex Gorbachev – Big Data 

4 Tanel Poder – Connecting Oracle with Hadoop  Chris Antognini – Adaptive Dynamic Sampling

Ted Talks between noon and 1pm

 Monday  Tuesday
 Eric Grancher – graphing outliers Jonah H. Harris – Manipulating the Oracle Call Interface

Greg Rahn- What Cloud Can Offer For A DBMS

Kellyn Pot’Vin-Gorman – TBD

Jonathan Lewis – indexes, column groups and 12c

Jonathan Lewis Virtual columns


            gluent            pythian


Web Pages Not Databases – Part 2: Fail2ban, Apache, IP Addresses, Linux, SELinux

August 23, 2015 (Modified August 31, 2015, September 14, 2015) (Back to the Previous Article in this Series) I started using Linux in 1999, specifically Red Hat Linux 6.0, and I recall upgrading to Red Hat Linux 6.1 after downloading the files over a 56k modem – the good old days.  I was a little […]

Windows 10 Again

DiagnosticsI wrote a few months ago about having a play with Windows 10 (here).

I’m visiting family today, catching up on all the Windows desktop (and mobile phone) support that I missed while I was away.

I purposely postponed the Windows 10 update on the desktops before I went away, but now I’m back I did the first of them.

The update itself was fine, but it did take a long time. Nothing really to write home about.

I’ve installed the latest version of Classic Shell on the machine, so the experience is similar to what they had before, Windows 8.1 and Classic Shell, which felt like Windows 7. :)

I’ve also switched out their shortcuts from Edge (Spartan) to Internet Explorer 11. They already use a combination of IE, Firefox and Chrome, so I didn’t want to add another thing into the mix. Also, the nephews use the Java plugin for some web-based games, so it is easier to leave them with IE for the time being. Maybe I will introduce Edge later…

So all in all, the user experience is pretty much unchanged compared to what they had before. I guess I will see how many calls Captain Support gets over the coming weeks! :)



Windows 10 Again was first posted on August 22, 2015 at 6:55 pm.
©2012 "The ORACLE-BASE Blog". Use of this feed is for personal non-commercial use only. If you are not reading this article in your feed reader, then the site is guilty of copyright infringement.

Presenting in Perth on 9 September and Adelaide on 11 September (Stage)

For those of you lucky enough to live on the western half of Australia, I’ll be presenting at a couple of events in both Perth and Adelaide in the coming weeks. On Wednesday, 9th September 2015, I’ll be presenting on Oracle Database 12c New Features For DBAs (and Developers) at a “Let’s Talk Oracle” event […]

Avoiding public embarrassment with triggers

If you create a trigger that does not compile, any part of your application that refers to the table with the invalid trigger is basically dead until that trigger becomes valid.  Even if a DML on that table is not going affect any rows, you still wont be able to issue it.  For example,

SQL> delete from T where 1=0;
delete from T where 1=0
ERROR at line 1:
ORA-04098: trigger 'TRG' is invalid and failed re-validation

That’s a bad bad place for your application…and a bad bad place for your career Smile
In 11g and above, you can avoid this using the DISABLE keyword every time you create a trigger
All triggers should be created as:

create or replace
trigger MY_TRIGGER

If the trigger is created and compiled successfully, then  you can enable it.

alter trigger MY_TRIGGER enable

If the trigger for some unforeseen reason does not compile, it is disabled, and hence, the failed compilation will not break your application.

Make it a coding standard for your database developers.

Messed-Up App of the Day: Crux CCH-01W

Today’s Messed-Up App of the Day is the “Crux CCH-01W rear-view camera for select 2007-up Jeep Wrangler models.”

A rear-view camera is an especially good idea in the Jeep Wrangler, because it is very difficult to see behind the vehicle. The rear seat headrests, the wiper motor housing, the spare tire, and the center brake light all conspire to obstruct much of what little view the window had given you to begin with.
The view is so bad that it’s easy to, for example, accidentally demolish a mailbox.
I chose the Crux CCH-01W because it is purpose-built for our 2012 Jeep Wrangler. It snaps right into the license plate frame. I liked that. It had 4.5 out of 5.0 stars in four reviews at, my favorite place to buy stuff like this. I liked that, too.
But I do not like the Crux CCH-01W. I returned it because our Jeep will be safer without this camera than with it. Here’s the story.
My installation process was probably pretty normal. I had never done a project like this before, so it took me longer than it should have. Crux doesn’t include any installation instructions with the camera, which is a little frustrating, but I knew that from the reviews. There is a lot of help online, and Crutchfield helped as much as I needed. After all the work of installing it, it was a huge thrill when I first shifted into Reverse and—voilà!—a picture appeared in my dashboard.
However, that was where the happiness would end. When I tried to use the camera, I noticed right away that the red, yellow, and green grid lines that the camera superimposes upon its picture didn’t make any sense. The grid lines showed that I was going to collide with the vehicle on my left that clearly wasn’t in jeopardy (an inconvenient false negative), and they showed that I was all-clear on the right when in fact I was about to ram into my garage door facing (a dangerous false positive).
The problem is that the grid lines are offset about two feet to the left. Of course, this is because the camera is about two feet to the left of the vehicle’s centerline. It’s above the license plate, below the left-hand tail light.
So then, to use these grid lines, you have to shift them in your mind about two feet to the right. In your mind. There’s no way to adjust them on the screen. Since this camera is designed exclusively for the left-hand corner of a 2007-up Jeep Wrangler, shouldn’t the designers have adjusted the location of the grid lines to compensate?
So, let’s recap. The safety device I bought to relieve driver workload and improve safety will, unfortunately, increase driver workload and degrade safety.
That’s bad enough, but it doesn’t end there. There is a far worse problem than just the misalignment of the grid lines.
Here is a photo of a my little girl standing a few feet behind the Jeep, directly behind the right rear wheel:
And here is what the camera shows the driver while she is standing there:
No way am I keeping that camera on the vehicle.
It’s easy to understand why it happens. The camera, which has a 120° viewing angle, is located so far off the vehicle centerline that it creates a blind spot behind the right-hand corner of the vehicle and grid lines that don’t make sense.
The Crux CCH-01W is one of those products that seems like nobody who designed it ever actually had to use it. I think it should never have been released.
As I was shopping for this project, my son and a local professional installer advised me to buy a camera that mounted on the vehicle centerline instead of this one. I didn’t take their advice because the reviews for the CCH-01W were good, and the price was $170 less. Fortunately, Crutchfield has a generous return policy, and the center-mounting 170°-view replacement camera that I’ll install this weekend has arrived today.
I’ve learned a lot. The second installation will go much more quickly than the first.

Python Pass the Pigs

So I don’t doubt that many parents are bleeding out money for kid’s school fees, supplies, clothing and other demands this time of year.  How many of you are in their local Target, Walmart or other store and after filling the cart with the necessary, the kids eye up the toy aisle and start to ask for something?

Even teens are not immune and may be asking for games.  If you could turn around and say to them, “If you want the game, then why don’t you build it instead of buying it?”

Now as many of you have figured out, I’m starting to build an arsenal of Raspberry Pi content to teach in classes and meetups.  The following is to simulate the game “Pass the Pigs”.  If you’ve ever played this popular game that uses plastic pigs in the place of dice, it required my brain to rethink how I was coding my dice games to how I would code a game that used the fall placement of a plastic pig.  This demonstrated one of the greatest things about Python-  I still was using the same module, but I just used it in a different way to code my new game!

So let’s say we want to code a simple roll of dice game.

#Dice Roll
import random
#Insert space between import and code...
for x in range(1, 2):
dice_1 = random.randint(1, 6)
dice_2 = random.randint(1, 6)
print(dice_1 + ' + ' + dice_2)

The above code will do the following:

  1. Import the RANDOM module.
  2. Will “roll” the dice 2 times
  3. Will use two dice, with random calls of 1-6.
  4. Will output the first dice ‘+’ the second dice.

Now to change the code and create the Pass the Pigs game, we get to use the RANDOM module again,  but the code changes as we are going to use a function to tell Random what “word options” will be used instead of random numbers to return.  It will then use two pigs and then will output the roll, (which is a function).

#Pass the Pigs
import random

pig_fall = ['Razorback', 'Trotter', 'Snouter', 'Leaning Jowl', 'Pig Out', 'Oinker']
def pick_pigs():
        Pig1 = random.randint(0, len(pig_fall) -1)
        Pig2 = random.randint(0, len(pig_fall) -1)
        return pig_fall[Pig1] + ' + ' + pig_fall[Pig2]
print (pick_pigs())

This code is different from the first one, even though it really ends up doing the same thing.  Notice that there are six options, just like there are six sides to a die.  This is really very similar to the dice game, we’ve just used different code that works more efficiently with our requirements.  By execution the code, we can then play the game!

>sudo python3 .py
Leaning Jowl + Pig Out

Trotter + Pig Out

Leaning Jowl + Razorback

We can use the game instructions, (can be found online) to then add up our scores for our three players-


So there you have it and next time your kids ask you for toys and games, consider getting them a Raspberry Pi instead and make it pay forward, creating projects and games with it instead!

Until next time…




Tags:  ,





Copyright © DBA Kevlar [Python Pass the Pigs], All Right Reserved. 2015.