I work for @serverdensity; curate @phpweekly; curate @tipsforgit; release open source goodness at http://t.co/u0glbpZMun support me at https://t.co/rqrQ1aFp
26 stories
·
11 followers

Subtle differences – tracking down CPU version performance

1 Share

When unusual activity occurs in a cluster of servers, the first job is to eliminate any variables so you can rule out small configuration differences. Often this will be small software version differences but with the use of modern config management tools like Puppet or Chef, this is becoming less and less likely.

So when that doesn’t reveal anything unusual, the next step is to look at the hardware. Here lies a classic example of taking care of spotting the details, and understanding what is out of the ordinary when it comes to your infrastructure.

The process

We have weekly reviews of several performance indicators across our infrastructure. This doesn’t replace automated monitoring and alerting on those indicators, however it allows us to spot small performance decreases over time so that we can investigate issues within the infrastructure, schedule in time for performance improvements to our codebase and plan for upgrades.

Since we use Server Density to monitor Server Density that becomes very easy – it usually takes only a couple of minutes by glancing at some preset time interval on our performance dashboards.

This is the last 3 months of data on one of those dashboards:

3 months performance data

The odd performance values

Some time ago, soon after an upgrade on one of our clusters, it started to show this load profile:

Initial cluster load

This is a 4 server queue processing cluster run on Softlayer with dedicated hardware (SuperMicro, Xeon 1270 Quadcores, 8GB RAM). All the software stack is built from the same source using Puppet and our deploy process ensures all of the cluster nodes run exactly the same versions.

Why was one of the servers showing lower load for the exact same work? We couldn’t justify any difference so we went and asked Softlayer support:

There are no discernible differences between the servers

was the first answer we got.

The plot thickens

Not being happy with having servers that should behave the same and not doing so, we looked further into the matter and found yet another, this time more worrying, issue – packet loss on the 3 servers that showed the higher load:

Initial cluster packet loss

So we went back to Softlayer support. They were quite diligent and “looked at the switch/s for these servers, network speed, connections Established & Waiting, apache/python/tornado process etc…” but in the end came back empty except for a subtle difference on the cluster hardware: “all of the processors are Xeon 1270 Quadcores, -web4 is running V3 and is the newest; -web2 and -web3 is running V2; -web1 is running V1“.

When we order new servers, we pick the CPU type but it doesn’t offer the granularity of the CPU versions. The data center team deliver what they have ready.

The fix

After some research, we discovered that there were some potentially interesting differences between the CPU versions and so we decided to eliminate the hardware difference and see what would happen.

Softlayer usually accommodates special requests and we had no difficulty in getting this through.

The next graph show the replacement of -web1 and then -web2 and -web3. Can you see when it was done?

cluster load

Then a similar plot for the cluster packet loss:

cluster packet loss

Switching all the servers to a consistent CPU and CPU version solved the problem. The packet loss disappeared and the performance equalised. This is a great example of a very subtle difference having some measurable impact on the operation of the server. Using config management allowed us to quickly eliminate a software cause, at least one that we could control. It’s possible that the CPU version had some issue with the hardware drivers, but it illustrates how consistency within a cluster is important.

The post Subtle differences – tracking down CPU version performance appeared first on Server Density Blog.

Read the whole story
1stvamp
3603 days ago
reply
Selby, UK
Share this story
Delete

A walk-through charming an existing wsgi application

1 Share

After recently writing the re-usable wsgi-app role for juju charms, I wanted to see how easy it would be to apply this to an existing service. I chose the existing ubuntu-reviews service because it’s open-source and something that we’ll probably need to deploy with juju soon (albeit only the functionality for reviewing newer click applications).

I’ve tried to include the workflow below that I used to create the charm including the detailed steps and some mistakes, in case it’s useful for others. If you’re after more of an overview about using reusable ansible roles in your juju charms, checkout these slides.

1. Create the new charm from the bootstrap-wsgi charm

First grab the charm-bootstrap-wsgi code and rename it to ubuntu-reviews:

$ mkdir -p ~/charms/rnr/precise && cd ~/charms/rnr/precise
$ git clone https://github.com/absoludity/charm-bootstrap-wsgi.git
$ mv charm-bootstrap-wsgi ubuntu-reviews && cd ubuntu-reviews/

Then update the charm metadata to reflect the ubuntu-reviews service:

--- a/metadata.yaml
+++ b/metadata.yaml
@@ -1,6 +1,6 @@
-name: charm-bootstrap-wsgi
-summary: Bootstrap your wsgi service charm.
-maintainer: Developer Account <Developer.Account@localhost>
+name: ubuntu-reviews
+summary: A review API service for Ubuntu click packages.
+maintainer: Michael Nelson <michael.nelson@canonical.com>
 description: |
   <Multi-line description here>
 categories:

I then updated the playbook to expect a tarball named rnr-server.tgz, and gave it a more relevant app_label (which controls the directories under which the service is setup):

--- a/playbook.yml
+++ b/playbook.yml
@@ -2,13 +2,13 @@
 - hosts: localhost
 
   vars:
-    - app_label: wsgi-app.example.com
+    - app_label: click-reviews.ubuntu.com
 
   roles:
     - role: wsgi-app
       listen_port: 8080
-      wsgi_application: example_wsgi:application
-      code_archive: "{{ build_label }}/example-wsgi-app.tar.bzip2"
+      wsgi_application: wsgi:app
+      code_archive: "{{ build_label }}/rnr-server.tgz"
       when: build_label != ''

Although when deploying this service we’d be deploying a built asset published somewhere else, for development it’s easier to work with a local file in the charm – and the reusable wsgi-app role allows you to switch between those two options. So the last step here is to add some Makefile targets to enable pulling in the application code and creating the tarball (you can see the details on the git commit for this step).

