Wednesday, 17 December 2014

RHadoop not working: Java heap space error



    Hello!

    I am trying to get RHadoop working on a Hadoop cluster.

    This is a test cluster for development / proof of concept.
    It has 5 nodes, virtualized in VMware.
    The OS on all nodes is centos 6.4

    To install R on Hadoop, I followed these instructions:

    https://github.com/RevolutionAnalytics/RHadoop/wiki

    I did not encounter any errors during installation.
    However, the following example analysis is constantly failing with a Java heap size error:

    groups = rbinom(100, n = 500, prob = 0.5)
    tapply(groups, groups, length)
    require(‘rmr2′)
    groups = rbinom(100, n = 500, prob = 0.5)
    groups = to.dfs(groups)
    result = mapreduce(
    input = groups,
    map = function(k,v) keyval(v, 1),
    reduce = function(k,vv) keyval(k, length(vv)))
    print(result())
    print(from.dfs(result, to.data.frame=T))

    The code above is from this repo:

    https://github.com/hortonworks/HDP-Public-Utilities/tree/master/Installation/r

    Please find more information here:

    https://raw.githubusercontent.com/manuel-at-coursera/mixedStuff/master/RHadoop_bugReport.md

    Any help to get this solved would be very much appreciated!

    Best,

    Manuel

Viewing 3 replies - 1 through 3 (of 3 total)

You must be logged in to reply to this topic.

    Author
    Replies
    October 1, 2014 at 4:17 am #61153

    Manuel
    Participant

    Yes, the issue seems to be resolved (or at least a work-around available):

    https://groups.google.com/forum/#!topic/rhadoop/E1-riwegvD4

    Basically, it boils down to that rmr2 uses its own settings, independent from what was set in Hadoop itself.
    The feedback from Antonio (link above) was very helpful.

    The following information might be helpfull as well.

    Within Hadoop I used these settings:

    Number of containers: 2
    RAM per container: 2048 MB

    Configuration Setting Value Calculation Value (MB)
    yarn.nodemanager.resource.memory-mb = containers * RAM-per-container 4096
    yarn.scheduler.minimum-allocation-mb = RAM-per-container 2048
    yarn.scheduler.maximum-allocation-mb = containers * RAM-per-container 4096
    mapreduce.map.memory.mb = RAM-per-container 2048
    mapreduce.reduce.memory.mb = 2 * RAM-per-container 4096
    mapreduce.map.java.opts = 0.8 * RAM-per-container 1638
    mapreduce.reduce.java.opts = 0.8 * 2 * RAM-per-container 3277
    yarn.app.mapreduce.am.resource.mb = 2 * RAM-per-container 4096
    yarn.app.mapreduce.am.command-opts = 0.8 * 2 * RAM-per-container 3277

    For rmr2, I used this code to change all in one go (starting from the prompt in centOS 6.4):

    R;
    library(rmr2);
    bp = rmr.options(“backend.parameters”);
    bp$hadoop[1] = “mapreduce.map.java.opts=-Xmx1024M”;
    bp$hadoop[2] = “mapreduce.reduce.java.opts=-Xmx2048M”;
    bp$hadoop[3] = “mapreduce.map.memory.mb=1280″;
    bp$hadoop[4] = “mapreduce.reduce.memory.mb=2560″;
    rmr.options(backend.parameters = bp);
    rmr.options(“backend.parameters”)

Determine HDP Memory Configuration Settings

9. Determine HDP Memory Configuration Settings

Two methods can be used to determine YARN and MapReduce memory configuration settings:


The HDP utility script is the recommended method for calculating HDP memory configuration settings, but information about manually calculating YARN and MapReduce memory configuration settings is also provided for reference.

 9.1. Use the HDP Utility Script to Calculate Memory Configuration Settings

This section describes how to use the hdp-configuration-utils.py Python script to calculate YARN, MapReduce, Hive, and Tez memory allocation settings based on the node hardware specifications. The hdp-configuration-utils.py script is included in the HDP companion files.
Running the Script
To run the hdp-configuration-utils.py script, execute the following command from the folder containing the script:
python hdp-configuration-utils.py <options>
With the following options:

Option Description
-c CORES The number of cores on each host.
-m MEMORY The amount of memory on each host in GB.
-d DISKS The number of disks on each host.
-k HBASE "True" if HBase is installed, "False" if not.

Note: You can also use the -h or --help option to display a Help message that describes the options.
Example
Running the following command:
python hdp-configuration-utils.py -c 16 -m 64 -d 4 -k True
Would return:
 Using cores=16 memory=64GB disks=4 hbase=True
 Profile: cores=16 memory=49152MB reserved=16GB usableMem=48GB disks=4
 Num Container=8
 Container Ram=6144MB
 Used Ram=48GB
 Unused Ram=16GB
 yarn.scheduler.minimum-allocation-mb=6144
 yarn.scheduler.maximum-allocation-mb=49152
 yarn.nodemanager.resource.memory-mb=49152
 mapreduce.map.memory.mb=6144
 mapreduce.map.java.opts=-Xmx4096m
 mapreduce.reduce.memory.mb=6144
 mapreduce.reduce.java.opts=-Xmx4096m
 yarn.app.mapreduce.am.resource.mb=6144
 yarn.app.mapreduce.am.command-opts=-Xmx4096m
 mapreduce.task.io.sort.mb=1792
 tez.am.resource.memory.mb=6144
 tez.am.java.opts=-Xmx4096m
 hive.tez.container.size=6144
 hive.tez.java.opts=-Xmx4096m
 hive.auto.convert.join.noconditionaltask.size=1342177000

 9.2. Manually Calculate YARN and MapReduce Memory Configuration Settings

This section describes how to manually configure YARN and MapReduce memory allocation settings based on the node hardware specifications.
YARN takes into account all of the available compute resources on each machine in the cluster. Based on the available resources, YARN negotiates resource requests from applications (such as MapReduce) running in the cluster. YARN then provides processing capacity to each application by allocating Containers. A Container is the basic unit of processing capacity in YARN, and is an encapsulation of resource elements (memory, cpu etc.).
In a Hadoop cluster, it is vital to balance the usage of memory (RAM), processors (CPU cores) and disks so that processing is not constrained by any one of these cluster resources. As a general recommendation, allowing for two Containers per disk and per core gives the best balance for cluster utilization.
When determining the appropriate YARN and MapReduce memory configurations for a cluster node, start with the available hardware resources. Specifically, note the following values on each node:
  • RAM (Amount of memory)
  • CORES (Number of CPU cores)
  • DISKS (Number of disks)
