网站开发WebDev分类
OpenERP Magento Integration
几个python的模块:
http://bazaar.launchpad.net/~magentoerpconnect-core-editors/magentoerpconnect/trunk_version/files
https://code.launchpad.net/~magentoerpconnect-core-editors/magentoerpconnect/magentoerpconnect-v6
Magento is a feature-rich eCommerce platform built on open-source technology that provides online merchants with unprecedented flexibility and control over the look, content and functionality of their eCommerce store. Magento’s intuitive administration interface features powerful marketing, search engine optimization and catalog-management tools to give merchants the power to create sites that are tailored to their unique business needs. Designed to be completely scalable and backed by Varien’s support network, Magento offers companies the ultimate eCommerce solution.
A new bridge between OpenERP and Magento initiated by Openlabs team and under rapid improvement with the active support of community is now one of the most popular combinations for the ‘best of breed’ approach in ERP e-commerce deployment. This module allows synchronization of Magento with Open ERP. It supports Synchronization of Customer Groups, Product Categories, Product Attribute Sets, Attribute Groups, Product Attributes, Products, Order Statuses, Image Synchronization and many more
The bridge consists of two components: A Magento side extension and an Open ERP side module. The Magento extension is a PHP based web services improvement which is fully written and maintained by Openlabs. The Open ERP module has achieved further modularity and is today the preferred algorithm to quickly integrate Open ERP with other applications. Our latest in the list is a simple integration with Sales Force CRM.
Openlabs is actively involved in the Magento – Open ERP Projects and ever since the development of the new connector we have been helping customers across the globe to migrate and implement it.At Openlabs we are committed to provide an excellent user experience .The following detailed tutorial will guide you to the installation process of – :
-
- Magento (full version)
-
- Magento – Open ERP Connector module contributed by Openlabs
-
- Magento Extension
Step 1: Required bits & pieces
- Required Open ERP Addons (Available for download from this site):
- Module: product
- Module: base_external_referentials
- Module: base_sale_multichannels
- Module: product_m2mcategories
2. Xampp 1.6.4
http://sourceforge.net/projects/xampp/files/XAMPP%20Linux/1.6.4/xampp-linux-1.6.4.tar.gz/download
After copying the addons to the Open ERP addons path,
- Go to a Linux shell and login as the system administrator root:
- For xampp-linux-1.6.4.tar.gz file follow these steps:-
- a)Extract the downloaded archive file to /opt. using sudo tar xvfz xampp-linux-1.6.4.tar.gz -C /opt
3. Open the page http://localhost.It should show xampp on the page. You may get errors like shown below:
Warning: file_get_contents(lang.tmp) [function.file-get-contents]: failed to open stream: Permission denied in /opt/lampp/htdocs/xampp/index.php on line 2
Warning: Cannot modify header information – headers already sent by (output started at /opt/lampp/htdocs/xampp/index.php:2) in /opt/lampp/htdocs/xampp/index.php on line 4
Above Errors can be Removed by following step – :
Change the permissions by using chmod of the lampp folder in /opt to readable and writable. After this change the permissions of file config.inc.php in /opt/lampp/phpmyadmin to “read-only” and restart lampp.
Step2: Installation of Magento full version
1.Download magento-1.3.2.4.tar.gz and extract it to /opt/lampp/htdocs
sudo tar xvfz magento-1.3.2.4.tar.gz -C /opt/lampp/htdocs
3.To check your Magento installation type http://<yourIPaddress>/magento in the browser.
It should display the “Setup for Magento”/”Magento Installation” for the first time and “Magento Demo Store” from the next time.
Note: Use your IP instead of localhost to avoid issues of magento kicking you from the admin login.
If you get an error: innodb engine Open file xamppmysqlbinmy.cnf (Using sudo gedit or sudo nano)
Find code:
- # Comment the following if you are using InnoDB tables
- Skip-innodb
- #innodb_data_home_dir = “/xampplite/mysql/”
- #innodb_data_file_path = ibdata1:10M: autoextend
- #innodb_log_group_home_dir = “/xampplite/mysql/”
- #innodb_log_arch_dir = “/xampplite/mysql/”
- ## You can set._buffer_pool_size up to 50 – 80 %
- ## Of RAM but beware of setting memory usage too high
- #innodb_buffer_pool_size = 16M
- #innodb_additional_mem_pool_size = 2M
- ## Set._log_file_size to 25 % of buffer pool size
- #innodb_log_file_size = 5M
- #innodb_log_buffer_size = 8M
- #innodb_flush_log_at_trx_commit = 1
- #innodb_lock_wait_timeout = 50
Modify to
- #Comment the following if you are using InnoDB tables
- #skip-innodb
- innodb_data_home_dir = “/xampplite/mysql/”
- innodb_data_file_path = ibdata1:10M: autoextend
- innodb_log_group_home_dir = “/xampplite/mysql/”
- innodb_log_arch_dir = “/xampplite/mysql/”
- ## You can set._buffer_pool_size up to 50 – 80 %
- ## Of RAM but beware of setting memory usage too high
- innodb_buffer_pool_size = 16M
- innodb_additional_mem_pool_size = 2M
- ## Set…_log_file_size to 25 % of buffer pool size
- innodb_log_file_size = 5M
- innodb_log_buffer_size = 8M
- innodb_flush_log_at_trx_commit = 1
- innodb_lock_wait_timeout = 50
Reload the magento page, if some error of “mysql.sock not found” comes then change the rights of my.cnf file to read only, than reload the magento page and it should work now.
Step3:Configuring the Bridge
The Magento-Open ERP bridge comprises of two parts:
-
- Open ERP Module
-
- Magento ERP Extension
Installation of Magento extension
For this in magento admin panel go to:
System >> Magento Connect >> Magento Connect Manager
Key in your username and password again and click on the second tab for settings and change preferred state to ‘Beta’. (As of this date the plug-in is beta).Save your settings and select page 1 (Extensions) and paste the following extension key in the box
“Magento-community/Openlabs_OpenERPConnector” without quotes
Now your installation of magento is almost ready to talk to Open ERP
To enable open ERP to access the resources in magento and synchronize, it is necessary to have a web services user. To create a web services user go to : System >> Web Service >>Roles
Create a new role e.g. ‘admin’
Save the user and set resources access as: ALL
Save the role and now create a web services user at: System >> Web Services >> Users
Create a user, save the user and set the assigned role as the newly created role, in the above example ‘admin’
The same settings have to be entered as credentials in the connector frontend in Open ERP at Magento >> Magento Web
The user id will be the newly created user name and password the newly created password of the web services user.
STEP 4 :Configuration of Magento – Open ERP Connector module
1.Ensure that you have at least 1 Product category in your system or create one. (Required)
2.Install the magentoerpconnect module (For how to install a module refer to doc.openerp.com) (Module available at bzr branch lp: magentoerpconnect and/or bzr branch http://bazaar.launchpad.net/~openlabs-akretion-consortium/magentoerpconnect/magentoerpconnect_generic)
3.Go to Magento Connection > Core Settings > Magento Instances
4.Create a new instance by clicking new
5.Give the connection a name (e.g. My local magento)
6.Referential Type (Only 1.3.2.4 is available now)
7.Location: Your magento URL e.g. (http ://< yourIPaddress>/magento) & Default Product category (Magento allows products without category and those will be classified here)
8.Click on Reload Referential Mapping Templates
9.Enter API Username and password defined in step 4 of magento configuration
10.Click on Synchronize Referential Settings
Openlabs provides standard packages for implementation of Magento OpenERP connector with following services, provided by our expert technical team:
- 1.Installation of Open ERP on production server.
- 2.Implementation of Magento Open ERP Connector.
- 3.Configuration & testing of the complete system as per the standard functionality.
- 4.Detailed user guides & user trainings for using the system.
Creating a Weather Widget with XML and Actionscript 3.0
In this tutorial I’m presenting you a little weather widget, that could come handy for travel websites, or our personal page. If you make some changes on the code, you could show a different interface on your website, according to the weather on your city. Pretty cool stuff. We gonna connect weather.com XML data file with our flash widget. Weather.com offers us a free service to do that.
Adobe Flash CS3
Source Files
The first thing we gonna do is login in our weather.com account:
https://registration.weather.com/ursa/profile

Or if we already don’t have an account we could register for free in here:
https://registration.weather.com/ursa/profile/new?

Once we are in our account, we gonna have a page like this one, where on the left we gonna have all the services we are subscribed and on the right, all the available services, the XML data feed is not there, it’s a little bit tricky.

So, we gonna signup to the XML data feed on this page https://registration.weather.com/ursa/xmloap/Where we gonna tell to weather.com in which way we gonna use the data. If it’s for personal purpose, there’s no problem, but if it’s commercial purpose, you have to contact them, but everything is better explained on the mail you are going to receive.

So if our registration it’s ok, we gonna receive an email like this one (check on the spam inbox too, just in case).

The email explains us about the service, and there’s three important things to see in here:
- Where to download the weather.com SDK (http://download.weather.com/web/xml/sdk.zip)
- The weather.com XML Partner ID & License Key
- How to make a properly formatted XML request for the service

On this package you gonna find a PDF, with the license agreement and some useful information about how to make a request for the services, and two folder, one with the weather.com logos, and the other with the weather icons we gonna use on this tutorial as reference. There’s 3 type of icon sizes, for this tutorial we gonna use the 93×93 icons. The icons are not too fancy, so we could download a free cool set of weather icons from here:
http://liquidweather.net/icons.php#iconsets
This iconsets and backgrounds are from the Liquid Weather++ appi.
We could put all our data inside flash, like the code of our city, our partner id, and so on, but it’s gonna be a pain in the ass every time we want to change it. So, it’s much better if we load this type of data externally form an XML file. The XML structure should looks like this:
ARBA0009 XXXXXXXXX XXXXXXXXXX m
Now our hands on AS. We gonna do all the programming in just 2 frames, pretty easy. On the first frame, we load all the data to use it when we call the weather.com xml service. On the second frame, called “weather”, we load the weather.com xml and display the data. So, in our first frame:
- We define the variables we gonna use
- We call the xml that holds our data
- Then we pass the data from the xml to our variables
- And if everything it’s oki, we could go to the weather frame

stop();
//=================
//ini variables
//=================
//we define our variables to be loaded
var city:String;
var par_id:String;
var key:String;
var units:String;
//call the xml with our data
var data_xml_url:String = "data.xml";
var user_data:XML = new XML();
var data_url:URLRequest = new URLRequest(data_xml_url);
var dataLoader:URLLoader = new URLLoader(data_url);
dataLoader.addEventListener(Event.COMPLETE, dataLoaded);
function dataLoaded(e:Event):void
{
user_data = XML(dataLoader.data);
//pass the data from the XML to our variables
city = user_data.city.toString();
par_id = user_data.parid.toString();
key = user_data.key.toString();
units = user_data.units.toString();
//if everything it's oki, we could go to the weather frame
gotoAndStop("weather");
}
To display the different type of weather, we need a movieClip that holds all the possible weathers, there’s not too much, just 47. So, we create a new movieclip and call it “icons_mc”, and inside the icons_mc we import all the icons from the Weather.com SDK icons folder, or from the cool icons from liquidweather. There’s a lil bit differences between the Weather.com SDK icons and the liquidweather icons. On the weather.com SDK, the icons 25 and 44 are N/A, if you gonna use the liquidweather icons, just replace the 25 and 44 icons with the N/A png. Don’t forget to put a stop(); in the first frame of the icons_mc, so the MC start stopped.

Now we could load the data from weather.com. We allow our swf to make calls from the domain “xoap.weather.com”, we make the icons_mc MovieClip invisible, because we don’t know which icon use yet, and we compose the weather xml url with the variables from the frame 1.
stop();
//=================
//allow domains
//=================
Security.allowDomain("*", "xoap.weather.com");
//=================
//ini settings
//=================
icons_mc.visible = false;
//=================
//XML
//=================
var weather_xml_url:String = "http://xoap.weather.com/weather/local/"+city+"?cc=*&link=xoap&par="+par_id+"&key="+key+"&unit="+units;
The xml from weather.com have this structure with some interesting data. As you can see there’s a lot of useful data to load, but we gonna load just the necessary by now. Later you could play with it and make an advanced one.
en_US
C
km
km/h
mb
mm
Buenos Aires, Argentina
1:39 PM
-34.61
-58.37
8:01 AM
5:52 PM
-3
6/27/08 1:00 PM Local Time
Buenos Aires Air Park, Argentina
13
13
Fog
20
1021.0
falling
11
N/A
360
N
82
2.5
2
Low
10
23
Waning Crescent
To display the data, we have three different sprites:
- The icons MovieClip
- A dynamic text field to load the outside temp called “temp_txt”
- A dynamic text filed in the bottom to make a lil speech about how feels to be outside called “info_txt”

Our main interest is on the XML file on the icon number, so we could show the right icon. We load the xml using our well formatted “weather_xml_url”, and traverse the xml three to find what we are looking for.
In this step we:
- we load the temp on the temp_txt text field
- make the icons visible, load the icon number and tell the icons:mc to stopn on thet icon frame
- and finally we set the complementary text
var weather:XML = new XML();
var weather_url:URLRequest = new URLRequest(weather_xml_url);
var weatherLoader:URLLoader = new URLLoader(weather_url);
weatherLoader.addEventListener(Event.COMPLETE, weatherLoaded);
function weatherLoaded(e:Event):void
{
weather = XML(weatherLoader.data);
//we load the temp on the temp_txt text field
temp_txt.text = weather.cc.tmp;
//we make the icons visible, load the icon number and tell the icons:mc to stopn on thet icon frame
icons_mc.visible = true;
var weather_icon:int = Number(weather.cc.icon.toString())+1;
icons_mc.gotoAndStop(weather_icon);
//and finally we set the complementary text
var ud:String = weather.head.ud;
var us:String = weather.head.us;
var city:String = weather.loc.dnam;
var time:String = weather.loc.tm;
var temp:String = weather.cc.tmp;
var flik:String = weather.cc.flik;
var term:String;
if (temp == flik)
{
term = "and it feels like "+flik+" degrees,";
} else {
term = "but it feels like "+flik+" degrees,";
}
var wind_v:String = weather.cc.wind.s;
var wind_gust:String = weather.cc.wind.gust;
var wind_d:String = weather.cc.wind.d;
var wind_t:String = weather.cc.wind.t;
var hmid:String = weather.cc.hmid;
var vis:String = weather.cc.vis;
info_txt.text = "It's "+time+" here in "+city+". It's "+temp+" degrees out there "+term+" the wind blows from the "+wind_t+" at "+wind_v+" "+us+", the humidity is "+hmid+"% and the visibility is "+vis+" "+ud+".";
}

Now we could export our movie and if it’s cold outside, it’s good to be inside.
We gonna need to know the international code of our city, so we could go tohttp://www.weather.com/common/welcomepage/world.html Search for our city, and find the code on our address bar. I know it’s a lil bit tricky but it works fine.

Hope you enjoyed this lil tutorial as much as I enjoyed writing for you.
5 ImageMagick command line examples – part 1
If you have ever wanted to manipulate images under linux you probably have used Gimp. This isn’t your only option and if you want to do things from the command line a better option is to use ImageMagick‘s convert utility.
I’ve put together 5 simple command line examples that I have found useful. This is just a sample of what you can do with convert. To see more examples and get more explanation of options see: ImageMagick v6 Examples.
I started with the following image as a base for all the examples that follow:

1. Text annotations
Example (simple text in static location):
Produces:

Example (text with background at bottom):
Produces:

Look at these examples to see more.
2. Cropping an image
Example:
Produces:

Look at these examples or -crop for more information.
3. Rotate an image
Example:
Produces:

Look at these examples or -rotate for more information.
4. Image montage
Example:
Produces:

Look at these examples to see more.
5. Animation
Example:
convert flower.jpg -resize 100×100 -font courier -fill white -pointsize 20 -annotate +50+50 ‘Frame 2′ flower_frame2.gif
convert flower.jpg -resize 100×100 -font courier -fill white -pointsize 20 -annotate +50+50 ‘Frame 3′ flower_frame3.gif
convert flower.jpg -resize 100×100 -font courier -fill white -pointsize 20 -annotate +50+50 ‘Frame 4′ flower_frame4.gif
convert -delay 100 -size 100×100 \
-page +0+0 flower_frame1.gif \
-page +0+0 flower_frame2.gif \
-page +0+0 flower_frame3.gif \
-page +0+0 flower_frame4.gif \
-loop 0 flower_animation.gif
Produces:

PHP ImageMagick MagickWand Examples
A while back I explained how to compile the ImageMagick extension for PHPand this past week I got around to creating some example code to make some of the command line examples I have in ImageMagick command line examples part 1 and ImageMagick command line examples part 2.
The first step of course is to make sure the MagickWand extension is installed. You will want to verify that it is listed in a phpinfo() call before trying any of these examples. After verifying that you have the extension installed you might want to read an introduction to using MagickWand before looking at these examples. And of course you will want to know where the MagickWand reference documentation is once you are ready to try more.
For more information on the options used in these examples it is best to look at their corresponding command line example.
Example 1: Simple Annotate
// convert flower.jpg -font courier -fill white -pointsize 20 -annotate +50+50 Flower flower_annotate1.jpg
$resource = NewMagickWand();
$dwand = NewDrawingWand();
$pwand = NewPixelWand();
PixelSetColor($pwand, ”white”);
DrawSetFont($dwand,”/usr/share/fonts/default/TrueType/cour.ttf”);
DrawSetFontSize($dwand, 20);
DrawSetFillColor($dwand, $pwand);
MagickReadImage( $resource, ’small_flower.jpg’ );
if( MagickAnnotateImage( $resource, $dwand, 0, 0, 0, ”Flower” ))
{
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
}
else
{
echo MagickGetExceptionString($resource);
}
?>
One note on the above is that I needed to specify the exact location of the font to get it to show up. I believe this isn’t always needed but if you try to leave it out and nothing shows up you should try specifying the full path to the font.
Example 2: Complex Annotate
// convert flower.jpg -fill white -box “#00770080″ -gravity South -pointsize 20 -annotate +0+5 ” Flower ” flower_annotate2.jpg
$resource = NewMagickWand();
$dwand = NewDrawingWand();
$pwand = NewPixelWand();
PixelSetColor($pwand, ”white”);
DrawSetFont($dwand,”/usr/share/fonts/default/TrueType/cour.ttf”);
DrawSetFontSize($dwand, 20);
DrawSetFillColor($dwand, $pwand);
DrawSetGravity($dwand, MW_SouthGravity);
MagickReadImage( $resource, ’small_flower.jpg’ );
if( MagickAnnotateImage( $resource, $dwand, 0, 0, 0, ”Flower” ))
{
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
}
else
{
echo MagickGetExceptionString($resource);
}
?>
Example 3: Crop an Area
// convert flower.jpg -crop 128×128+50+50 flower_crop.jpg
$resource = NewMagickWand();
MagickReadImage( $resource, ’small_flower.jpg’ );
if( MagickCropImage( $resource, 128, 128, 50, 50 ) )
{
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
}
else
{
echo MagickGetExceptionString($resource);
}
?>
Example 4: Rotate
// convert flower.jpg -rotate 45 flower_rotate45.jpg
$resource = NewMagickWand();
MagickReadImage( $resource, ’small_flower.jpg’ );
MagickRotateImage( $resource, null, 45 );
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
?>
Example 5: Resize
// convert flower_original.jpg -resize 640×480 flower.jpg
$resource = NewMagickWand();
MagickReadImage( $resource, ’small_flower.jpg’ );
MagickResizeImage( $resource, 100, 100, MW_QuadraticFilter, 1.0);
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
?>
Example 6: Apply Resharp Filter
// convert flower.jpg -unsharp 1.5×1.0+1.5+0.02 flower_unsharp.jpg
$resource = NewMagickWand();
MagickReadImage( $resource, ’small_flower.jpg’ );
MagickUnsharpMaskImage( $resource, 1.5, 1.0, 1.5, 0.02 );
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
?>
Example 7: Compress JPG
// convert flower.jpg -quality 80% flower_quality.jpg
$resource = NewMagickWand();
MagickReadImage( $resource, ’small_flower.jpg’ );
MagickSetFormat($resource, ’JPG’);
MagickSetImageCompression($resource, MW_JPEGCompression);
MagickSetImageCompressionQuality($resource, 80.0);
header( ’Content-Type: image/gif’ );
MagickEchoImageBlob( $resource );
?>
linux 文件系统 btrfs 在 kernel 3.0的支持
安装的gentoo现在用的是keernel 3.0所以顺便研究了btrfs性能改进情况。 具体这篇文章很有价值,是一个很好的入门指引。
如果基于ssd,或者用raid搭建文件系统,btrfs给了我很大的想法。
Btrfs 简介
文件系统似乎是内核中比较稳定的部分,多年来,人们一直使用 ext2/3,ext 文件系统以其卓越的稳定性成为了事实上的 Linux 标准文件系统。近年来 ext2/3 暴露出了一些扩展性问题,于是便催生了 ext4 。在 2008 年发布的 Linux2.6.19 内核中集成了 ext4 的 dev 版本。 2.6.28 内核发布时,ext4 结束了开发版,开始接受用户的使用。似乎 ext 就将成为 Linux 文件系统的代名词。然而当您阅读很多有关 ext4 的文章时,会发现都不约而同地提到了 btrfs,并认为 ext4 将是一个过渡的文件系统。 ext4 的作者 Theodore Tso 也盛赞 btrfs 并认为 btrfs 将成为下一代 Linux 标准文件系统。 Oracle,IBM, Intel 等厂商也对 btrfs 表现出了极大的关注,投入了资金和人力。为什么 btrfs 如此受人瞩目呢。这便是本文首先想探讨的问题。
Kevin Bowling[1] 有一篇介绍各种文件系统的文章,在他看来,ext2/3 等文件系统属于“古典时期”。文件系统的新时代是 2005 年由 Sun 公司的 ZFS 开创的。 ZFS 代表” last word in file system ”,意思是此后再也不需要开发其他的文件系统了。 ZFS 的确带来了很多崭新的观念,对文件系统来讲是一个划时代的作品。
如果您比较 btrfs 的特性,将会发现 btrfs 和 ZFS 非常类似。也许我们可以认为 btrfs 就是 Linux 社区对 ZFS 所作出的回应。从此往后在 Linux 中也终于有了一个可以和 ZFS 相媲美的文件系统。
回页首
btrfs 的特性
您可以在 btrfs 的主页上 [2] 看到 btrfs 的特性列表。我自作主张,将那张列表分成了四大部分。
首先是扩展性 (scalability) 相关的特性,btrfs 最重要的设计目标是应对大型机器对文件系统的扩展性要求。 Extent,B-Tree 和动态 inode 创建等特性保证了 btrfs 在大型机器上仍有卓越的表现,其整体性能而不会随着系统容量的增加而降低。
其次是数据一致性 (data integrity) 相关的特性。系统面临不可预料的硬件故障,Btrfs 采用 COW 事务技术来保证文件系统的一致性。 btrfs 还支持 checksum,避免了 silent corrupt 的出现。而传统文件系统则无法做到这一点。
第三是和多设备管理相关的特性。 Btrfs 支持创建快照 (snapshot),和克隆 (clone) 。 btrfs 还能够方便的管理多个物理设备,使得传统的卷管理软件变得多余。
最后是其他难以归类的特性。这些特性都是比较先进的技术,能够显著提高文件系统的时间 / 空间性能,包括延迟分配,小文件的存储优化,目录索引等。
扩展性相关的特性
B-Tree
btrfs 文件系统中所有的 metadata 都由 BTree 管理。使用 BTree 的主要好处在于查找,插入和删除操作都很高效。可以说 BTree 是 btrfs 的核心。
一味地夸耀 BTree 很好很高效也许并不能让人信服,但假如稍微花费一点儿时间看看 ext2/3 中元数据管理的实现方式,便可以反衬出 BTree 的优点。
妨碍 ext2/3 扩展性的一个问题来自其目录的组织方式。目录是一种特殊的文件,在 ext2/3 中其内容是一张线性表格。如图 1-1 所示 [6]:
图 1. ext2 directory [6]
图 1 展示了一个 ext2 目录文件的内容,该目录中包含四个文件。分别是 “home1″,”usr”,”oldfile” 和 “sbin” 。如果需要在该目录中查找目录 sbin,ext2 将遍历前三项,直至找到 sbin 这个字符串为止。
这种结构在文件个数有限的情况下是比较直观的设计,但随着目录下文件数的增加,查找文件的时间将线性增长。 2003 年,ext3 设计者开发了目录索引技术,解决了这个问题。目录索引使用的数据结构就是 BTree 。如果同一目录下的文件数超过 2K,inode 中的 i_data 域指向一个特殊的 block 。在该 block 中存储着目录索引 BTree 。 BTree 的查找效率高于线性表,
但为同一个元数据设计两种数据结构总是不太优雅。在文件系统中还有很多其他的元数据,用统一的 BTree 管理是非常简单而优美的设计。
Btrfs 内部所有的元数据都采用 BTree 管理,拥有良好的可扩展性。 btrfs 内部不同的元数据由不同的 Tree 管理。在 superblock 中,有指针指向这些 BTree 的根。如图 2 所示:
图 2. btrfs btree
FS Tree 管理文件相关的元数据,如 inode,dir 等; Chunk tree 管理设备,每一个磁盘设备都在 Chunk Tree 中有一个 item ; Extent Tree 管理磁盘空间分配,btrfs 每分配一段磁盘空间,便将该磁盘空间的信息插入到 Extent tree 。查询 Extent Tree 将得到空闲的磁盘空间信息; Tree of tree root 保存很多 BTree 的根节点。比如用户每建立一个快照,btrfs 便会创建一个 FS Tree 。为了管理所有的树,btrfs 采用 Tree of tree root 来保存所有树的根节点; checksum Tree 保存数据块的校验和。
基于 Extent 的文件存储
现代很多文件系统都采用了 extent 替代 block 来管理磁盘。 Extent 就是一些连续的 block,一个 extent 由起始的 block 加上长度进行定义。
Extent 能有效地减少元数据开销。为了进一步理解这个问题,我们还是看看 ext2 中的反面例子。
ext2/3 以 block 为基本单位,将磁盘划分为多个 block 。为了管理磁盘空间,文件系统需要知道哪些 block 是空闲的。 Ext 使用 bitmap 来达到这个目的。 Bitmap 中的每一个 bit 对应磁盘上的一个 block,当相应 block 被分配后,bitmap 中的相应 bit 被设置为 1 。这是很经典也很清晰的一个设计,但不幸的是当磁盘容量变大时,bitmap 自身所占用的空间也将变大。这就导致了扩展性问题,随着存储设备容量的增加,bitmap 这个元数据所占用的空间也随之增加。而人们希望无论磁盘容量如何增加,元数据不应该随之线形增加,这样的设计才具有可扩展性。
下图比较了 block 和 extent 的区别:
图 3. 采用 extent 的 btrfs 和采用 bitmap 的 ext2/3
在 ext2/3 中,10 个 block 需要 10 个 bit 来表示;在 btrfs 中则只需要一个元数据。对于大文件,extent 表现出了更加优异的管理性能。
Extent 是 btrfs 管理磁盘空间的最小单位,由 extent tree 管理。 Btrfs 分配 data 或 metadata 都需要查询 extent tree 以便获得空闲空间的信息。
动态 inode 分配
为了理解动态 inode 分配,还是需要借助 ext2/3 。下表列举了 ext2 文件系统的限制:
表 1. ext2 限制
限制
最大文件数量 文件系统空间大小 V / 8192
比如 100G 大小的文件系统中,能创建的文件个数最大为 131072
图 4 显示了 ext2 的磁盘布局:
图 4. ext2 layout
在 ext2 中 inode 区是被预先固定分配的,且大小固定,比如一个 100G 的分区中,inode table 区中只能存放 131072 个 inode,这就意味着不可能创建超过 131072 个文件,因为每一个文件都必须有一个唯一的 inode 。
为了解决这个问题,必须动态分配 inode 。每一个 inode 只是 BTree 中的一个节点,用户可以无限制地任意插入新的 inode,其物理存储位置是动态分配的。所以 btrfs 没有对文件个数的限制。
针对 SSD 的优化支持
SSD 是固态存储 Solid State Disk 的简称。在过去的几十年中,CPU/RAM 等器件的发展始终遵循着摩尔定律,但硬盘 HDD 的读写速率却始终没有飞跃式的发展。磁盘 IO 始终是系统性能的瓶颈。
SSD 采用 flash memory 技术,内部没有磁盘磁头等机械装置,读写速率大幅度提升。 flash memory 有一些不同于 HDD 的特性。 flash 在写数据之前必须先执行擦除操作;其次,flash 对擦除操作的次数有一定的限制,在目前的技术水平下,对同一个数据单元最多能进行约 100 万次擦除操作,因此,为了延长 flash 的寿命,应该将写操作平均到整个 flash 上。
SSD 在硬件内部的微代码中实现了 wear leveling 等分布写操作的技术,因此系统无须再使用特殊的 MTD 驱动和 FTL 层。虽然 SSD 在硬件层面做了很多努力,但毕竟还是有限。文件系统针对 SSD 的特性做优化不仅能提高 SSD 的使用寿命,而且能提高读写性能。 Btrfs 是少数专门对 SSD 进行优化的文件系统。 btrfs 用户可以使用 mount 参数打开对 SSD 的特殊优化处理。
Btrfs 的 COW 技术从根本上避免了对同一个物理单元的反复写操作。如果用户打开了 SSD 优化选项,btrfs 将在底层的块空间分配策略上进行优化:将多次磁盘空间分配请求聚合成一个大小为 2M 的连续的块。大块连续地址的 IO 能够让固化在 SSD 内部的微代码更好的进行读写优化,从而提高 IO 性能。
数据一致性相关的特性
COW 事务
理解 COW 事务,必须首先理解 COW 和事务这两个术语。
什么是 COW?
所谓 COW,即每次写磁盘数据时,先将更新数据写入一个新的 block,当新数据写入成功之后,再更新相关的数据结构指向新 block 。
什么是事务?
COW 只能保证单一数据更新的原子性。但文件系统中很多操作需要更新多个不同的元数据,比如创建文件需要修改以下这些元数据:
修改 extent tree,分配一段磁盘空间
创建一个新的 inode,并插入 FS Tree 中
增加一个目录项,插入到 FS Tree 中
任何一个步骤出错,文件便不能创建成功,因此可以定义为一个事务。
下面将演示一个 COW 事务。
A 是 FS Tree 的根节点,新的 inode 的信息将被插入节点 C 。首先,btrfs 将 inode 插入一个新分配的 block C ’中,并修改上层节点 B,使其指向新的 block C ’;修改 B 也将引发 COW,以此类推,引发一个连锁反应,直到最顶层的 Root A 。当整个过程结束后,新节点 A ’变成了 FS Tree 的根。但此时事务并未结束,superblock 依然指向 A 。
图 5. COW transaction 1
接下来,修改目录项(E 节点),同样引发这一过程,从而生成新的根节点 A ’’。
图 6. COW transaction 2
此时,inode 和目录项都已经写入磁盘,可以认为事务已经结束。 btrfs 修改 superblock,使其指向 A ’’,如下图所示:
图 7. COW transaction 3
COW 事务能够保证文件系统的一致性,并且系统 Reboot 之后不需要执行 fsck 。因为 superblock 要么指向新的 A ’’,要么指向 A,无论哪个都是一致的数据。
Checksum
Checksum 技术保证了数据的可靠性,避免 silent corruption 现象。由于硬件原因,从磁盘上读出的数据会出错。比如 block A 中存放的数据为 0×55,但读取出来的数据变是 0×54,因为读取操作并未报错,所以这种错误不能被上层软件所察觉。
解决这个问题的方法是保存数据的校验和,在读取数据后检查校验和。如果不符合,便知道数据出现了错误。
ext2/3 没有校验和,对磁盘完全信任。而不幸的是,磁盘的错误始终存在,不仅发生在廉价的 IDE 硬盘上,昂贵的 RAID 也存在 silent corruption 问题。而且随着存储网络的发展,即使数据从磁盘读出正确,也很难确保能够安全地穿越网络设备。
btrfs 在读取数据的同时会读取其相应的 checksum 。如果最终从磁盘读取出来的数据和 checksum 不相同,btrfs 会首先尝试读取数据的镜像备份,如果数据没有镜像备份,btrfs 将返回错误。写入磁盘数据之前,btrfs 计算数据的 checksum 。然后将 checksum 和数据同时写入磁盘。
Btrfs 采用单独的 checksum Tree 来管理数据块的校验和,把 checksum 和 checksum 所保护的数据块分离开,从而提供了更严格的保护。假如在每个数据 block 的 header 中加入一个域保存 checksum,那么这个数据 block 就成为一个自己保护自己的结构。这种结构下有一种错误无法检测出来,比如本来文件系统打算从磁盘上读 block A,但返回了 block B,由于 checksum 在 block 内部,因此 checksum 依旧是正确的。 btrfs 采用 checksum tree 来保存数据块的 checksum,避免了上述问题。
Btrfs 采用 crc32 算法计算 checksum,在将来的开发中会支持其他类型的校验算法。为了提高效率,btrfs 将写数据和 checksum 的工作分别用不同的内核线程并行执行。
多设备管理相关的特性
每个 Unix 管理员都曾面临为用户和各种应用分配磁盘空间的任务。多数情况下,人们无法事先准确地估计一个用户或者应用在未来究竟需要多少磁盘空间。磁盘空间被用尽的情况经常发生,此时人们不得不试图增加文件系统空间。传统的 ext2/3 无法应付这种需求。
很多卷管理软件被设计出来满足用户对多设备管理的需求,比如 LVM 。 Btrfs 集成了卷管理软件的功能,一方面简化了用户命令;另一方面提高了效率。
多设备管理
Btrfs 支持动态添加设备。用户在系统中增加新的磁盘之后,可以使用 btrfs 的命令将该设备添加到文件系统中。
为了灵活利用设备空间,Btrfs 将磁盘空间划分为多个 chunk 。每个 chunk 可以使用不同的磁盘空间分配策略。比如某些 chunk 只存放 metadata,某些 chunk 只存放数据。一些 chunk 可以配置为 mirror,而另一些 chunk 则可以配置为 stripe 。这为用户提供了非常灵活的配置可能性。
Subvolume
Subvolume 是很优雅的一个概念。即把文件系统的一部分配置为一个完整的子文件系统,称之为 subvolume 。
采用 subvolume,一个大的文件系统可以被划分为多个子文件系统,这些子文件系统共享底层的设备空间,在需要磁盘空间时便从底层设备中分配,类似应用程序调用 malloc() 分配内存一样。可以称之为存储池。这种模型有很多优点,比如可以充分利用 disk 的带宽,可以简化磁盘空间的管理等。
所谓充分利用 disk 的带宽,指文件系统可以并行读写底层的多个 disk,这是因为每个文件系统都可以访问所有的 disk 。传统的文件系统不能共享底层的 disk 设备,无论是物理的还是逻辑的,因此无法做到并行读写。
所谓简化管理,是相对于 LVM 等卷管理软件而言。采用存储池模型,每个文件系统的大小都可以自动调节。而使用 LVM,如果一个文件系统的空间不够了,该文件系统并不能自动使用其他磁盘设备上的空闲空间,而必须使用 LVM 的管理命令手动调节。
Subvolume 可以作为根目录挂载到任意 mount 点。 subvolume 是非常有趣的一个特性,有很多应用。
假如管理员只希望某些用户访问文件系统的一部分,比如希望用户只能访问 /var/test/ 下面的所有内容,而不能访问 /var/ 下面其他的内容。那么便可以将 /var/test 做成一个 subvolume 。 /var/test 这个 subvolume 便是一个完整的文件系统,可以用 mount 命令挂载。比如挂载到 /test 目录下,给用户访问 /test 的权限,那么用户便只能访问 /var/test 下面的内容了。
快照和克隆
快照是对文件系统某一时刻的完全备份。建立快照之后,对文件系统的修改不会影响快照中的内容。这是非常有用的一种技术。
比如数据库备份。假如在时间点 T1,管理员决定对数据库进行备份,那么他必须先停止数据库。备份文件是非常耗时的操作,假如在备份过程中某个应用程序修改了数据库的内容,那么将无法得到一个一致性的备份。因此在备份过程中数据库服务必须停止,对于某些关键应用这是不能允许的。
利用快照,管理员可以在时间点 T1 将数据库停止,对系统建立一个快照。这个过程一般只需要几秒钟,然后就可以立即重新恢复数据库服务。此后在任何时候,管理员都可以对快照的内容进行备份操作,而此时用户对数据库的修改不会影响快照中的内容。当备份完成,管理员便可以删除快照,释放磁盘空间。
快照一般是只读的,当系统支持可写快照,那么这种可写快照便被称为克隆。克隆技术也有很多应用。比如在一个系统中安装好基本的软件,然后为不同的用户做不同的克隆,每个用户使用自己的克隆而不会影响其他用户的磁盘空间。非常类似于虚拟机。
Btrfs 支持 snapshot 和 clone 。这个特性极大地增加了 btrfs 的使用范围,用户不需要购买和安装昂贵并且使用复杂的卷管理软件。下面简要介绍一下 btrfs 实现快照的基本原理。
如前所述 Btrfs 采用 COW 事务技术,从图 1-10 可以看到,COW 事务结束后,如果不删除原来的节点 A,C,E,那么 A,C,E,D,F 依然完整的表示着事务开始之前的文件系统。这就是 snapshot 实现的基本原理。
Btrfs 采用引用计数决定是否在事务 commit 之后删除原有节点。对每一个节点,btrfs 维护一个引用计数。当该节点被别的节点引用时,该计数加一,当该节点不再被别的节点引用时,该计数减一。当引用计数归零时,该节点被删除。对于普通的 Tree Root, 引用计数在创建时被加一,因为 Superblock 会引用这个 Root block 。很明显,初始情况下这棵树中的所有其他节点的引用计数都为一。当 COW 事务 commit 时,superblock 被修改指向新的 Root A ’’,原来 Root block A 的引用计数被减一,变为零,因此 A 节点被删除。 A 节点的删除会引发其子孙节点的引用计数也减一,图 1-10 中的 B,C 节点的引用计数因此也变成了 0,从而被删除。 D,E 节点在 COW 时,因为被 A ’’所引用,计数器加一,因此计数器这时并未归零,从而没有被删除。
创建 Snapshot 时,btrfs 将的 Root A 节点复制到 sA,并将 sA 的引用计数设置为 2 。在事务 commit 的时候,sA 节点的引用计数不会归零,从而不会被删除,因此用户可以继续通过 Root sA 访问 snapshot 中的文件。
图 8. Snapshot
软件 RAID
RAID 技术有很多非常吸引人的特性,比如用户可以将多个廉价的 IDE 磁盘组合为 RAID0 阵列,从而变成了一个大容量的磁盘; RAID1 和更高级的 RAID 配置还提供了数据冗余保护,从而使得存储在磁盘中的数据更加安全。
Btrfs 很好的支持了软件 RAID,RAID 种类包括 RAID0,RAID1 和 RAID10.
Btrfs 缺省情况下对 metadata 进行 RAID1 保护。前面已经提及 btrfs 将设备空间划分为 chunk,一些 chunk 被配置为 metadata,即只存储 metadata 。对于这类 chunk,btrfs 将 chunk 分成两个条带,写 metadata 的时候,会同时写入两个条带内,从而实现对 metadata 的保护。
其他特性
Btrfs 主页上罗列的其他特性不容易分类,这些特性都是现代文件系统中比较先进的技术,能够提高文件系统的时间或空间效率。
Delay allocation
延迟分配技术能够减少磁盘碎片。在 Linux 内核中,为了提高效率,很多操作都会延迟。
在文件系统中,小块空间频繁的分配和释放会造成碎片。延迟分配是这样一种技术,当用户需要磁盘空间时,先将数据保存在内存中。并将磁盘分配需求发送给磁盘空间分配器,磁盘空间分配器并不立即分配真正的磁盘空间。只是记录下这个请求便返回。
磁盘空间分配请求可能很频繁,所以在延迟分配的一段时间内,磁盘分配器可以收到很多的分配请求,一些请求也许可以合并,一些请求在这段延迟期间甚至可能被取消。通过这样的“等待”,往往能够减少不必要的分配,也有可能将多个小的分配请求合并为一个大的请求,从而提高 IO 效率。
Inline file
系统中往往存在大量的小文件,比如几百个字节或者更小。如果为其分配单独的数据 block,便会引起内部碎片,浪费磁盘空间。 btrfs 将小文件的内容保存在元数据中,不再额外分配存放文件数据的磁盘块。改善了内部碎片问题,也增加了文件的访问效率。
图 9. inline file
上图显示了一个 BTree 的叶子节点。叶子中有两个 extent data item 元数据,分别用来表示文件 file1 和 file2 所使用的磁盘空间。
假设 file1 的大小仅为 15 个字节; file2 的大小为 1M 。如图所示,file2 采用普通的 extent 表示方法:extent2 元数据指向一段 extent,大小为 1M,其内容便是 file2 文件的内容。
而对于 file1, btrfs 会把其文件内容内嵌到元数据 extent1 中。如果不采用 inline file 技术。如虚线所示,extent1 指向一个最小的 extent,即一个 block,但 file1 有 15 个字节,其余的空间便成为了碎片空间。
采用 inline 技术,读取 file1 时只需要读取元数据 block,而无需先读取 extent1 这个元数据,再读取真正存放文件内容的 block,从而减少了磁盘 IO 。
得益于 inline file 技术,btrfs 处理小文件的效率非常高,也避免了磁盘碎片问题。
目录索引 Directory index
当一个目录下的文件数目巨大时,目录索引可以显著提高文件搜索时间。 Btrfs 本身采用 BTree 存储目录项,所以在给定目录下搜索文件的效率是非常高的。
然而,btrfs 使用 BTree 管理目录项的方式无法同时满足 readdir 的需求。 readdir 是 POSIX 标准 API,它要求返回指定目录下的所有文件,并且特别的,这些文件要按照 inode number 排序。而 btrfs 目录项插入 BTree 时的 Key 并不是 Inode number,而是根据文件名计算的一个 hash 值。这种方式在查找一个特定文件时非常高效,但却不适于 readdir 。为此,btrfs 在每次创建新的文件时,除了插入以 hash 值为 Key 的目录项外,还同时插入另外一种目录项索引,该目录项索引的 KEY 以 sequence number 作为 BTree 的键值。这个 sequence number 在每次创建新文件时线性增加。因为 Inode number 也是每次创建新文件时增加的,所以 sequence number 和 inode number 的顺序相同。以这种 sequence number 作为 KEY 在 BTree 中查找便可以方便的得到一个以 inode number 排序的文件列表。
另外以 sequence number 排序的文件往往在磁盘上的位置也是相邻的,所以以 sequence number 为序访问大量文件会获得更好的 IO 效率。
压缩
大家都曾使用过 zip,winrar 等压缩软件,将一个大文件进行压缩可以有效节约磁盘空间。 Btrfs 内置了压缩功能。
通常人们认为将数据写入磁盘之前进行压缩会占用很多的 CPU 计算时间,必然降低文件系统的读写效率。但随着硬件技术的发展,CPU 处理时间和磁盘 IO 时间的差距不断加大。在某些情况下,花费一定的 CPU 时间和一些内存,但却能大大节约磁盘 IO 的数量,这反而能够增加整体的效率。
比如一个文件不经过压缩的情况下需要 100 次磁盘 IO 。但花费少量 CPU 时间进行压缩后,只需要 10 次磁盘 IO 就可以将压缩后的文件写入磁盘。在这种情况下,IO 效率反而提高了。当然,这取决于压缩率。目前 btrfs 采用 zlib 提供的 DEFALTE/INFLATE 算法进行压缩和解压。在将来,btrfs 应该可以支持更多的压缩算法,满足不同用户的不同需求。
目前 btrfs 的压缩特性还存在一些不足,当压缩使能后,整个文件系统下的所有文件都将被压缩,但用户可能需要更细粒度的控制,比如针对不同的目录采用不同的压缩算法,或者禁止压缩。我相信,btrfs 开发团队将在今后的版本中解决这个问题。
对于某些类型的文件,比如 jpeg 文件,已经无法再进行压缩。尝试对其压缩将纯粹浪费 CPU 。为此,当对某文件的若干个 block 压缩后发现压缩率不佳,btrfs 将不会再对文件的其余部分进行压缩操作。这个特性在某种程度上提高了文件系统的 IO 效率。
预分配
很多应用程序有预先分配磁盘空间的需要。他们可以通过 posix_fallocate 接口告诉文件系统在磁盘上预留一部分空间,但暂时并不写入数据。如果底层文件系统不支持 fallocate,那么应用程序只有使用 write 预先写一些无用信息以便为自己预留足够的磁盘空间。
由文件系统来支持预留空间更加有效,而且能够减少磁盘碎片,因为所有的空间都是一次分配,因而更有可能使用连续的空间。 Btrfs 支持 posix_fallocate 。
总结
至此,我们对 btrfs 的很多特性进行了较为详细的探讨,但 btrfs 能提供的特性却并不止这些。 btrfs 正处于试验开发阶段,还将有更多的特性。
Btrfs 也有一个重要的缺点,当 BTree 中某个节点出现错误时,文件系统将失去该节点之下的所有的文件信息。而 ext2/3 却避免了这种被称为”错误扩散”的问题。
但无论怎样,希望您和我一样,开始认同 btrfs 将是 Linux 未来最有希望的文件系统。
回页首
BTRFS 使用简介
了解了 btrfs 的特性,想必您一定想亲身体验一下 btrfs 的使用。本章将简要介绍如何使用 btrfs 。
创建文件系统
mkfs.btrfs 命令建立一个 btrfs 格式的文件系统。可以用如下命令在设备 sda5 上建立一个 btrfs 文件系统,并将其挂载到 /btrfsdisk 目录下:
#mkfs.btrfs /dev/sda5
#mkdir /btrfsdisk
#mount – t btrfs /dev/sda5 /btrfsdisk
这样一个 Btrfs 就在设备 sda5 上建立好了。值得一提的是在这种缺省情况下,即使只有一个设备,Btrfs 也会对 metadata 进行冗余保护。如果有多个设备,那么您可以在创建文件系统的时候进行 RAID 设置。详细信息请参见后续的介绍。
这里介绍其他几个 mkfs.btrfs 的参数。
Nodesize 和 leafsize 用来设定 btrfs 内部 BTree 节点的大小,缺省为一个 page 大小。但用户也可以使用更大的节点,以便增加 fanout,减小树的高度,当然这只适合非常大的文件系统。
Alloc-start 参数用来指定文件系统在磁盘设备上的起始地址。这使得用户可以方便的预留磁盘前面的一些特殊空间。
Byte-count 参数设定文件系统的大小,用户可以只使用设备的一部分空间,当空间不足时再增加文件系统大小。
修改文件系统的大小
当文件系统建立好之后,您可以修改文件系统的大小。 /dev/sda5 挂载到了 /btrfsdisk 下,大小为 800M 。假如您希望只使用其中的 500M,则需要减小当前文件系统的大小,这可以通过如下命令实现:
#df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 101086 19000 76867 20% /boot
/dev/sda5 811248 32 811216 1% /btrfsdisk
#btrfsctl – r -300M /btrfsdisk
#df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 101086 19000 76867 20% /boot
/dev/sda5 504148 32 504106 1% /btrfsdisk
同样的,您可以使用 btrfsctl 命令增加文件系统的大小。
创建 Snapshot
下面的例子中,创建快照 snap1 时系统存在 2 个文件。创建快照之后,对 test1 的内容进行修改。再回到 snap1,打开 test1 文件,可以看到 test1 的内容依旧是之前的内容。
#ls /btrfsdisk
test1 test2
#vi test1
This is a test
#btrfsctl – s snap1 /btrfsdisk
#vi test1
Test1 is modified
#cd /btrfsdisk/snap1
#cat test1
This is a test
可以从上面的例子看到,快照 snap1 保存的内容不会被后续的写操作所改变。
创建 subvolume
使用 btrfs 命令,用户可以方便的建立 subvolume 。假设 /btrfsdisk 已经挂载到了 btrfs 文件系统,则用户可以在这个文件系统内创建新的 subvolume 。比如建立一个 /sub1 的 subvolume,并将 sub1 挂载到 /mnt/test 下:
#mkdir /mnt/test
#btrfsctl – S sub1 /btrfsdisk
#mount – t btrfs – o subvol=sub1 /dev/sda5 /mnt/test
Subvolme 可以方便管理员在文件系统上创建不同用途的子文件系统,并对其进行一些特殊的配置,比如有些目录下的文件关注节约磁盘空间,因此需要打开压缩,或者配置不同的 RAID 策略等。目前 btrfs 尚处于开发阶段,创建的 subvolme 和 snapshot 还无法删除。此外针对 subvolume 的磁盘 quota 功能也未能实现。但随着 btrfs 的不断成熟,这些功能必然将会进一步完善。
创建 RAID
mkfs 的时候,可以指定多个设备,并配置 RAID 。下面的命令演示了如何使用 mkfs.btrfs 配置 RAID1 。 Sda6 和 sda7 可以配置为 RAID1,即 mirror 。用户可以选择将数据配置为 RAID1,也可以选择将元数据配置为 RAID1 。
将数据配置为 RAID1,可以使用 mkfs.btrfs 的 -d 参数。如下所示:
#mkfs.btrfs – d raid1 /dev/sda6 /dev/sda7
#mount – t btrfs /dev/sda6 /btrfsdisk
添加新设备
当设备的空间快被使用完的时候,用户可以使用 btrfs-vol 命令为文件系统添加新的磁盘设备,从而增加存储空间。下面的命令向 /btrfsdisk 文件系统增加一个设备 /sda8
#btrfs-vol – a /dev/sda8 /btrfsdisk
SSD 支持
用户可以使用 mount 参数打开 btrfs 针对 SSD 的优化。命令如下:
#mount – t btrfs – o SSD /dev/sda5 /btrfsdisk
开启压缩功能
用户可以使用 mount 参数打开压缩功能。命令如下:
#mount – t btrfs – o compress /dev/sda5 /btrfsdisk
同步文件系统
为了提高效率,btrfs 的 IO 操作由一些内核线程异步处理。这使得用户对文件的操作并不会立即反应到磁盘上。您可以做一个实验,在 btrfs 上创建一个文件后,稍等 5 到 10 秒将系统电源切断,再次重启后,新建的文件并没有出现。
对于多数应用这并不是问题,但有些时候用户希望 IO 操作立即执行,此时就需要对文件系统进行同步。下面的 btrfs 命令用来同步文件系统:
#btrfsctl – c /btrfsdisk
Debug 功能
Btrfs 提供了一定的 debug 功能,对于想了解 Btrfs 内部实现原理的读者,debug 将是您最喜欢的工具。这里简单介绍一下 debug 功能的命令使用。
下面的命令将设备 sda5 上的 btrfs 文件系统中的元数据打印到屏幕上。
#btrfs-debug-tree /dev/sda5
通过对打印信息的分析,您将能了解 btrfs 内部各个 BTree 的变化情况,从而进一步理解每一个文件系统功能的内部实现细节。
比如您可以在创建一个文件之前将 BTree 的内容打印出来,创建文件后再次打印。通过比较两次的不同来了解 btrfs 创建一个文件需要修改哪些元数据。进而理解 btrfs 内部的工作原理。
回页首
结束语
我想,在未来的日子里,EXT 等传统文件系统肯定不会消失。古典文件系统成熟,稳定,经受了历史的考验,必然将在很长一段时间内被继续广泛使用。古典文件系统和新一代文件系统同时并存是非常合理的事情。正如我们在被陈亦迅的歌声触动和感伤之后,还可以在莫扎特的奏鸣曲中得到慰藉和平静。
本人水平有限,对文件系统的了解也并不深入,因此文中必然有很多错误的地方,希望能得到大家的指正。
参考资料
On file system 是一篇非常不错的关于 Linux 文件系统的文章。
btrfs wiki 也是不错的参考资料。
在 developerWorks Linux 专区 寻找为 Linux 开发人员(包括 Linux 新手入门)准备的更多参考资料,查阅我们 最受欢迎的文章和教程。
在 developerWorks 上查阅所有 Linux 技巧 和 Linux 教程。
关于作者
刘明,从事嵌入式软件开发,热爱开源软件。喜欢学习和使用 linux,目前致力于数据库方面的工作和研究
wordpress 作为cms的短板
信息的趋势:
1 动态信息和外部交互(wp需要插件扩展,但是业务流程的扩展在api支持下较为欠缺)
2 系统不支持多语言(这个问题使其只能是blog,不能作为cms,即使是multi-blog也无法解决内容冗余问题)
3 系统的流程部分太呆板,无法流程定制。网站的业务逻辑有限
4 系统的后台围绕内容,没有有效分离内容的各个职能, 很难定义不同内容
symfony2 框架更新
这个框架在ror的时代活到现在,可见生命力旺盛, 但是框架和应用是需要互相呼应的,不然系统的框架再好也没法得到体现。
http://symfony.com
GA 事件跟踪指南
设置事件跟踪
您需要按照以下步骤在网站上设置跟踪功能,然后才能在报告中查看事件跟踪结果。
- 在您的网站上设置跟踪功能。确保您已为网站设置好了跟踪功能。要了解具体设置方法,请参阅跟踪网站。
- 在网页对象、窗口小部件或视频的源代码中调用
_trackEvent()方法。
_trackEvent()方法的规范为:_trackEvent(category, action, opt_label, opt_value)
- 类别(必填)为要跟踪的对象组指定的名称。
- 操作(必填)与每个类别具有唯一对应关系的字符串,通常用于为网页对象定义用户互动的类型。
- 标签(可选)为事件数据提供额外维度的可选字符串。
- 值(可选)可以用来提供有关用户事件的数值数据的整数。
- 查看报告。在事件跟踪设置完毕并在您的网站上运行一天之后,请转到报告的“内容” 部分并查看“事件跟踪” 。
事件跟踪详解
事件跟踪数据模型由以下几部分组成,这些组成部分与 Google Analytics(分析)报告界面中的相应元素一一对应:
- 类别
- 操作
- 标签
- 值
- 隐含计数
以下是一个简单的例子,解释了如何使用事件跟踪方法来记录用户与您网页上的视频“播放”链接之间的互动。 其中假设 pageTracker 是您的跟踪对象的名称。
<a href="#" onClick="_gaq.push(['_trackEvent', 'Videos', 'Play', 'Baby\'s First Birthday']);">Play</a>
在这个例子中,事件的报告会将 Videos 显示为“类别”,将 Play 显示为“操作”,将 Baby’s First Birthday 显示为“标签”。下文将会详细说明这些组成部分。请注意,在实施事件跟踪时,您可以使用此数据模型作为指南,也可以直接使用 _trackEvent() 方法,以适用于您的数据的任意方式来细分用户互动。
类别
对于事件跟踪,“类别” 是您指定的用于分组要跟踪的对象的方式。它是 _trackEvent() 方法中使用的第一个参数,并且是必填参数。
“类别” 一词在报告界面里位于“事件概览”页上,名为“热门类别”。在此模型中,类别代表事件跟踪层级结构的“根级别”,您可以采用适合您的报告需要的任意方式使用该结构。一般来说,对于您想要归入指定类别的相关用户界面元素,您会多次用到同一类别名称。
例如,要跟踪同一个视频界面上的三个单独控件的用户互动情况,您可以使用以下函数:
_gaq.push(['_trackEvent', 'Videos', 'Play', 'Gone With the Wind']); _gaq.push(['_trackEvent', 'Videos', 'Pause', 'Gone With the Wind']); _gaq.push(['_trackEvent', 'Videos', 'Stop', 'Gone With the Wind']);
假设您还想要跟踪视频的下载次数。您可以使用:
gaq.push(['_trackEvent', 'Videos', 'Downloaded', 'Gone With the Wind']);
在这个例子中,“事件概览”页上的“热门类别”中将只有一个类别(即“Videos”),并且您会看到有关该视频对象整个元素集用户互动情况的汇总指标。
不过,通常您想要通过事件跟踪功能跟踪的对象很可能不止一个,因此在实施调用前最好先仔细考虑一下如何分类报告。例如,您可能希望跟踪“Videos”主类别下的所有不同影片,这样您就可以获得有关所有视频互动的汇总数据,无论用户实际观看的是哪部视频。
另一方面,您可以根据视频的类型创建单独的类别。例如,影片视频和音乐视频各占一个类别。您可能还希望为视频下载单独创建一个类别:
- Videos – Movies
- Videos – Music
- Downloads
在这个例子中,您可以通过“事件总数”标签确定所有三个类别的总计事件数。“事件总数”指标显示的是您在事件跟踪实施中指定的所有类别的事件总数。不过,您无法在“Downloads”中单独查看所有视频的汇总指标,因为详细事件指标汇总在其各自所属的类别之下。
尽管事件跟踪对象模型非常灵活,您还是应该先规划好所需的报告结构,然后再为类似网页对象元素调用 _trackEvent() 方法。如果您打算在多个位置使用相同的类别名称,请务必按名称正确引用所需的类别。例如,如果您打算调用视频跟踪类别“Video”,而后来又因为记错而使用了复数的“Videos”,其结果将是针对视频跟踪产生两个单独的类别。另外,如果您决定更改某个对象的类别名称,而该对象已通过不同的名称进行了跟踪,则原类别的历史数据将不会得到重新处理,而这会导致同一个网页元素的指标列于报告界面中的两个类别之下。
操作
事件跟踪模型中的“操作”一词指的是 _trackEvent() 方法中的第二个参数。它也是必填参数。
_gaq.push(['_trackEvent', 'Videos', 'Play', 'Gone With the Wind']);
一般来说,您会使用 action 参数来命名您希望针对特定网页对象跟踪的事件或互动的类型。例如,在一个“Videos”类别中,您可以使用此参数跟踪多个特定事件,如:
- 视频完成载入的时间
- “播放”按钮点击次数
- “停止”按钮点击次数
- “暂停”按钮点击次数
与类别一样,为 action 指定怎样的名称完全取决于您自己,不过,对于事件操作在报告中的使用方式,有两个要点需要注意:
- 所有操作均独立于其母类别单独列出。
而这一点正好可以在您为报告细分事件数据时派上用场。 - 唯一身份事件取决于唯一身份操作名称。
您可以在不同类别中使用重复的操作名称,但这可能会影响唯一身份事件的计算方式。有关详情,请参阅下文的建议以及隐含计数部分。
要确保在针对网站大范围实施事件跟踪时的报告效果,请在使用操作时注意以下几点:
- 操作名称应与您的报告数据相关事件跟踪会将两个不同类别中具有相同操作名称的指标合并在一起。例如,如果您将操作名称“Click”同时用于“Downloads”类别和“Videos”类别,那么“热门操作”报告中有关“Click”的指标会同时包含使用该名称标记的所有互动。您可以在下一报告级别中看到按类别细分的“点击”操作的详细信息。不过,如果您在整个事件跟踪实施中不加区分地使用“click”操作,则报告中该细分的实用性就会大打折扣。如果您准备在网站上大规模使用事件跟踪,那么您最好选择与您的数据类别相关的操作名称。例如,您可以选择为小工具互动保留“click”一词,而为视频播放器互动保留“Play”、“Pause”、“Stop”等操作名称。
- 以全局为背景使用操作名称以便汇总或区分用户互动例如,您可以为针对网站上所有视频的“Videos”类别使用“Play”作为操作名称。在此模型中,“热门操作”报告会为“Play”操作的事件提供汇总数据,而且您可以了解有关您的视频的这一事件与其他事件(如“暂停”或“停止”)的对比情况。
不过,假设您想只在报告中使用一个视频类别,但是又想了解有关两个不同的视频播放器用户界面的信息。您可以使用操作名称来区分不同的视频播放器,而不必创建单独的视频类别。这样,报告就可以区分两个播放器的风格,同时还能汇总有关网站上所有视频的数据。
_gaq.push(['_trackEvent', 'Videos', 'Play - Mac Chrome'); _gaq.push(['_trackEvent', 'Videos', 'Play - Windows Chrome');
- 操作并不总是意味着“操作”。您可以为操作参数指定任意字符串。某些情况下,实际的事件或操作名称可能并不实用,此时您可以使用操作参数来跟踪其他元素。例如,如果您想要跟踪网页的下载情况,您可以将文档的文件类型指定为下载事件的操作参数。在这个例子中,有关“Downloads”类别的报告数据就会按照文件类型(pdf、doc、xls 等)来划分。
- 唯一身份事件数的累加取决于唯一身份操作数每当有用户与标为特定操作名称的对象进行互动时,其首次互动均会被记录为该操作名称的一次“唯一身份事件”。 由该用户的会话触发的同一操作的其他任何互动均不会计入该特定操作的唯一身份事件。即使该用户离开相应对象,然后开始与标为相同操作名称的其他对象进行互动,情况也是如此。
这会对报告的结果产生两个显著的影响。首先,假设某用户与两个类别各异的不同视频播放器的“Play”操作进行了互动。有关“Play”的“热门操作”报告只会列出一个唯一身份事件,虽然该用户实际上与两个不同的播放器进行了互动。 其次,每个类别的“操作” 报告会列出一个唯一身份操作,因为每个类别/操作配对确实有一个唯一身份事件。要了解详情,请参阅隐含计数部分。
标签
事件跟踪数据模型中的“标签”一词指的是 _trackEvent() 方法中的第三个字符串参数。此参数是可选参数。通过标签,您可以为要跟踪的事件提供额外信息,例如以上示例中的影片标题,或是跟踪下载情况时的文件名称。
pageTracker._trackEvent('Downloads', 'PDF', '/salesForms/orderForm1.pdf');
与“类别”和“操作”一样,报告界面中有单独的部分来显示您为事件跟踪创建的所有标签。您可以将标签看作创建针对用户与网页对象之间互动的额外报告维度的方法。例如,假设您的网页上有 5 个视频播放器需要跟踪用户与之的互动情况。这些视频播放器中的每一个都可以使用“Videos”类别和“Play”操作,但是每个播放器又可以使用单独的标签(比如影片名称),这样它们就可以在报告中显示为不同的元素。
_gaq.push(['_trackEvent', 'Videos', 'Play', 'Gone With the Wind']); _gaq.push(['_trackEvent', 'Videos', 'Play', 'Huckleberry Finn']);
值
“值”一词指的是 _trackEvent() 方法中使用的第四个参数,它是一个可选的参数。此参数与其他参数不同,因为它属于 integer 类型,而不是 string 类型,因此您可以使用它来为所跟踪的网页对象指定数字值。例如,您可以通过它来指定以秒计时的播放器载入时间,还可以在视频播放器播放到特定位置时触发一个美元价值。
_gaq.push(['_trackEvent', 'Videos', 'Video Load Time', 'Gone With the Wind', downloadTime]);
该值会按数字识别,并且报告会根据每个事件计数(请参阅下文的隐含计数)累加出总计值。报告还会确定相应类别的平均值。在上述示例中,_trackEvent()方法会在视频载入完成时针对“Video Load Time”操作进行调用。视频的名称作为标记提供,算出的载入时间会针对各视频下载进行累加。这样,您就可以确定“Videos”类别所有“Video Load Time”操作的平均载入时间。假设您网站上的视频获得了 5 次唯一下载,其下载时间(以秒计算)为:
- 10
- 25
- 8
- 5
- 5
报告界面会进行如下计算,该示例中的数字表示的是以秒计算的下载时间:
| 带来事件的访问 | 值 | 平均值 |
|---|---|---|
| 5 | 53 | 10.6 |
目前此参数还不支持负整数。
隐含计数
在事件跟踪中,每一次与所跟踪的网页对象之间的互动都会得到记录,并且每次互动都会与特定用户会话相关联。在报告中,“事件总数”计算的是与某所跟踪网页对象之间的互动总数。另一方面,当某一用户会话(即访问)涉及一个或多个事件时,此指标就会计算为报告中的单个“带来事件的访问”或者“唯一身份事件”。
比如说,如果一个用户对某视频上的同一按钮点击了 5 次,那么与该视频相关联的事件总数为 5,唯一身份事件数为 1。
以下表格解释了报告界面中某一指定事件类别的数据如何汇总。在该示例中,同一类别名称调用自两个不同的视频播放器,两个播放器各自具有不同的标签。这两个播放器共享“Play”和“Stop”操作,Flash 用户界面针对视频播放器采用的就是这一编程方式。
| 操作类型 | 标签:“Gone With the Wind” | 标签:“Mr Smith Goes to Washington” | 总计 |
|---|---|---|---|
| Play | 10 次带来事件的访问 | 5 次带来事件的访问 | 15 次唯一身份事件“播放” |
| Pause | 2 次带来事件的访问 | 8 次带来事件的访问 | 10 次唯一身份事件“暂停” |
| Stop | 2 次带来事件的访问 | 3 次带来事件的访问 | 5 次唯一身份事件“停止” |
| 总计 | GWTW 获得了 14 次唯一身份事件 | Mr Smith 获得了 16 次唯一身份事件 | 类别“videos”获得了 30 次唯一身份事件 |
上方的表格假设用户与“Gone With the Wind”和“Mr Smith Goes to Washington”之间的互动各自发生在单独的用户会话(访问)中。不过,下方的表格显示了如何在更为复杂和典型的情况下计算指定操作的事件次数,即,一些用户只在一个视频中按了“播放”,而其他用户则在一次访问期间与多个视频进行了互动。在这里,唯一身份事件总数反映了所有标签的“Play”操作的唯一身份事件总数。请注意,尽管每个标签/操作配对都对应有 17 次唯一身份事件,但报告中只列出相关维度的唯一身份事件次数。因此,对于所有操作,仅有 16 次唯一身份事件,对于“Videos”类别,总共有 16 次访问,其中包含类别为“Videos”的一个事件。
| 操作类型 | 标签:“Gone With the Wind” | 标签:“Mr Smith Goes to Washington” | 总计 |
|---|---|---|---|
| Play | 10 次带来事件的访问 | 10 次唯一身份事件“播放” | |
| Play | 5 次带来事件的访问 | 5 次唯一身份事件“播放” | |
| Play | 两部影片均有 1 次带来事件的访问(对“播放”的两次点击) | 1 次唯一身份事件“播放” | |
| 总计 | GWTW 获得了 11 次唯一身份播放事件 | Mr Smith 获得了 6 次唯一身份播放事件 | 类别“Videos”获得了 16 次唯一身份事件,操作“Play”获得了 16 次唯一身份事件 |
实施注意事项
在为网站实施事件跟踪时,请注意以下几点。
跳出率的影响
一般来说,“跳出”是指用户只在您的网站上访问了一个网页。在 Google Analytics(分析)中,“跳出”特指仅触发了一次 GIF 请求的会话,例如,用户访问了您网站上的一个网页,然后就退出了网站,并且这次访问没有向 Google Analytics(分析)服务器发出有关这次会话的任何其他请求。不过,如果您为网站实施了事件跟踪,您可能会注意到实施了事件跟踪的网页的跳出率指标有所不同。这是因为事件跟踪像网页跟踪一样被归类为互动请求。
例如,假设您的一个包含视频播放器的网页的跳出率一直很高,并且您没有为该网页实施事件跟踪。如果您随后为该播放器设置了事件跟踪,那么您可能会注意到该网页的跳出率有所降低,这是因为 Google Analytics(分析)会记录用户与播放器的互动,并将互动作为额外的 GIF 请求发送给服务器。因此,即使在访问该网页的访问者中,仍有相同比例的访问者未查看您网站上的任何其他网页就退出网站,他们与视频播放器之间的互动同样会触发事件跟踪调用,这样一来,他们的访问就不能算作跳出。
在这种计算方法中,对于实施了事件跟踪的网页,“跳出”所指的意义稍有不同:只访问一个页面,并且在该访问中,用户与跟踪的事件之间未发生任何互动。
请您务必注意,任何在网页载入时自动执行的事件跟踪都会导致该网页的零跳出率。如果您实施了 TimeTracker 示例或任何类似的事件跟踪功能,就会出现这种情况。
每次会话事件数限制
对于每次访问(用户会话),最多只能跟踪大概 500 个综合 GATC 请求(事件和浏览量)。在您通过编程方式生成事件时,请注意这一数量限制。另请注意:当一次会话中的事件数量接近上限时,您可能不能再跟踪更多事件。例如,您应该:
- 避免将视频的脚本处理为每播放一秒就发送一次事件,以及其他重复次数过多的事件触发因素
- 避免使用过多的鼠标移动跟踪
- 避免使用可生成较高的事件次数的定时机制
GA 电子商务跟踪 (后续需要进一步和event整合按钮响应)
电子商务跟踪
在报告您网站的电子商务活动之前,Google Analytics(分析)需要您先在网站的配置文件设置页启用电子商务跟踪。然后,您还必须在购物车页或通过电子商务软件实施 ga.js 电子商务跟踪方法。会有一整套电子商务方法协同运作,将有关每个用户所发生交易的信息发送到 Google Analytics(分析)数据库。采用这种方式,Google Analytics(分析)即可将特定引荐来源关联到转化或购买操作。大多数由模板驱动的电子商务引擎都可以进行修改,以便收集隐藏在订单确认页中的信息。
一般过程
对以下三个用于跟踪您网站上的电子商务交易的方法进行总结,即可最好地说明使用 Google Analytics(分析)跟踪电子商务的基本过程。对这些方法的说明顺序即是您应该在购物车或电子商务软件中调用它们的顺序。
- 创建交易对象。使用
_addTrans()方法初始化交易对象。该交易对象存储与单个交易相关的所有信息,如订单 ID、运费和帐单邮寄地址。交易对象中的信息是采用订单 ID 的方法与其项目相关联,交易及其所有项目的 ID 都应该是相同的。 - 将项目添加到交易。
_addItem()方法跟踪有关用户购物车中各个项目的信息,并通过orderId字段将项目与各个交易关联起来。此字段会跟踪有关各个项目的详情,如 SKU、价格、类别和数量等。 - 将交易提交到 Google Analytics(分析)服务器。
_trackTrans()方法确认已经发生了购买操作,且交易对象中积累的所有数据已作为交易完结。
有多种方式可从电子商务引擎获取此项信息。有些电子商务引擎会将购买信息写入可供您使用的隐藏表单,有些则将此信息保留在可供您检索的数据库中,其他电子商务引擎则将此信息存储在 Cookie 中。某些认可 Google Analytics(分析)的更为热门的电子商务引擎会提供自己的模块以便简化 Google Analytics(分析)的订单跟踪过程。
指南
当您实施电子商务跟踪时,请注意以下几点。
- 对于每个被添加到交易的项目,该 SKU 代码都是必要参数。
如果某个交易包含多个项目,却没有为每个项目提供 SKU,则只会向已添加到交易且提供了 SKU 的最后一个项目发送 GIF 请求。另外,如果您的库存中的两种不同的物品具有相同的 SKU,并且某访问者同时购买了这两种物品,则您将只会获得与最近添加的物品有关的数据。因此,您应该确认自己提供的每个项目都有唯一的 SKU。 _addTrans()和_addItem()的参数列表按位置进行匹配。
虽然不是所有的参数都是必需的,对于未指定的参数,您还是应该提供空的占位符,以避免错误发生。例如,如下所示,您要添加只包含订单 ID、SKU、价格和数量的项目:_addItem("54321", "12345", "", "", "55.95", "1");price和total参数的值必须是整数,且不附带任何币种值。
对于这些参数,值中的逗号或句号都表示小数值。例如,如果您以1,996.00作为total的值,该值将记录为1.996,而不是 $1,996.00;这是因为此参数的格式是简单整数格式,逗号和其他币种指示符都不会认可。此外,因为这些值都作为整数记录,所以也不会与任何币种相关联。因此,您必须先通过您的电子商务软件处理所有币种转换,然后再将数据传递到 Google Analytics(分析)。- 如果您实施电子商务跟踪,并使用第 3 方购物车,您可能还需要配置跨域跟踪。
详细信息,请参阅“跨域跟踪”一节。 - 虽然不是严格要求,但如果您希望将特定页面与交易数据相关联,最好还是在收据页中调用
_trackPageview()。
完整示例
以下示例说明了在收据页上使用所有三种方法的电子商务跟踪配置:使用 _trackPageview() 将交易与“Acme Clothing 服装购买收据”页关联起来。
异步语法(推荐)
<html> <head> <title>Receipt for your clothing purchase from Acme Clothing</title> <script type="text/javascript"> var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-XXXXX-X']); _gaq.push(['_trackPageview']); _gaq.push(['_addTrans', '1234', // order ID - required 'Acme Clothing', // affiliation or store name '11.99', // total - required '1.29', // tax '5', // shipping 'San Jose', // city 'California', // state or province 'USA' // country ]); // add item might be called for every item in the shopping cart // where your ecommerce engine loops through each item in the cart and // prints out _addItem for each _gaq.push(['_addItem', '1234', // order ID - required 'DD44', // SKU/code - required 'T-Shirt', // product name 'Green Medium', // category or variation '11.99', // unit price - required '1' // quantity - required ]); _gaq.push(['_trackTrans']); //submits transaction to the Analytics servers (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script> </head> <body> Thank you for your order. You will receive an email containing all your order details. </body> </html>
传统语法
<html>
<head>
<title>Receipt for your clothing purchase from Acme Clothing</title>
</head>
<body>
Thank you for your order. You will receive an email containing all your order details.
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol ) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try{
var pageTracker = _gat._getTracker("UA-xxxxx-x");
pageTracker._trackPageview();
pageTracker._addTrans(
"1234", // order ID - required
"Womens Apparel", // affiliation or store name
"11.99", // total - required
"1.29", // tax
"15.00", // shipping
"San Jose", // city
"California", // state or province
"USA" // country
);
// add item might be called for every item in the shopping cart
// where your ecommerce engine loops through each item in the cart and
// prints out _addItem for each
pageTracker._addItem(
"1234", // order ID - necessary to associate item with transaction
"DD44", // SKU/code - required
"T-Shirt", // product name
"Olive Medium", // category or variation
"11.99", // unit price - required
"1" // quantity - required
);
pageTracker._trackTrans(); //submits transaction to the Analytics servers
} catch(err) {}
</script>
</body>
</html>