With that done, I can deploy the charm with `make deploy` – expecting it to fail because the wsgi object doesn’t yet exist.

Aside: if you’re deploying locally, it’s sometimes useful to watch the deployment progress with:

$ tail -F ~/.juju/local/log/unit-ubuntu-reviews-0.log

At this point, juju status shows no issues, but curling the service does (as expected):

$ juju ssh ubuntu-reviews/0 "curl http://localhost:8080"
curl: (56) Recv failure: Connection reset by peer

Checking the logs (which, oh joy, are already setup and configured with log rotation etc. by the wsgi-app role) shows the expected error:

$ juju ssh ubuntu-reviews/0 "tail -F /srv/reviews.click.ubuntu.com/logs/reviews.click.ubuntu.com-error.log"
...
ImportError: No module named wsgi

2. Adding the wsgi, settings and urls

The current rnr-server code does have a django project setup, but it’s specific to the current setup (requiring a 3rd party library for settings, and serves the non-click reviews service too). I’d like to simplify that here, so I’m bundling a project configuration in the charm. First the standard (django-generated) manage.py, wsgi.py and near-default settings.py (which you can see in the actual commit).

An extra task is added which copies the project configuration into place during install/upgrade, and both the wsgi application location and the required PYTHONPATH are specified:

--- a/playbook.yml
+++ b/playbook.yml
@@ -7,7 +7,8 @@
   roles:
     - role: wsgi-app
       listen_port: 8080
-      wsgi_application: wsgi:app
+      python_path: "{{ application_dir }}/django-project:{{ current_code_dir }}/src"
+      wsgi_application: clickreviewsproject.wsgi:application
       code_archive: "{{ build_label }}/rnr-server.tgz"
       when: build_label != ''
 
@@ -18,20 +19,18 @@
 
   tasks:
 
-    # Pretend there are some package dependencies for the example wsgi app.
     - name: Install any required packages for your app.
-      apt: pkg={{ item }} state=latest update_cache=yes
+      apt: pkg={{ item }}
       with_items:
-        - python-django
-        - python-django-celery
+        - python-django=1.5.4-1ubuntu1~ctools0
       tags:
         - install
         - upgrade-charm
 
     - name: Write any custom configuration files
-      debug: msg="You'd write any custom config files here, then notify the 'Restart wsgi' handler."
+      copy: src=django-project dest={{ application_dir }} owner={{ wsgi_user }} group={{ wsgi_group }}
       tags:
-        - config-changed
-        # Also any backend relation-changed events, such as databases.
+        - install
+        - upgrade-charm
       notify:
         - Restart wsgi

I can now run juju upgrade-charm and see that the application now responds, but with a 500 error highlighting the first (of a few) missing dependencies…

3. Adding missing dependencies

At this point, it’s easiest in my opinion to run debug-hooks:

$ juju debug-hooks ubuntu-reviews/0

and directly test and install missing dependencies, adding them to the playbook as you go:

ubuntu@michael-local-machine-1:~$ curl http://localhost:8080 | less
ubuntu@michael-local-machine-1:~$ sudo apt-get install python-missing-library -y (and add it to the playbook in your charm editor).
ubuntu@michael-local-machine-1:~$ sudo service gunicorn restart

Rinse and repeat. You may want to occasionally run upgrade-charm in a separate terminal:

$ juju upgrade-charm --repository=../.. ubuntu-reviews

to verify that you’ve not broken things, running the hooks as they are triggered in your debug-hooks window.

Other times you might want to destroy the environment to redeploy from scratch.

Sometimes you’ll have dependencies which are not available in the distro. For our deployments,
we always ensure we have these included in our tarball (in some form). In the case of the reviews server, there was an existing script which pulls in a bunch of extra branches, so I’ve updated to use that in the Makefile target that builds the tarball. But there was another dependency, south, which wasn’t included by that task, so for simplicity here, I’m going to install that via pip (which you don’t want to do in reality – you’d update your build process instead).

You can see the extra dependencies in the commit.

4. Customise settings

At this point, the service deploys but errors due to a missing setting, which makes complete sense as I’ve not added any app-specific settings yet.

So, remove the vanilla settings.py and add a custom settings.py.j2 template, and add an extra django_secret_key option to the charm config.yaml:

--- a/config.yaml
+++ b/config.yaml
@@ -10,8 +10,13 @@ options:
     current_symlink:
         default: "latest"
         type: string
-        description: |
+        description: >
             The symlink of the code to run. The default of 'latest' will use
             the most recently added build on the instance.  Specifying a
             differnt label (eg. "r235") will symlink to that directory assuming
             it has been previously added to the instance.
+    django_secret_key:
+        default: "you-really-should-set-this"
+        type: string
+        description: >
+            The secret key that django should use for each instance.

--- /dev/null
+++ b/templates/settings.py.j2
@@ -0,0 +1,41 @@
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+SECRET_KEY = '{{ django_secret_key }}'
+TIME_ZONE = 'UTC'
+ROOT_URLCONF = 'clickreviewsproject.urls'
+WSGI_APPLICATION = 'clickreviewsproject.wsgi.application'
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django_openid_auth',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'clickreviews',
+    'south',
+)
...

Again, full diff in the commit.

With the extra settings now defined and one more dependencie added, I’m now able to ping the service successfully:

$ juju ssh ubuntu-reviews/0 "curl http://localhost:8080/api/1.0/reviews/"
{"errors": {"package_name": ["This field is required."]}}

But that only works because the input validation is fired before anything tries to use the (non-existent) database…

5. Adding the database relation