The total available RAM for YARN and MapReduce should take into account the Reserved Memory. Reserved Memory is the RAM needed by system processes and other Hadoop processes (such as HBase).
Reserved Memory = Reserved for stack memory + Reserved for HBase Memory (If HBase is on the same node)
Use the following table to determine the Reserved Memory per node.
Reserved Memory Recommendations

Total Memory per Node Recommended Reserved System Memory Recommended Reserved HBase Memory
4 GB 1 GB 1 GB
8 GB 2 GB 1 GB
16 GB 2 GB 2 GB
24 GB 4 GB 4 GB
48 GB 6 GB 8 GB
64 GB 8 GB 8 GB
72 GB 8 GB 8 GB
96 GB 12 GB 16 GB
128 GB 24 GB 24 GB
256 GB 32 GB 32 GB
512 GB 64 GB 64 GB

The next calculation is to determine the maximum number of containers allowed per node. The following formula can be used:
# of containers = min (2*CORES, 1.8*DISKS, (Total available RAM) / MIN_CONTAINER_SIZE)
Where MIN_CONTAINER_SIZE is the minimum container size (in RAM). This value is dependent on the amount of RAM available -- in smaller memory nodes, the minimum container size should also be smaller. The following table outlines the recommended values:

Total RAM per Node Recommended Minimum Container Size
Less than 4 GB 256 MB
Between 4 GB and 8 GB 512 MB
Between 8 GB and 24 GB 1024 MB
Above 24 GB 2048 MB

The final calculation is to determine the amount of RAM per container:
RAM-per-container = max(MIN_CONTAINER_SIZE, (Total Available RAM) / containers))
With these calculations, the YARN and MapReduce configurations can be set:
Configuration File Configuration Setting Value Calculation
yarn-site.xml yarn.nodemanager.resource.memory-mb = containers * RAM-per-container
yarn-site.xml yarn.scheduler.minimum-allocation-mb = RAM-per-container
yarn-site.xml yarn.scheduler.maximum-allocation-mb = containers * RAM-per-container
mapred-site.xml mapreduce.map.memory.mb = RAM-per-container
mapred-site.xml         mapreduce.reduce.memory.mb = 2 * RAM-per-container
mapred-site.xml mapreduce.map.java.opts = 0.8 * RAM-per-container
mapred-site.xml mapreduce.reduce.java.opts = 0.8 * 2 * RAM-per-container
yarn-site.xml (check) yarn.app.mapreduce.am.resource.mb = 2 * RAM-per-container
yarn-site.xml (check) yarn.app.mapreduce.am.command-opts = 0.8 * 2 * RAM-per-container
Note: After installation, both yarn-site.xml and mapred-site.xml are located in the /etc/hadoop/conf folder.
Examples
Cluster nodes have 12 CPU cores, 48 GB RAM, and 12 disks.
Reserved Memory = 6 GB reserved for system memory + (if HBase) 8 GB for HBase
Min container size = 2 GB
If there is no HBase:
# of containers = min (2*12, 1.8* 12, (48-6)/2) = min (24, 21.6, 21) = 21
RAM-per-container = max (2, (48-6)/21) = max (2, 2) = 2

Configuration Value Calculation
yarn.nodemanager.resource.memory-mb = 21 * 2 = 42*1024 MB
yarn.scheduler.minimum-allocation-mb = 2*1024 MB
yarn.scheduler.maximum-allocation-mb = 21 * 2 = 42*1024 MB
mapreduce.map.memory.mb = 2*1024 MB
mapreduce.reduce.memory.mb          = 2 * 2 = 4*1024 MB
mapreduce.map.java.opts = 0.8 * 2 = 1.6*1024 MB
mapreduce.reduce.java.opts = 0.8 * 2 * 2 = 3.2*1024 MB
yarn.app.mapreduce.am.resource.mb = 2 * 2 = 4*1024 MB
yarn.app.mapreduce.am.command-opts = 0.8 * 2 * 2 = 3.2*1024 MB

If HBase is included:
# of containers = min (2*12, 1.8* 12, (48-6-8)/2) = min (24, 21.6, 17) = 17
RAM-per-container = max (2, (48-6-8)/17) = max (2, 2) = 2

Configuration Value Calculation
yarn.nodemanager.resource.memory-mb = 17 * 2 = 34*1024 MB
yarn.scheduler.minimum-allocation-mb = 2*1024 MB
yarn.scheduler.maximum-allocation-mb = 17 * 2 = 34*1024 MB
mapreduce.map.memory.mb = 2*1024 MB
mapreduce.reduce.memory.mb          = 2 * 2 = 4*1024 MB
mapreduce.map.java.opts = 0.8 * 2 = 1.6*1024 MB
mapreduce.reduce.java.opts = 0.8 * 2 * 2 = 3.2*1024 MB
yarn.app.mapreduce.am.resource.mb = 2 * 2 = 4*1024 MB
yarn.app.mapreduce.am.command-opts = 0.8 * 2 * 2 = 3.2*1024 MB

Notes:
  1. Changing yarn.scheduler.minimum-allocation-mb without also changing yarn.nodemanager.resource.memory-mb, or changing yarn.nodemanager.resource.memory-mb without also changing yarn.scheduler.minimum-allocation-mb changes the number of containers per node.
  2. If your installation has high RAM but not many disks/cores, you can free up RAM for other tasks by lowering both yarn.scheduler.minimum-allocation-mb and yarn.nodemanager.resource.memory-mb.

 9.2.1. Configuring MapReduce Memory Settings on YARN

MapReduce runs on top of YARN and utilizes YARN Containers to schedule and execute its Map and Reduce tasks. When configuring MapReduce resource utilization on YARN, there are three aspects to consider:

  • The physical RAM limit for each Map and Reduce task.
  • The JVM heap size limit for each task.
  • The amount of virtual memory each task will receive.