I had expected this step to be simple – add the database relation, call syncdb/migrate, but there were three factors conspiring to complicate things:

  1. The reviews application uses a custom postgres “debversion” column type
  2. A migration for the older non-click reviewsapp service requires postgres superuser creds (specifically, reviews/0013_add_debversion_field.py
  3. The clickreviews app, which I wanted to deploy on its own, is currently dependent on the older non-click reviews app, so it’s necessary to have both enabled and therefore the tables from both sync’d

So to work around the first point, I split the ‘deploy’ task into two tasks so I can install and setup the custom debversion field on the postgres units, before running syncdb:

--- a/Makefile
+++ b/Makefile
@@ -37,8 +37,17 @@ deploy: create-tarball
        @juju set ubuntu-reviews build_label=r$(REVIEWS_REVNO)
        @juju deploy gunicorn
        @juju deploy nrpe-external-master
+       @juju deploy postgresql
        @juju add-relation ubuntu-reviews gunicorn
        @juju add-relation ubuntu-reviews nrpe-external-master
+       @juju add-relation ubuntu-reviews postgresql:db
+       @juju set postgresql extra-packages=postgresql-9.1-debversion
+       @echo "Once the services are related, run 'make setup-db'"
+
+setup-db:
+       @echo "Creating custom debversion postgres type and syncing the ubuntu-reviews database."
+       @juju run --service postgresql 'psql -U postgres ubuntu-reviews -c "CREATE EXTENSION debversion;"'
+       @juju run --unit=ubuntu-reviews/0 "hooks/syncdb"
        @echo See the README for explorations after deploying.

I’ve separated the syncdb out to a manual step (rather than running syncdb on a hook like config-changed) so that I can run it after the postgresql service had been updated with the custom field, and also because juju doesn’t currently have a concept of a leader (yes, syncdb could be run on every unit, and might be safe, but I don’t like it conceptually).

--- a/playbook.yml
+++ b/playbook.yml
@@ -3,13 +3,13 @@
 
   vars:
     - app_label: click-reviews.ubuntu.com
+    - code_archive: "{{ build_label }}/rnr-server.tgz"
 
   roles:
     - role: wsgi-app
       listen_port: 8080
       python_path: "{{ application_dir }}/django-project:{{ current_code_dir }}/src"
       wsgi_application: clickreviewsproject.wsgi:application
-      code_archive: "{{ build_label }}/rnr-server.tgz"
       when: build_label != ''
 
     - role: nrpe-external-master
@@ -25,6 +25,7 @@
         - python-django=1.5.4-1ubuntu1~ctools0
         - python-tz
         - python-pip
+        - python-psycopg2
       tags:
         - install
         - upgrade-charm
@@ -46,13 +47,24 @@
       tags:
         - install
         - upgrade-charm
+        - db-relation-changed
       notify:
         - Restart wsgi
+
     # XXX Normally our deployment build would ensure these are all available in the tarball.
     - name: Install any non-distro dependencies (Don't depend on pip for a real deployment!)
       pip: name={{ item.key }} version={{ item.value }}
       with_dict:
         south: 0.7.6
+        django-openid-auth: 0.2
       tags:
         - install
         - upgrade-charm
+
+    - name: sync the database
+      django_manage: >
+        command="syncdb --noinput"
+        app_path="{{ application_dir }}/django-project"
+        pythonpath="{{ code_dir }}/current/src"
+      tags:
+        - syncdb

To work around the second and third points above, I updated to remove the ‘south’ django application so that syncdb will setup the correct current tables without using migrations (while leaving it on the pythonpath as some code imports it currently), and added the old non-click reviews app to satisfy some of the imported dependencies.

By far the most complicated part of this change though, was the database settings:

--- a/templates/settings.py.j2
+++ b/templates/settings.py.j2
@@ -1,6 +1,31 @@
 DEBUG = True
 TEMPLATE_DEBUG = DEBUG
 SECRET_KEY = '{{ django_secret_key }}'
+
+{% if 'db' in relations %}
+DATABASES = {
+  {% for dbrelation in relations['db'] %}
+  {% if loop.first %}
+    {% set services=relations['db'][dbrelation] %}
+    {% for service in services %}
+      {% if service.startswith('postgres') %}
+      {% set dbdetails = services[service] %}
+        {% if 'database' in dbdetails %}
+    'default': {
+        'ENGINE': 'django.db.backends.postgresql_psycopg2',
+        'NAME': '{{ dbdetails["database"] }}',
+        'HOST': '{{ dbdetails["host"] }}',
+        'USER': '{{ dbdetails["user"] }}',
+        'PASSWORD': '{{ dbdetails["password"] }}',
+    }
+        {% endif %}
+      {% endif %}
+    {% endfor %}
+  {% endif %}
+  {% endfor %}
+}
+{% endif %}
+
 TIME_ZONE = 'UTC'
 ROOT_URLCONF = 'clickreviewsproject.urls'
 WSGI_APPLICATION = 'clickreviewsproject.wsgi.application'
@@ -12,8 +37,8 @@ INSTALLED_APPS = (
     'django_openid_auth',
     'django.contrib.messages',
     'django.contrib.staticfiles',
+    'reviewsapp',
     'clickreviews',
-    'south',
 )
 AUTHENTICATION_BACKENDS = (
     'django_openid_auth.auth.OpenIDBackend',

Why are the database settings so complicated? I’m currently rendering all the settings on any config change as well as database relation change which means I need to use the global juju state for the relation data, and as far as juju knows, there could be multiple database relations for this stack. The current charm helpers gives you this as a dict (above it’s ‘relations’). The ‘db’ key of that dict contains all the database relations, so I’m grabbing the first database relation and taking the details from the postgres side of that relation (there are two keys for the relation, one for postgres, the other for the connecting service). Yuk (see below for a better solution).

Full details are in the commit. With those changes I can now use the service:

$ juju ssh ubuntu-reviews/0 "curl http://localhost:8080/api/1.0/reviews/"
{"errors": {"package_name": ["This field is required."]}}

$ juju ssh ubuntu-reviews/0 "curl http://localhost:8080/api/1.0/reviews/?package_name=foo"
[]

A. Cleanup: Simplify database settings

In retrospect, the complicated database settings are only needed because the settings are rendered on any config change, not just when the database relation changes, so I’ll clean this up in the next step by moving the database settings to a separate file which is only written on the db-relation-changed hook where we can use the more convenient current_relation dict (given that I know this service only has one database relationship).

This simplifies things quite a bit:

--- a/playbook.yml
+++ b/playbook.yml
@@ -45,11 +45,21 @@
         owner: "{{ wsgi_user }}"
         group: "{{ wsgi_group }}"
       tags:
-        - install
-        - upgrade-charm
+        - config-changed
+      notify:
+        - Restart wsgi
+
+    - name: Write the database settings
+      template:
+        src: "templates/database_settings.py.j2"
+        dest: "{{ application_dir }}/django-project/clickreviewsproject/database_settings.py"
+        owner: "{{ wsgi_user }}"
+        group: "{{ wsgi_group }}"
+      tags:
         - db-relation-changed
       notify:
         - Restart wsgi
+      when: "'database' in current_relation"
 
--- /dev/null
+++ b/templates/database_settings.py.j2
@@ -0,0 +1,9 @@
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.postgresql_psycopg2',
+        'NAME': '{{ current_relation["database"] }}',
+        'HOST': '{{ current_relation["host"] }}',
+        'USER': '{{ current_relation["user"] }}',
+        'PASSWORD': '{{ current_relation["password"] }}',
+    }
+}

--- a/templates/settings.py.j2
+++ b/templates/settings.py.j2
@@ -2,29 +2,7 @@ DEBUG = True
 TEMPLATE_DEBUG = DEBUG
 SECRET_KEY = '{{ django_secret_key }}'
 
-{% if 'db' in relations %}
-DATABASES = {
-  {% for dbrelation in relations['db'] %}
-  {% if loop.first %}
-    {% set services=relations['db'][dbrelation] %}
-    {% for service in services %}
-      {% if service.startswith('postgres') %}
-      {% set dbdetails = services[service] %}
-        {% if 'database' in dbdetails %}
-    'default': {
-        'ENGINE': 'django.db.backends.postgresql_psycopg2',
-        'NAME': '{{ dbdetails["database"] }}',
-        'HOST': '{{ dbdetails["host"] }}',
-        'USER': '{{ dbdetails["user"] }}',
-        'PASSWORD': '{{ dbdetails["password"] }}',
-    }
-        {% endif %}
-      {% endif %}
-    {% endfor %}
-  {% endif %}
-  {% endfor %}
-}
-{% endif %}
+from database_settings import DATABASES
 
 TIME_ZONE = 'UTC'

With those changes, the deploy still works as expected:
$ make deploy
$ make setup-db
Creating custom debversion postgres type and syncing the ubuntu-reviews database.
...
$ juju ssh ubuntu-reviews/0 "curl http://localhost:8080/api/1.0/reviews/?package_name=foo"
[]

Summary

Reusing the wsgi-app ansible role allowed me to focus just on the things that make this service different from other wsgi-app services:

  1. Creating the tarball for deployment (not normally part of the charm, but useful for developing the charm)
  2. Handling the application settings
  3. Handling the database relation and custom setup

The wsgi-app role already provides the functionality to deploy and upgrade code tarballs via a config set (ie. `juju set ubuntu-reviews build_label=r156`) from a configured url. It provides the functionality for the complete directory layout, user and group setup, log rotation etc. All of that comes for free.

Things I’d do if I was doing this charm for a real deployment now:

  1. Use the postgres db-admin relation for syncdb/migrations (thanks Simon) and ensure that the normal app uses the non-admin settings (and can’t change the schema etc.) This functionality could be factored out into another reusable role.
  2. Remove the dependency on the older non-click reviews app so that the clickreviews service can be deployed in isolation.
  3. Ensure all dependencies are either available in the distro, or included in the tarball

Filed under: cloud automation, juju
Read the whole story
1stvamp
3783 days ago
reply
Selby, UK
Share this story
Delete

Dave Behnke: Building Python on Ubuntu with pyenv

1 Share

Summary

Have you ever wanted a newer version of Python on your Ubuntu install?

For example, Ubuntu 12.04.4 provides Python 2.7.3 and Python 3.2 versions, but you'd like to use a feature only available in 3.3+. Or perhaps, you need to test something on 2.6.6 to match what is avilable on Centos 6.

In this article, I'm going to show you how to install a newer version of Python that installs in your home directory without breaking your system's default installed version of Python.

You will also be able to switch between the system and your local versions, and still use tools such as pip and virtualenv.

I'm using Ubuntu 12.04.4 LTS, but you should be able to use this information in later versions of Ubuntu and it's various respins and derivatives. These instructions may also work on Debian as well.

Introducing Pyenv

pyenv lets you easily switch between multiple versions of Python. It's simple, unobtrusive, and follows the UNIX tradition of single-purpose tools that do one thing well.

pyenv follows a family of home directory installing options for Python. In the past I've used pythonbrew and it's fork pythonz. I discovered pyenv about 6 months ago, and use it now for all my Python development.

I encourage visiting https://github.com/yyuu/pyenv for complete documentation on pyenv before continuing.

Getting Started

Whenver I install pyenv on a new system, I use the automatic installer. This automates the process of cloning the repository from github and such. Before you continue, you should make sure you have git and curl installed.

dbehnke@precise:~$ sudo apt-get install curl git-core 

Running the Automatic Installer

dbehnke@precise:~$ curl https://raw.github.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash 

Open up .bashrc located in your home directory with a text editor and add the following.

export PYENV_ROOT="${HOME}/.pyenv" if [ -d "${PYENV_ROOT}" ]; then export PATH="${PYENV_ROOT}/bin:${PATH}" eval "$(pyenv init -)" fi 

Before changes will take place, you will need to exit your terminal and restart or type the following command

dbehnke@precise:~$ source ~/.bashrc 

Verifying pyenv is installed properly

dbehnke@precise:~$ pyenv --version pyenv 0.4.0-20140211-11-g5bbfcf7 

Listing Available Python Versions

This is an example of what was available on March 2, 2014.

As you can see, there are many versions to choose from!

dbehnke@precise:~$ pyenv install --list Available versions: 2.4 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.6.6 2.6.7 2.6.8 2.6.9 2.6-dev 2.7 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7-dev 3.0.1 3.1.3 3.1.4 3.1.5 3.1-dev 3.2 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2-dev 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5rc1 3.3-dev 3.4.0a2 3.4.0a3 3.4.0a4 3.4.0b1 3.4.0b2 3.4.0b3 3.4.0rc1 3.4.0rc2 3.4-dev anaconda-1.4.0 anaconda-1.5.0 anaconda-1.5.1 anaconda-1.6.0 anaconda-1.6.1 anaconda-1.7.0 anaconda-1.8.0 jython-2.5.0 jython-2.5.1 jython-2.5.2 jython-2.5.3 jython-2.5.4-rc1 jython-2.5-dev jython-2.7-beta1 jython-dev miniconda-2.2.2 miniconda-3.0.0 miniconda3-2.2.2 miniconda3-3.0.0 pypy-1.5 pypy-1.5-src pypy-1.6 pypy-1.7 pypy-1.7-dev pypy-1.8 pypy-1.8-dev pypy-1.9 pypy-1.9-dev pypy-2.0 pypy-2.0.1 pypy-2.0.1-src pypy-2.0.2 pypy-2.0.2-src pypy-2.0-dev pypy-2.0-src pypy-2.1 pypy-2.1-src pypy-2.2 pypy-2.2.1 pypy-2.2.1-src pypy-2.2-src pypy3-2.1-beta1 pypy3-2.1-beta1-src pypy3-dev pypy-dev stackless-2.7.2 stackless-2.7-dev stackless-3.2.2 stackless-3.2-dev stackless-3.3-dev stackless-dev 

Install Build Dependencies

First, since we will be compiling Python from source, your system needs to have the build dependencies. This is easy to do since 12.04.4 comes with Python 3.2 and the dependencies haven't changed much for 3.3 and later.

Here's how to install the build dependencies on an Ubuntu 12.04.4 system. We will pull in both 2.7 and 3.2 build dependencies.

dbehnke@precise:~$ sudo apt-get build-dep python2.7 python3.2 Reading package lists... Done Building dependency tree Reading state information... Done The following NEW packages will be installed: autoconf blt blt-dev diffstat help2man libbluetooth-dev libdb5.1-dev libexpat1-dev libffi-dev libfontconfig1-dev libfreetype6-dev libgdbm-dev libjs-sphinxdoc libjs-underscore libncursesw5-dev libpthread-stubs0 libpthread-stubs0-dev libx11-dev libxau-dev libxcb1-dev libxdmcp-dev libxext-dev libxft-dev libxrender-dev libxss-dev libxss1 m4 python-jinja2 python-pygments python-sphinx quilt sharutils sphinx-common tcl8.5 tcl8.5-dev tk8.5 tk8.5-dev x11proto-core-dev x11proto-input-dev x11proto-kb-dev x11proto-render-dev x11proto-scrnsaver-dev x11proto-xext-dev xorg-sgml-doctools xtrans-dev xvfb 0 upgraded, 46 newly installed, 0 to remove and 0 not upgraded. Need to get 15.9 MB of archives. After this operation, 54.7 MB of additional disk space will be used. Do you want to continue [Y/n]? Y 

Additional Dependencies

Most of this is covered in the previous step, however sometimes it doesn't catch everything. Make sure to run this as well.

dbehnke@precise:~$ sudo apt-get install build-essential wget \ libreadline-dev libncurses5-dev libssl1.0.0 tk8.5-dev \ zlib1g-dev liblzma-dev 

Installing Python

e.g. Python 3.3.4

dbehnke@precise:~$ pyenv install 3.3.4 -v > install-3.3.4.log Downloading Python-3.3.4.tgz... -> http://yyuu.github.io/pythons/9f7df0dde690132c63b1dd2b640ed3a6 Installing Python-3.3.4... Installed Python-3.3.4 to /home/dbehnke/.pyenv/versions/3.3.4 Downloading setuptools-2.2.tar.gz... -> https://pypi.python.org/packages/source/s/setuptools/setuptools-2.2.tar.gz Installing setuptools-2.2... Installed setuptools-2.2 to /home/dbehnke/.pyenv/versions/3.3.4 Downloading pip-1.5.4.tar.gz... -> https://pypi.python.org/packages/source/p/pip/pip-1.5.4.tar.gz Installing pip-1.5.4... Installed pip-1.5.4 to /home/dbehnke/.pyenv/versions/3.3.4 

If you want to see a verbose log of the install, open up install-3.3.4.log in a text editor.

Testing

$ which python 

It should return ~/.pyenv/shims/python

Listing Python Versions

dbehnke@precise:~$ pyenv versions * system (set by /home/dbehnke/.pyenv/version) 2.7.6 3.3.4 

notice system is default.. system = os default installed python

Switch to Python Version for a shell session

e.g. using python 3.3.4

dbehnke@precise:~$ export PYENV_VERSION=3.3.4 dbehnke@precise:~$ pyenv versions system 2.7.6 * 3.3.4 (set by PYENV_VERSION environment variable) dbehnke@precise:~$ python Python 3.3.4 (default, Mar 2 2014, 00:13:28) [GCC 4.6.3] on linux Type "help", "copyright", "credits" or "license" for more information. >>> 

Setting a Python Version for a Directory

e.g. for a test directory

dbehnke@precise:~$ mkdir test dbehnke@precise:~$ cd test dbehnke@precise:~/test$ pyenv local 3.3.4 dbehnke@precise:~/test$ pyenv versions system 2.7.6 * 3.3.4 (set by /home/dbehnke/test/.python-version) dbehnke@precise:~/test$ python Python 3.3.4 (default, Mar 2 2014, 00:13:28) [GCC 4.6.3] on linux Type "help", "copyright", "credits" or "license" for more information. >>> 

To show that we are still using the current version in the home directory.

test $ cd .. dbehnke@precise:~$ python Python 2.7.3 (default, Sep 26 2013, 20:03:06) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> 

Single use

You can also change versions for a single shell command.

dbehnke@precise:~$ PYENV_VERSION=3.4.0rc2 python Python 3.4.0rc2 (default, Mar 2 2014, 16:04:06) [GCC 4.6.3] on linux Type "help", "copyright", "credits" or "license" for more information. >>> 

Rehash New Binaries

Anytime you install a package using "pip install" that also installs a helper executable, it usually won't work until you have pyenv rescan all the executables.

To accomplish this, run the rehash command.

dbehnke@precise:~$ pyenv rehash 

Updating Pyenv Periodically

Since pyenv is installed by downloading from github, updating is very simple.

dbehnke@precise:~$ cd ~/.pyenv dbehnke@precise:~$ git pull dbehnke@precise:~/.pyenv$ git pull Already up-to-date. 

Advanced Use

Using pyenv to compile your a version of Python in your home directory may be enough. However, if you want to take it a step further and use virtualenv to sandbox your packages, I suggest using the pyenv-virtualenv plugin.

Virtualenv and Virtualenvwrapper will be covered in more depth in a future article.

However, if you are already familiar with virtualenv, please look at yyuu's pyenv-virtualenv plugin documentation at https://github.com/yyuu/pyenv-virtualenv

Here is a brief summary of how to use the pyenv-virtualenv plugin.

Installation

(This may already be installed if you followed the automated installer instructions for pyenv)

$ git clone git://github.com/yyuu/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv 

Creating a virtualenv is similar to installing a new version of Python.

e.g. Creating a virtualenv based on 3.3.4 called my334

dbehnke@precise:~$ pyenv virtualenv 3.3.4 my334 

List the versions and you will see my334 is in the list

dbehnke@precise:~$ pyenv versions * system (set by /home/dbehnke/.pyenv/version) 3.3.4 my334 

Changing to a virtualenv is also similar to how we change a python version.

dbehnke@precise:~$ export PYENV_VERSION=my334 dbehnke@precise:~$ pyenv versions system 3.3.4 * my334 (set by PYENV_VERSION environment variable) 

Conclusions

Python can be installed almost anywhere with pyenv provided you have the proper prerequites installed. You can have multiple versions installed for testing with older systems. Release canidates are also available! You can use tools such as virtualenv to extend the sandboxing.

Read the whole story
1stvamp
3914 days ago
reply
Selby, UK
Share this story
Delete

Armin Ronacher: Python on Wheels

1 Share

The python packaging infrastructure has long received criticism from both Python developers as well as system administrators. For a long time even the Python community in itself could not agree on what exactly the tools to use were. We had distutils, setuptools, distribute, distutils2 as basic distribution mechanisms and then virtualenv, buildout, easy_install and pip as high level tools to deal with this mess.

As distribution formats before setuptools we had source files and for Windows there were some binary distributions in form of MSIs. On Linux we had bdist_dumb which was historically broken and bdist_rpm which only worked on Red Hat based systems. But even bdist_rpm did not actually work good enough that people were actually using it.

A few years ago PJE stepped up and tried to fix the initial distribution problems by providing the mix of setuptools + pkg_resources to improve distutils and to provide metadata for Python packages. In addition to that he wrote the easy_install tool to install packages. In lack of a distribution format that supported the required metadata, the egg format was invented.

Python eggs are basically zip packages that include the python package plus the metadata that is required. Even though many people probably never built eggs intentionally, the egg metadata is still alive and kicking and everybody deploys things through setuptools now.

Now unfortunately a few years ago the community split in half and part of the community declared the death to binary distributions and eggs. When that happened the replacement for easy_install (pip) stopped accepting eggs altogether.

Fast forward a few years later. The removal of binary distributions has become noticed very painfully as people started more and more cloud deployment and having to recompile C libraries on every single machine is no fun. Because eggs at that point were poorly understood I assume, they were reimplemented on top of newer PEPs and called wheels.

As a general information before we dive in: I'm assuming that you are in all cases operating out of a virtualenv.

What are Wheels

So let's start simple. What exactly are wheels and what's the difference to eggs? Both eggs and wheels are basically just zip files. The main difference is that you could import eggs without having to unpack them. Wheels on the other hand are just distribution archives that you need to unpack upon installation. While there are technically no reasons for wheels not to be importable, that was never the plan to begin with and there is currently no support for importing wheels directly.

The other main difference is that eggs included compiled python bytecode whereas wheels do not. The biggest advantage of this is that you don't need to make wheels for different Python versions for as long as you don't ship binary modules that link against libpython. On newer Python 3 versions you can actually even safely link against libpython for as long as you only use the stable ABI.

There are a few problems with wheels however. One of the problems is that wheels inherit some of the problems that egg already had. For instance Linux binary distributions are still not an option for most people because of two basic problems: Python itself being compiled in different forms on Linux and modules being linked against different system libraries. The first problem is caused by Python 2 coming in two flavours that are both incompatible to each other: UCS2 Pythons and UCS4 Pythons. Depending on which mode Python is compiled with the ABI looks different. Presently the wheel format (from what I can tell) does not annotate for which Python unicode mode a library is linked. A separate problem is that Linux distributions are less compatible to each other as you would wish and concerns have been brought up that wheels compiled on one distribution will not work on others.

The end effect of this is that you presently cannot upload binary wheels to PyPI on concerns of incompatibility with different setups.

In addition to that wheel currently only knows two extremes: binary packages and pure Python packages. When something is a binary package it's specific to a Python version on 2.x. Right now that's actually not the worst thing in the world because Python 2.x is end of life and we really only need to build packages for 2.7 for a long time to come. If however we would start considering Python 2.8 then it would be interesting to have a way to say: this package is Python version independent but it ships binaries so it needs to be architecture specific.

The reason why you might have a package like this are packages that ship shared libraries loaded with ctypes of CFFI. These libraries do not link against libpython and as such would work cross Python (even cross Python implementation which means you can use them with pypy).

On the bright side: nothing stops yourself from using binary wheels for your own homogenous infrastructure.

Building Wheels

So now that you know what a wheel is, how do you make one? Building a wheel out of your own libraries is a very straightforward process. All you need to do is using a recent version of setuptools and the wheel library. Once you have both installed you can build a wheel out of your package by running this command:

$ python setup.py bdist_wheel

This will throw a wheel into your distribution folder. There are however one extra things you should be aware of and that's what happens if you ship binaries. By default the wheel you build (assuming you don't use any binary build steps as part of your setup.py) is to produce a pure Python wheel. This means that even if you ship a .so, .dylib or .dll as part of your package data the wheel spit out will look like it's platform independent.

The solution for this problem is to manually subclass the setuptools distribution to flip the purity flag to false:

import os
from setuptools import setup
from setuptools.dist import Distribution

class BinaryDistribution(Distribution):
    def is_pure(self):
        return False

setup(
    ...,
    include_package_data=True,
    distclass=BinaryDistribution,
)

Installing Wheels

Now you have a wheel, how do you install it? On a recent pip version you can install it this way:

$ pip install package-1.0-cp27-none-macosx_10_7_intel.whl

But what about your dependencies? This is what it gets a bit tricker. Generally what you would want is to install a package without ever connecting to the internet. Pip thankfully supports that by disabling downloading from an index and by providing a path to a folder for all the things it needs to install. So assuming you have all the wheels for all your dependencies in just the right version available, you can do this:

$ pip install --no-index --find-links=path/to/wheels package==1.0

This will then install the 1.0 version of package into your virtualenv.

Wheels for Dependencies

Alright, but what if you don't have the wheels for your dependencies? Pip in theory supports doing that through the wheel command. In theory this is supposed to work:

pip wheel --wheel-dir=path/to/wheels package==1.0

In this case wheel will throw all packages that package depends on into the given folder. There are two problems with this.

The first one is that the command currently has a bug and does not actually throw dependencies into the wheel folder if the dependencies are already wheels. What the command is supposed to do is to collect all the dependencies and the convert them into wheels if necessary and then places them in the wheel folder. What's actually happening though is that it only places wheels there for things that were not wheels to begin with. So if a dependency is already available as a wheel on PyPI then pip will skip it and not actually put it there.