You can define a maximum amount of memory for each Map and Reduce task. Since each Map and Reduce task will run in a separate Container, these maximum memory settings should be equal to or greater than the YARN minimum Container allocation.
For the example cluster used in the previous section (48 GB RAM, 12 disks, and 12 cores), the minimum RAM for a Container (yarn.scheduler.minimum-allocation-mb) = 2 GB. Therefore we will assign 4 GB for Map task Containers, and 8 GB for Reduce task Containers.
In mapred-site.xml:
<name>mapreduce.map.memory.mb</name>
<value>4096</value>
<name>mapreduce.reduce.memory.mb</name>
<value>8192</value>
Each Container will run JVMs for the Map and Reduce tasks. The JVM heap sizes should be set to values lower than the Map and Reduce Containers, so that they are within the bounds of the Container memory allocated by YARN.
In mapred-site.xml:
<name>mapreduce.map.java.opts</name>
<value>-Xmx3072m</value>
<name>mapreduce.reduce.java.opts</name>
<value>-Xmx6144m</value>
The preceding settings configure the upper limit of the physical RAM that Map and Reduce tasks will use. The virtual memory (physical + paged memory) upper limit for each Map and Reduce task is determined by the virtual memory ratio each YARN Container is allowed. This ratio is set with the following configuration property, with a default value of 2.1:
In yarn-site.xml:
<name>yarn.nodemanager.vmem-pmem-ratio</name>
<value>2.1</value>
With the preceding settings on our example cluster, each Map task will receive the following memory allocations:
  • Total physical RAM allocated = 4 GB
  • JVM heap space upper limit within the Map task Container = 3 GB
  • Virtual memory upper limit = 4*2.1 = 8.2 GB
With MapReduce on YARN, there are no longer pre-configured static slots for Map and Reduce tasks. The entire cluster is available for dynamic resource allocation of Map and Reduce tasks as needed by each job. In our example cluster, with the above configurations, YARN will be able to allocate up to 10 Mappers (40/4) or 5 Reducers (40/8) on each node (or some other combination of Mappers and Reducers within the 40 GB per node limit).

An Introduction to Apache Hive

An Introduction to Apache Hive

Hive is an important member of hadoop ecosystem. It runs on top of hadoop.  Hive uses a SQL type query language to process the data in hdfs. Hive is very simple as compared to writing several lines of mapreduce codes using programming languages such as Java. Hive was developed by facebook in a vision to support their SQL experts to handle big data without much difficulty.  Hive queries are easy to learn for people who don’t know any programming languages.  People having experience in SQL can go straight forward with hive queries. The queries fired into hive will ultimately run as mapreduce.
Hive runs in two execution modes, local and distributed mode.
In local, the hive queries run as a single process and uses the local file system. In distributed mode, the mapper and reducer runs as different process and uses the hadoop distributed file system.
The installation of hive was explained well in my previous post Hive Installation.
Hive stores its contents in hdfs and table details (metadata) in some databases. By default the metadata is stored in derby database, but this is just for play around setups only and cannot be used for multiuser environments. For multiuser environments, we can use databases such as mysql, postgresql , oracle etc for storing the hive metadata. The data are stored in hdfs and it is contained in a location called hive warehouse directory which is defined by the property hive.metastore.warehouse.dir. By default this will be /user/hive/warehouse
We can fire queries into hive using a command line interface or using clients written in different programming languages. Hive server exposes a thrift service making hive accessible from various programming languages .
The simplicity and power of hive can be explained by comparing the word count program written in java program and in hive query.
The word count program written in java is well explained in my previous post A Simple Mapreduce Program – Wordcount . For that have to write a lot of lines of code and it will take time and it needs some good programming knowledge also.
The same word count can be done using hive query in a few lines of hive query.
CREATE TABLE docs (line STRING);
LOAD DATA INPATH 'text' OVERWRITE INTO TABLE docs;
CREATE TABLE word_counts AS
SELECT word, count(1) AS count FROM
(SELECT explode(split(line, '\s')) AS word FROM docs) word
GROUP BY word
ORDER BY word;

Configuring Your MapR Cluster for Splice Machine

We recommend using the MapR Quick Installer instructions.
The remainder of this section provides a summarized walk-through of using the MapR Quick Installer and setting the configuration up correctly for Splice Machine:
1. Prepare for your installation:
Prepare for installing MapR by following these steps:
  1. Download the mapr-setup file for your version of Linux:
    Linux VersionDownload
    CentOS / RHELDownload the setup file from this URL:
     http://package.mapr.com/releases/v4.0.1/redhat/mapr-setup
    UbuntuUse this shell command to download the setup file
    wget http://package.mapr.com/releases/v4.0.1/ubuntu/mapr-setup
  2. Navigate to the directory to which you downloaded the file.
  3. Execute permissions on the downloaded setup file:
    chmod 755 mapr-setup
  4. Run mapr-setup to extract the installer files to the /opt/mapr-installer directory:
    sudo ./mapr-setup
2. Start the MapR Interactive Quick Installer:
Invoke the installer, using the private SSH key file you created (id_rsa in the following example):
sudo /opt/mapr-installer/bin/install -s -U root --private id_rsa new
The installer asks you a set of configuration questions; make sure that you substitute your node names and disk paths instead of the highlighted values shown here:
An Installer config file is typically used by experienced MapR admins to skip through the interview process.
Do you have a config file (y/n) [n]: n
Enter the hostnames of all the control nodes separated by spaces or commas []: control-node

Enter the hostnames of all the data nodes separated by spaces or commas []: data-node1, data-node2, data-node3, ...