The workaround is a shell script that goes through the download cache and manually moves downloaded wheels into the wheel directory:

#!/bin/sh
WHEEL_DIR=path/to/wheels
DOWNLOAD_CACHE_DIR=path/to/cache
rm -rf $DOWNLOAD_CACHE_DIR
mkdir -p $DOWNLOAD_CACHE_DIR

pip wheel --use-wheel -w "$WHEEL_DIR" -f "$WHEEL_DIR" \
  --download-cache "$DOWNLOAD_CACHE_DIR" package==1.0
for x in "$DOWNLOAD_CACHE_DIR/"*.whl; do
  mv "$x" "$WHEEL_DIR/${x##*%2F}"
done

The second problem is more severe. How can pip wheel find your own package if it's not on PyPI? The answer is: it cannot. So what the documentation generally recommends is to not run pip wheel package but to run pip wheel -r requirements.txt where requirements.txt includes all the dependencies of the package. Once that is done, manually copy your own package's wheel in there and distribute the final wheel folder.

DevPI Based Package Building

That workaround with depending on the requirements certainly works in simple situations, but what do you do if you have multiple in-house Python packages that depend on each other? It quickly falls apart.

Thankfully Holker Krekel sat down last year and build a solution for this problem called devpi. DevPI is essentially a practical hack around how pip interacts with PyPI. Once you have DevPI installed on your own computer it acts as a transparent proxy in front of PyPI and you can point pip to install from your local DevPI server instead of the public PyPI. Not only that, it also automatically caches all packages downloaded from PyPI locally so even if you kill your network connection you can continue downloading those packages as if PyPI was still running. In addition to being a proxy you can also upload your own packages into that local server so once you point pip to that server it will both find public packages as well as your own ones.

In order to use DevPI I recommend making a local virtualenv and installing it into that and then linking devpi-server and devpi into your search path (in my case ~/.local/bin is on my PATH):

$ virtualenv devpi-venv
$ devpi-venv/bin/pip install --ugprade pip wheel setuptools devpi
$ ln -s `pwd`/devpi-venv/bin/devpi ~/.local/bin
$ ln -s `pwd`/devpi-venv/bin/devpi-server ~/.local/bin

Afterwards all you need to do is to start devpi-server and it will continue running until you shut it down or reboot your computer:

$ devpi-server --start

Once it's running you need to initialize it once:

$ devpi use http://localhost:3141
$ devpi user -c $USER password=
$ devpi login $USER --password=
$ devpi index -c yourproject

In this case because I use DevPI locally for myself only I use the same name for the DevPI user as I use for my system. As the last step I create an index named after my project. You can have multiple indexes next to each other to separate your work.

To point pip to your DevPI you can export an environment variable:

$ export PIP_INDEX_URL=http://localhost:3141/$USER/yourproject/+simple/

Personally I place this in the postactivate script of my virtualenv to not accidentally download from the wrong DevPI index.

To place your own wheels on your local DevPI you can use the devpi binary:

$ devpi use yourproject
$ devpi upload --no-vcs --formats=bdist_wheel