Set MapR User Name [mapr]: <press enter to accept default>
Set MapR User Password [mapr]: <press enter to accept default>
Is this cluster going to run YARN? (y/n) [y]:n
Is this cluster going to run MapReduce1? (y/n) [n]:y
Is this cluster going to run Apache HBase? (y/n) [y]:y
Is this cluster going to run MapR-DB? (y/n) [n]: n
Enter the full path of disks for hosts separated by spaces or commas []: /dev/sdb, /dev/sdc, /dev/sdd, ...
3. Modify options
When the MapR installer prompts you about whether to continue, modify, or abort, select modify options:
(c)ontinue with install, (m)odify options, or save current configuration and (a)bort? (c/m/a)? m
Select the Force Format Disks option
: F
Force Format Disks [n]: y
: ue
Set MapR Ecosystem Repo URL [http://package.mapr.com/releases/ecosystem]: http://package.mapr.com/releases/ecosystem-4.x/
4. Confirm your configuration:
MapR asks you to verify your configuration before continuing with the installation. After confirming that your options are like the following, enter cont to continue the installation.
And then select the MapR Ecosystem Repository URL and modify its value:
Pick an option to modify
========================
N] Cluster Name: "my.cluster.com"
u] MapR User Name: "mapr"
g] MapR Group Name: "mapr"
U] MapR User UID: "2000"
G] MapR User GID: "2000"
p] MapR User Password: "****"
S] WireLevel Security: "n"
d] Disk Settings: "/dev/sdb,/dev/sdc,/dev/sdd"
sw] Disk Stripe Width: ""
F] Force Format Disks: "y"
c] Client Nodes: ""
C] Control Nodes: "control-node"
D] Data Nodes: "data-node1,data-node2,data-node3"
b] Control Nodes to function as Data Nodes: "n"
v] Version: "4.0.1"
L] Local Repository: "n"
mr] MapReduce1: "y"
db] MapR-DB: "n"
hb] HBase: "y"
y] YARN: "n"
uc] Core Repo URL: "http://package.mapr.com/releases"
ue] Ecosystem Repo URL: "http://package.mapr.com/releases/ecosystem-4.x"
dbh] Metrics DB Host and Port: ""
dbu] Metrics DB User: ""
dbp] Metrics DB Password: ""
dbs] Metrics DB Schema: ""
cont] Continue
: cont
5. Enter the administrative username
When the MapR installer prompts you about whether to continue, modify, or abort, select continue, and when prompted for the SSH Username, enter the username that you have set up for password-less sudo privileges; we use clusteradmin for our examples:
(c)ontinue with install, (m)odify options, or save current configuration and (a)bort? (c/m/a)? [c]: c
SSH Username: clusteradmin
6. Wait for the MapR installation to complete
After a few minutes, you should see the following message:
MapR Installation Successful on Control Nodes. Please login via the web console at https://control-node:8443 or manage the cluster using 'maprcli' or 'hadoop' commands
The MapR installer then exits and returns you to the shell prompt.

Log into the MapR Web Console

Once you have MapR installed, it's a good idea to log into their web console, which they call the MapR Command System (MCS). You can log into the MCS as follows:
  1. Point your browser at https://control-node:8443.
  2. Use username=mapr and password=mapr, unless you modified these when running mapr-setup.

Next Steps in Clustered Version Installation

Follow these steps to continue installing the clustered version of Splice Machine:
  1. Install Splice Machine (remaining steps):
    1. Extract and install the Splice Machine jar
    2. Install the Splice Machine database tools
    3. Update Configuration Files
  2. Stop and then restart Hadoop services
  3. Start using Splice Machine!

Tuesday, 16 December 2014

sshpass command in linux


Posted: May 2, 2014 in Uncategorized
0
ssh login with password instead of waiting for password prompt:
Possible with sshpass.
Check if the server is installed with sshpass by running below command
which sshpass.
If no path found, then install with
#yum install sshpass
if no luck, then download rpm and install it.
sshpass rpm download link:
http://dl.fedoraproject.org/pub/epel/6/x86_64/sshpass-1.05-1.el6.x86_64.rpm
# wget http://dl.fedoraproject.org/pub/epel/6/x86_64/sshpass-1.05-1.el6.x86_64.rpm
# rpm -ivh sshpass-1.05-1.el6.x86_64.rpm
# which sshpass
/usr/bin/sshpass
Now log onto server with password in single command which ignores user prompt for password
sshpass -p ‘leo$123′ ssh root@172.15.25.228
If we want to run any command on other server from one server to prevent log on to other server.
ssh root@172.15.25.228 “netstat -tunlp”
Prompts for password
With password in a single command:
sshpass -p ‘leo$123′ ssh root@172.15.25.228 “netstat -tunlp”

Monday, 15 December 2014

Memory Management Basics

Memory Management Basics