The --no-vcs flag disables some magic in DevPI which tries to detect your version control system and moves some files off first. Personally this does not work for me because I ship files in my projects that I do not want to put into version control (like binaries).

Lastly I would strongly recommend breaking your setup.py files in a way that PyPI will reject them but DevPI will accept them to not accidentally release your code with setup.py release. The easiest way to accomplish this is to add an invalid PyPI trove classifier to your setup.py:

setup(
    ...
    classifier=['Private :: Do Not Upload'],
)

Wrapping it Up

Now with all that done you can start inter depending on your own private packages and build out wheels in one go. Once you have that, you can zip them up and upload them to another server and install them into a separate virtualenv.

All in all this whole process will get a bit simpler when the pip wheel command stops ignoring already existing wheels. Until then, a shell script is not the worst workaround.

Comparing to Eggs

Wheels currently seem to have more traction than eggs. The development is more active, PyPI started to add support for them and because all the tools start to work for them it seems to be the better solution. Eggs currently only work if you use easy_install instead of pip which seems to be something very few people still do.

I assume the Zope community is still largely based around eggs and buildout and I assume if an egg based deployment works for you, then that's the way to go. I know that many did not actually use eggs at all to install Python packages and instead built virtualenvs, zipped them up and sent them to different servers. For that kind of deployment, wheels are definitely a much superior solution because it means different servers can have the libraries in different paths. This previously was an issue because the .pyc files were created on the build server for the virtualenv and the .pyc files include the filenames.

With wheels the .pyc files are created upon installation into the virtualenv and will automatically include the correct paths.

So there you have it. Python on wheels. It's there, it kinda works, and it's probably worth your time.

Read the whole story
1stvamp
3950 days ago
reply
Selby, UK
Share this story
Delete

dduane:

1 Share
















dduane:

Read the whole story
1stvamp
3970 days ago
reply
Selby, UK
Share this story
Delete

A glimpse into a new general purpose programming language under development at Microsoft

2 Comments and 6 Shares

Microsoft's Joe Duffy and team have been (quietly) working on a new programming language, based on C# (for productivity, safety), but leveraging C++ features (for performance). I think it's fair to say - and agree with Joe - that a nirvana for a modern general purpose language would be one that satisfies high productivity (ease of use, intuitive, high level) AND guaranteed (type)safety AND high execution performance. As Joe outlines in his blog post (not video!):

At a high level, I classify the language features into six primary categories:

1) Lifetime understanding. C++ has RAII, deterministic destruction, and efficient allocation of objects. C# and Java both coax developers into relying too heavily on the GC heap, and offers only “loose” support for deterministic destruction via IDisposable. Part of what my team does is regularly convert C# programs to this new language, and it’s not uncommon for us to encounter 30-50% time spent in GC. For servers, this kills throughput; for clients, it degrades the experience, by injecting latency into the interaction. We’ve stolen a page from C++ — in areas like rvalue references, move semantics, destruction, references / borrowing — and yet retained the necessary elements of safety, and merged them with ideas from functional languages. This allows us to aggressively stack allocate objects, deterministically destruct, and more.


2) Side-effects understanding. This is the evolution of what we published in OOPSLA 2012, giving you elements of C++ const (but again with safety), along with first class immutability and isolation.


3) Async programming at scale. The community has been ’round and ’round on this one, namely whether to use continuation-passing or lightweight blocking coroutines. This includes C# but also pretty much every other language on the planet. The key innovation here is a composable type-system that is agnostic to the execution model, and can map efficiently to either one. It would be arrogant to claim we’ve got the one right way to expose this stuff, but having experience with many other approaches, I love where we landed.


4) Type-safe systems programming. It’s commonly claimed that with type-safety comes an inherent loss of performance. It is true that bounds checking is non-negotiable, and that we prefer overflow checking by default. It’s surprising what a good optimizing compiler can do here, versus JIT compiling. (And one only needs to casually audit some recent security bulletins to see why these features have merit.) Other areas include allowing you to do more without allocating. Like having lambda-based APIs that can be called with zero allocations (rather than the usual two: one for the delegate, one for the display). And being able to easily carve out sub-arrays and sub-strings without allocating.


5) Modern error model. This is another one that the community disagrees about. We have picked what I believe to be the sweet spot: contracts everywhere (preconditions, postconditions, invariants, assertions, etc), fail-fast as the default policy, exceptions for the rare dynamic failure (parsing, I/O, etc), and typed exceptions only when you absolutely need rich exceptions. All integrated into the type system in a 1st class way, so that you get all the proper subtyping behavior necessary to make it safe and sound.


6) Modern frameworks. This is a catch-all bucket that covers things like async LINQ, improved enumerator support that competes with C++ iterators in performance and doesn’t demand double-interface dispatch to extract elements, etc. To be entirely honest, this is the area we have the biggest list of “designed but not yet implemented features”, spanning things like void-as-a-1st-class-type, non-null types, traits, 1st class effect typing, and more. I expect us to have a few of these in our mid-2014 checkpoint, but not all of them.

What do you think?

Read the whole story
1stvamp
3970 days ago
reply
Selby, UK
Share this story
Delete
2 public comments
cpm
3977 days ago
reply
Worth watching... It does seems like there is a place for a language like this -- somewhere between C# and C++, taking lessons from both.
Chicago, USA
Next Page of Stories