Understanding memory allocation can help you better manage and monitor the health of your nodes and make the most out of your Hadoop cluster. Here are some insights on how memory allocation works especially in the MapReduce v1 world.
Memory allocation at startup
MapR takes care of allocating memory among all the installed services on a node based on percentage of available physical memory. When the package is installed and the configure.sh script is run, MapR generates /opt/mapr/conf/warden.conf file that has details about how much percentage of physical memory needs to be allocated for each service. For example, if you install FileSystem and MapReduce on a node, the memory allocation is distributed between those two services. On the other hand if node also has HBase™ servcies, configure.sh makes sure that the available memory is divided among FileSystem, MapReduce as well as HBase services.
To look at it in more detail, let us extend our first scenario above. If FileSystem (mfs process) and MapReduce (tasktracker process) are installed on a node, MapR allocates 20% of physical memory to FileSystem, about 5-8% of memory for OS and other applications and the rest would be given to MapReduce services (tasktracker and tasks in this case). On an average about 75% of physical memory is assigned to MapReduce in this kind of setting. Note that for the mfs process MapR pre-allocates 20% of memory, which means mfs grabs 20% of memory immediately. Hence when you run top command on a node, you would see that mfs is already using 20% of available memory - this is common and expected. On the other hand, MapReduce services starts off low and eventually grows up to 75% of physical memory because memory is not pre-allocated when you configure and start TaskTracker service.
Memory allocation for MapReduce
Given how the memory is allocated on startup, lets see how memory management works for MapReduce layer. On each node, MapR updates mapred-site.xml with number of map and reduce slots based on number of CPUs on the node. (One can change that to either absolute value or include different parameters like memory to come up with a formula to support heterogeneous nodes). This defines the compute capacity of the node. Once software is installed and configured (by running configure.sh), running Tasks would be assigned certain amount of default max memory. This calculation is based on the number of map and reduce slots.
Lets consider an example. On a 48G physical memory box, lets assume we configure 9 map slots and 6 reduce slots. Based on previous discussion, we would have allocated about 75% of memory for all of MapReduce services. This includes TaskTracker, Map Tasks and Reduce Tasks. TaskTracker usually consumes 130MB, so we can ignore it for calculation purpose. 75% of physical memory in this case is approximately 36G. Now, 36G of memory has to be divided into 9 Map and 6 Reduce slots. Based on a formula (see below), we end up dividing 36G of memory into 2.1G for each map task and 2.8G for each reduce task. When tasks are spawned on a node, MapR sets Xmx with the right memory value for the Map and Reduce slots. This way Mapper on that node will never go over 2.1G and reducer will never go over 2.8G. Note that as of now MapR does not allocate unused memory to running tasks. That means that on a node if there is 10G free memory and only one map task running which requires more than 2.1G, it will get killed with OOM exception.
Formula for calculating Xmx for MapReduce tasks.
Default memory for a map task =
   (Total Memory reserved for mapreduce) * (1 / (#mapslots + 1.3*#reduceslots))

Default memory for a map task =
   (Total Memory reserved for mapreduce) * (1.3 / (#mapslots + 1.3*#reduceslots))
Given this, on a normal running system if all Mapper and Reducers are running on a node and they are utilizing close to 100% of allocated memory, then 75% of physical memory should be utilized on the node. If any task tries to go over that, then those tasks will start getting killed due to OOM exceptions.
With the above default setting, no matter what the Map and Reduce slot numbers are, things should work normal without problems. This is because only 75% of memory is divided among available slots. Problems may surface when one overrides the default calculated Xmx value for Map and Reduce slots. Lets assume that based on slots, MapR ends up calculating 1G for Map tasks. Now, if one overrides that to specify 4G, then system will be overloaded for memory and will start swapping. This causes stability problem for services in the cluster.
Therefore it is important to understand the workload first and then perform memory management on your cluster so it is healthy at all times.
Config variables available to fine-tune this behavior.
Given how memory allocation and management works, there are few config variables that could be used to further fine-tune this. All these variables do, is override the default calculations. Some of them are shown here:
mapreduce.tasktracker.reserved.physicalmemory.mb. By default, 75% memory is allocated to the MapReduce layer. If you want to change that, you can specify absolute value by specifying the desired number in megabytes.
mapred.tasktracker.map.tasks.maximum. By default MapR calculates number of map slots based on number of CPU. Increasing this number will cause lower memory given to each map task. Decreasing this number will cause more memory to be assigned for each map tasks.
mapred.tasktracker.reduce.tasks.maximum. This is similar to above, but for Reduce tasks.

mapred.job.map.memory.physical.mb. This configuration defines how much memory (Xmx) is set for Map tasks. Default is calculated by the Formula specified above. If you want to change allocation, absolute value in megabytes needs to be specified.
mapred.job.reduce.memory.physical.mb. This is similar to above, but good for Reduce tasks.
Some Best Practices
Given this information, you can improve cluster utilization further by doing the below steps.
  • Gather job stats. You should gather nature of jobs and how much memory Map and Reduce tasks consume on a cluster. This could be done with a combination of ganglia and custom monitoring scripts.
  • Identify requirements. With stats, analyze memory usage pattern and identify requirement for jobs running on the cluster.
  • Tune cluster. With stats and requirements tune the cluster using the config variables specified above.
  • Validate changes. Re-gather stats and measure cluster utilization and throughput to validate changes have improved them.

Saturday, 15 November 2014

Toy example from airplane data tutorial throwing errors.

http://grokbase.com/p/gg/rhadoop/132k8h0901/547-toy-example-from-airplane-data-tutorial-throwing-errors

[RHadoop:#396] Toy example from airplane data tutorial throwing errors.

I think this is it:

Loading required package: rmr2
Loading required package: Rcpp
Loading required package: RJSONIO
Loading required package: methods
Loading required package: digest
Loading required package: functional
Loading required package: stringr
Loading required package: plyr
Error in aggregate.data.frame(vv, by = vv[, c("carb", "cyl")], mean) :
no rows to aggregate
Calls: <Anonymous> ... withVisible -> eval -> eval -> reduce -> aggregate.data.frame
Execution halted
java.lang.RuntimeException: PipeMapRed.waitOutputThreads(): subprocess failed with code 1
at org.apache.hadoop.streaming.PipeMapRed.waitOutputThreads(PipeMapRed.java:362)
at org.apache.hadoop.streaming.PipeMapRed.mapRedFinished(PipeMapRed.java:576)
at org.apache.hadoop.streaming.PipeReducer.close(PipeReducer.java:137)
at org.apache.hadoop.mapred.ReduceTask.runOldReducer(ReduceTask.java:530)
at org.apache.hadoop.mapred.ReduceTask.run(ReduceTask.java:421)
at org.apache.hadoop.mapred.Child$4.run(Child.java:255)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:396)
at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1136)
at org.apache.hadoop.mapred.Child.main(Child.java:249)
log4j:WARN No appenders could be found for logger (org.apache.hadoop.hdfs.DFSClient).
log4j:WARN Please initialize the log4j system properly.
- Hide quoted text -

On Tuesday, February 19, 2013 3:43:58 PM UTC-5, Antonio Piccolboni wrote:

Can I see the stderr of the R process? jobtracker web UI, click on job ->
number of failed jobs -> logs
Thanks


Antonio


On Tue, Feb 19, 2013 at 12:36 PM, Jim Solderitsch <jsolde...@gmail.com<javascript:>
wrote:
I tried this within RStudio running in a VM that also contains
Hortonworks Hadoop. I get this result:
from.dfs(+ mapreduce(+ to.dfs(mtcars), + map = + function(k, v) keyval(v[, c('carb', 'cyl')], v), + reduce = + function(k, vv) aggregate.data.frame(vv, by = vv[, c("carb", "cyl")], mean)))13/01/31 10:41:45 INFO util.NativeCodeLoader: Loaded the native-hadoop library13/01/31 10:41:45 INFO zlib.ZlibFactory: Successfully loaded & initialized native-zlib library13/01/31 10:41:45 INFO compress.CodecPool: Got brand-new compressorpackageJobJar: [/tmp/RtmpRXAMpx/rmr-local-env6093222f1f76, /tmp/RtmpRXAMpx/rmr-global-env60935e1a3370, /tmp/RtmpRXAMpx/rmr-streaming-map609379a3967b, /tmp/RtmpRXAMpx/rmr-streaming-reduce60935c8f64be, /tmp/hadoop-sandbox/hadoop-unjar5497485596887405011/] [] /tmp/streamjob3341665760954785005.jar tmpDir=null
13/01/31 10:41:48 INFO mapred.FileInputFormat: Total input paths to process : 1
13/01/31 10:41:48 INFO streaming.StreamJob: getLocalDirs(): [/hadoop/mapred]
13/01/31 10:41:48 INFO streaming.StreamJob: Running job: job_201301300407_0082
13/01/31 10:41:48 INFO streaming.StreamJob: To kill this job, run:
13/01/31 10:41:48 INFO streaming.StreamJob: /usr/lib/hadoop/libexec/../bin/hadoop job -Dmapred.job.tracker=sandbox:50300 -kill job_201301300407_0082
13/01/31 10:41:48 INFO streaming.StreamJob: Tracking URL: http://sandbox:50030/jobdetails.jsp?jobid=job_201301300407_0082
13/01/31 <http://sandbox:50030/jobdetails.jsp?jobid=job_201301300407_008213/01/31> 10:41:49 INFO streaming.StreamJob: map 0% reduce 0%
13/01/31 10:42:01 INFO streaming.StreamJob: map 100% reduce 0%
13/01/31 10:42:09 INFO streaming.StreamJob: map 100% reduce 67%
13/01/31 10:42:14 INFO streaming.StreamJob: map 100% reduce 0%
13/01/31 10:42:22 INFO streaming.StreamJob: map 100% reduce 33%
13/01/31 10:42:29 INFO streaming.StreamJob: map 100% reduce 0%
13/01/31 10:42:37 INFO streaming.StreamJob: map 100% reduce 33%
13/01/31 10:42:42 INFO streaming.StreamJob: map 100% reduce 0%
13/01/31 10:42:50 INFO streaming.StreamJob: map 100% reduce 33%
13/01/31 10:42:56 INFO streaming.StreamJob: map 100% reduce 0%
13/01/31 10:42:58 INFO streaming.StreamJob: map 100% reduce 100%
13/01/31 10:42:58 INFO streaming.StreamJob: To kill this job, run:
13/01/31 10:42:58 INFO streaming.StreamJob: /usr/lib/hadoop/libexec/../bin/hadoop job -Dmapred.job.tracker=sandbox:50300 -kill job_201301300407_0082
13/01/31 10:42:58 INFO streaming.StreamJob: Tracking URL: http://sandbox:50030/jobdetails.jsp?jobid=job_201301300407_0082
13/01/31 <http://sandbox:50030/jobdetails.jsp?jobid=job_201301300407_008213/01/31> 10:42:58 ERROR streaming.StreamJob: Job not successful. Error: # of failed Reduce Tasks exceeded allowed limit. FailedCount: 1. LastFailedTask: task_201301300407_0082_r_000000
13/01/31 10:42:58 INFO streaming.StreamJob: killJob...
Streaming Command Failed!Error in mr(map = map, reduce = reduce, combine = combine, in.folder = if (is.list(input)) { :
hadoop streaming failed with error code 1


BTW, this is similar to some of the failed execution steps I saw when I was trying my earlier experiments.


Any ideas?


Jim

On Thursday, February 14, 2013 6:46:39 PM UTC-5, Antonio Piccolboni wrote:

But why, why does it have to be so complicated? I don't have the
airline data set handy, but let's assume that you figured out the input
format and it is read in as a data frame with some colnames. So let's try
with mtcars which is also a dataframe


from.dfs(
mapreduce(
to.dfs(mtcars),
map =
function(k, v) keyval(v[, c('carb', 'cyl')], v),
reduce =
function(k, vv) aggregate.data.frame(vv, by = vv[, c("carb",
"cyl")], mean)))

Now replace the input, add the input format, replace the col names with
whatever you want to aggregate on and off you go. What am I missing?


Antonio
On Thursday, February 14, 2013 3:03:09 PM UTC-8, Jim Solderitsch wrote:

I can's say where you went wrong but I have a different approach that
seems to work for me. I can't get vectorized keys and values to work.
Reduce seems to get itself into an infinite loop when I do. But when a
concatenate values into strings with paste and then strsplit them in
reduce, I get good results. And the conversion to a data frame works well
to. BUT my version uses the csv input format and currently rmr2 disallows
headers in the source file in this case.

Any way, here is what I came up with. Here I just want to average
Arrival Delays and Departure delays.

#!/usr/bin/env Rscript

#
# Example 2: airline
#
# Calculate average enroute times by year and market (= airport pair)
from the
# airline data set (http://stat-computing.org/**
dataexpo/2009/the-data.html<http://stat-computing.org/dataexpo/2009/the-data.html>
).
# Requires rmr package (https://github.com/**
RevolutionAnalytics/RHadoop/**wiki<https://github.com/RevolutionAnalytics/RHadoop/wiki>
).
#
# by Jeffrey Breen <jef...@jeffreybreen.com>
#

library(rmr2)
library(plyr)

# assumes 'airline' and airline/data exists on HDFS under user's home
directory
# (e.g., /user/cloudera/airline/ & /user/cloudera/airline/data/)

hdfs.data.root = 'airline'
hdfs.data = file.path(hdfs.data.root, 'data')

# unless otherwise specified, directories on HDFS should be relative to
user's home
hdfs.out.root = hdfs.data.root
hdfs.out = file.path(hdfs.out.root, 'out')


# the mapper gets a key and a value vector generated by the formatter
# in our case, the key is NULL and all the field values come in as a
vector
#
mapper.year.market.enroute_**time = function(k, fields) {
# if (as.character(fields$Origin) < as.character(fields$Dest))
# market = paste(as.character(fields$**Origin),
as.character(fields$Dest), sep='-')
#else
# market = paste(as.character(fields$**Dest),
as.character(fields$Origin), sep='-')

# key consists of year, market
# output.key = paste(market, as.character(fields$**UniqueCarrier),
sep='-')
# output.key = paste(as.character(fields$**Origin),
as.character(fields$**UniqueCarrier), sep='-')


# keyval(output.key, fields$DepDelay)
keyval(as.character(fields$**UniqueCarrier),
paste(fields$ArrDelay, fields$DepDelay, sep=","))
# list(c(fields$ArrDelay,fields$**DepDelay)))
# }
}


#
# the reducer gets all the values for a given key
# the values (which may be mult-valued as here) come in the form of a
list()
#
reducer.year.market.enroute_**time = function(key, val.list) {
delays.list = strsplit(unlist(val.list),**split=",")
val.df = ldply(delays.list,as.numeric)
# val.df = ldply(val.list,as.numeric)
colnames(val.df) = c('arrDelay','depDelay')
output.val = paste(nrow(val.df), mean(val.df$arrDelay,na.rm=**TRUE),
mean(val.df$depDelay, na.rm=TRUE),sep=",")
keyval(key, output.val)
}

mr.year.market.enroute_time = function (input, output) {
mapreduce(input = input,
output = output,
input.format = make.input.format("csv", sep = ",",

col.names=c('Year','Month','**DayofMonth','DayOfWeek','**
DepTime','CRSDepTime',

'ArrTime','CRSArrTime','**UniqueCarrier','FlightNum','**TailNum',

'ActualElapsedTime','**CRSElapsedTime','AirTime','**ArrDelay',

'DepDelay','Origin','Dest','**Distance','TaxiIn','TaxiOut',
'Cancelled','*
*CancellationCode','Diverted','**CarrierDelay',

'WeatherDelay','NASDelay','**SecurityDelay','**LateAircraftDelay')),
output.format = make.output.format("csv",**
quote=FALSE,sep=",",mode="**text"),
map = mapper.year.market.enroute_**time,
reduce = reducer.year.market.enroute_**time)
}

csvFormat = make.input.format("csv", sep = ",",mode="text")
out = from.dfs(mr.year.market.**enroute_time(hdfs.data,
hdfs.out),format=csvFormat)
results.df = as.data.frame(out$val,**stringsAsFactors=F )
colnames(results.df) = c('carrier', 'num flights', 'mean arr delay',
'mean dep delay')
print(results.df)

On Sunday, February 3, 2013 11:58:36 AM UTC-5, Piers Harding wrote:

Hi -

I've been just working through a problem (most likely my
understanding) of the same code. The attached is a working example
upgraded to rmr2, but where it is less than satisfactory is the solution
that I've had to come up with for the output.key of the map, and the final
reformatting of the data out from.dfs().

Would really appreciate it if you could point out where i've gone
wrong.

Thanks,
Piers Harding.



# adapted from Jeffrey Breens - http://jeffreybreen.wordpress.**
com/2012/03/10/big-data-step-**by-step-slides/<http://jeffreybreen.wordpress.com/2012/03/10/big-data-step-by-step-slides/>
# sample data taken from http://stat-computing.org/**
dataexpo/2009/the-data.html<http://stat-computing.org/dataexpo/2009/the-data.html>


# sort out loss of env with RStudio
Sys.setenv(HADOOP_CMD="/usr/**bin/hadoop")
Sys.setenv(HADOOP_HOME="/usr/**share/hadoop")


# switch on to run local - no Hadoop
DEBUG=FALSE


library(rmr2)
library(rhdfs)
library(reshape2)


sample_data = '/home/piers/Downloads/**dataexpo/tmp/small.csv'




# DEBUG = run locally - ie. you don't need Hadoop, which is pretty cool
if (DEBUG) {
rmr.options(backend = "local")
hdfs.input.path = sample_data
hdfs.output.root = '/tmp/enroute-time'
if (file.exists(hdfs.output.root)**) {
unlink(hdfs.output.root, recursive = TRUE)
}
dir.create(hdfs.output.root)
} else {
# setup input and output directories and files under HDFS and PUT
up the data
rmr.options(backend = "hadoop")
hdfs.input.dir = 'asa-airline'
hdfs.input.path = file.path(hdfs.input.dir, 'data')
hdfs.output.root = 'asa-airline/out'
hdfs.init()
if (hdfs.exists(hdfs.input.dir)) {
hdfs.delete(hdfs.input.dir)
}
hdfs.mkdir(hdfs.input.dir)
hdfs.put(sample_data, hdfs.input.path)
if (hdfs.exists(hdfs.output.root)**) {
hdfs.delete(hdfs.output.root)
}
hdfs.mkdir(hdfs.output.root)
}


# file input formater
asa.csv.text.input.format = make.input.format(mode = 'text', format =
function(con, klen) {
# read a line and parse it into fields
if (length(line <- readLines(con, n = 1, warn = FALSE)) > 0) {
values = unlist( strsplit(line, "\\,") )
names(values) = c('Year','Month','DayofMonth',**'DayOfWeek',
'DepTime','**CRSDepTime',
'ArrTime','CRSArrTime','**UniqueCarrier',
'FlightNum','**TailNum',
'ActualElapsedTime','**CRSElapsedTime',
'AirTime','**ArrDelay',
'DepDelay','Origin','Dest','**Distance',
'TaxiIn','TaxiOut',
'Cancelled','CancellationCode'**,'Diverted',
'CarrierDelay',
'WeatherDelay','NASDelay','**SecurityDelay','*
*LateAircraftDelay')
# stop those NAs propagating
values[values == 'NA'] <- 0
return( keyval(NULL, values) )
}
else {
return(NULL)
}
} )




# mapper - grab the time fields I want, and build a key of Year +
market
mapper.year.market.enroute_**time = function(key, val) {
# Skip header lines, cancellations, and diversions:
if ( !identical(as.character(val['**Year']), 'Year')) {
if (identical(as.numeric(val['**Cancelled']), 0)
& identical(as.numeric(val['**Diverted']), 0) ) {

# We don't care about direction of travel, so construct 'market'
# with airports ordered alphabetically
# (e.g, LAX to JFK becomes 'JFK-LAX'
if (val['Origin'] < val['Dest'])
market = paste(val['Origin'], val['Dest'], sep='-')
else
market = paste(val['Dest'], val['Origin'], sep='-')

# key consists of year, market - I would love to know a better
way of making compound keys - XXX
output.key = paste(val['Year'], market, sep=':')

# output gate-to-gate elapsed times (CRS and actual) + time in
air
output.val = list(c(val['CRSElapsedTime'], val[
'ActualElapsedTime'], val['AirTime']))

return( keyval(output.key, output.val) )
}
}
return(NULL)
}


# reducer - fix numeric types and calculate the mean times
reducer.year.market.enroute_**time = function(key, val.list) {
# val.list is a list of row vectors
# a data.frame is a list of column vectors
require(plyr)
val.df = ldply(val.list, as.numeric)
colnames(val.df) = c('actual','crs','air')

output.key = key
output.val = c( nrow(val.df), mean(val.df$actual, na.rm=T),
mean(val.df$crs, na.rm=T),
mean(val.df$air, na.rm=T) )

return( keyval(output.key, output.val) )
}




# mapreduce job
mr.year.market.enroute_time = function (input, output) {
mapreduce(input = input,
output = output,
input.format = asa.csv.text.input.format,
map = mapper.year.market.enroute_**time,
reduce = reducer.year.market.enroute_**time,
verbose=T)
}


hdfs.output.path = file.path(hdfs.output.root, 'enroute-time')
results.df = from.dfs(mr.year.market.enrout**e_time(hdfs.input.path,hdfs
.output.path))


# now - all of this fixing up is because of the key problem and loss
of structure
# in the data (ie: I want a data.frame back with vectors for keys, and
output values) - what do I do ?
results.df <- data.frame(colsplit(unique(res**ults.df$key), ":", list(
'year', 'market')),
as.data.frame(matrix(results.**df$val, nrow=
length(results.df$val)/4, ncol=4, byrow=TRUE)))
colnames(results.df) = c('year', 'market', 'flights', 'scheduled',
'actual', 'in.air')
str(results.df)


On Friday, 23 November 2012 06:13:20 UTC+13, Antonio Piccolboni wrote:

It looks to me you made one crucial change in the first line, so now
you have to upgrade the code as well. rmr2 is not backward compatible, I
think that fact has not been a secret. We even changed the package name to
make it impossible to run old programs after an upgrade without thinking
about it.


Antonio

On Thu, Nov 22, 2012 at 2:44 PM, Robert Ness wrote:

I am trying to implement the example code for the Airline dataset
given in Jeffrey Breen's tutorial found here<http://www.slideshare.net/jeffreybreen/big-data-stepbystep-using-r-hadoop-with-rhadoops-rmr-package#btnNext>.
I haven't been successful.

In my input directory I have a csv file with lines that look like
this
*2004,3,25,4,1445,1437,1820,1812,AA,399,N275AA,215,215,
197,8,8,BOS,MIA,1258,6,12,0,,0,0,0,0,0,0*
I am using only one input file, it is a csv containing only a subset
of the true dataset -- only about 5,000 lines.

I went though the code within the formatting, map, and reduce
functions with example inputs and it all works fine.

*When I run the code I get the error;
Error in mr(map = map, reduce = reduce, combine = combine, in.folder
= if (is.list(input)) { :
hadoop streaming failed with error code 5
In addition: Warning message:
In mapreduce(input = input, output = output, input.format =
asa.csvtextinputformat, :
backend.parameters is deprecated.
*
Can someone tell me what I am missing? Thanks. Code below.

library(rmr2)
hdfs.input.path <- "/usr/cloudera/airline/data/**in"

asa.csvtextinputformat <- make.input.format(format = function(line){
values <- unlist(strsplit(line, "\\,"))
names(values) <- c("Year", "Month", "DayofMonth", "DayofWeek",
"DepTime",
"CRSDepTime", "ArrTime", "CRSArrTime", "UniqueCarrier",
"FlightNum", "TailNum", "ActualElapsedTime", "CRSElapsedTime",
"AirTime", "ArrDelay", "DepDelay", "Origin", "Dest", "Distance",
"TaxiIn", "TaxiOut", "Cancelled", "CancellationCode", "Diverted"
,
"CarrierDelay", "WeatherDelay", "NASDelay",
"SecurityDelay", "LateAircraftDelay")
return(keyval(NULL, values))
})

mapper.year.market.enroute_**time <- function(key, val){
#Skip header lines, cancellations, and diversions:
if(!identical(as.character(val**['Year']),'Year')
&identical(as.numeric(val['**Cancelled']),0)
&identical(as.numeric(val['**Diverted']),0)){

#We don't care about direction of travel, so construct 'market'
#with airports ordered alphabetically (e.g, LAX to JFK becomes
'JFK-LAX'
if(val['Origin'] < val['Dest']){
market <- paste(val['Origin'], val['Dest'], sep='-')
} else market <- paste(val['Dest'], val['Origin'], sep='-')
# key consists of year,market
output.key=c(val['Year'],marke**t)

#output gate-to-gate elapsed times(CRSandactual) + time in
airoutput.
output.val=c(val['**CRSElapsedTime'],val['**ActualElapsedTime'],
val['**AirTime'])

return(keyval(output.key,outpu**t.val))
}
}

reducer.year.market.enroute_**time = function(key, val.list){
if(require(plyr)) val.df = ldply(val.list, as.numeric) else{
#this is as close as my deficient * apply skills can come w/o
plyrval.list = lapply(val.list, as.numeric)
val.df = data.frame(do.call(rbind, val.list))
}
colnames(val.df) = c('actual','crs','air')
output.key = key
output.val = c(nrow(val.df), mean(val.df$actual,na.rm=T), mean(val
.df$crs, na.rm = T), mean(val.df$air,na.rm = T))
return(keyval(output.key, output.val))
}

mr.year.market.enroute_time = function(input,output){
mapreduce(input = input,
output = output,
input.format = asa.csvtextinputformat,
map = mapper.year.market.enroute_**time,
reduce = reducer.year.market.enroute_**time,
backend.parameters = list(hadoop= list(D=
"mapred.reduce.tasks=**10")),
verbose=T)
}
hdfs.output.root <- "/usr/cloudera/airline/data/**out"
hdfs.output.path = file.path(hdfs.output.root,'**enroute-time')
results = mr.year.market.enroute_time(hd**fs.input.path, hdfs.output
.path)


--
post: rha...@googlegroups.com ||
unsubscribe: rhadoop+u...@googlegroups.com ||
web: https://groups.google.com/d/**forum/rhadoop?hl=en-US<https://groups.google.com/d/forum/rhadoop?hl=en-US>
--
post: rha...@googlegroups.com <javascript:> ||
unsubscribe: rhadoop+u...@googlegroups.com <javascript:> ||
web: https://groups.google.com/d/forum/rhadoop?hl=en-US
---
You received this message because you are subscribed to the Google Groups
"RHadoop" group.
To unsubscribe from this group and stop receiving emails from it, send an
email to rhadoop+u...@googlegroups.com <javascript:>.
For more options, visit https://groups.google.com/groups/opt_out.
--
post: rhadoop@googlegroups.com ||
unsubscribe: rhadoop+unsubscribe@googlegroups.com ||
web: https://groups.google.com/d/forum/rhadoop?hl=en-US
---
You received this message because you are subscribed to the Google Groups "RHadoop" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rhadoop+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Search Discussions

Discussion Posts

Previous
Follow ups

Related Discussions

Discussion Navigation
viewthread | post
posts ‹ prev | 11 of 34 | next ›
Discussion Overview
grouprhadoop
postedNov 22, '12 at 7:29p
activeFeb 27, '13 at 7:47a
posts34
users8
Preferences
font variable
user style avatars

8 users in discussion

Jim Solderitsch: 14 posts Antonio Piccolboni: 11 posts Antonio Piccolboni: 4 posts Piers Harding: 1 post Roman: 1 post Alton Ing: 1 post JeffreyBreen: 1 post Robert Ness: 1 post