tag:blogger.com,1999:blog-12349538372838322822026-01-11T06:10:59.631-08:00Still trying to get it all outSara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.comBlogger36125tag:blogger.com,1999:blog-1234953837283832282.post-62593667958263315482016-05-10T12:50:00.002-07:002016-05-10T12:50:22.993-07:00Comments DisabledSomewhere around the beginning of this year, Google suffered a traumatic brain injury and forgot how to stop spam on Blogger. Probably forgot the product existed entirely, knowing Google. So I've had to disable comments completely. Feel free to reach out to me on Twitter @<a href="http://twitter.com/SaraMG">SaraMG</a>.Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-65299273155580884382016-05-02T12:57:00.000-07:002016-05-02T12:59:45.320-07:00Dario Blood Glucose Monitoring SystemLast week, my wife found a <a href="http://intro.mydario.com/DarioGlucoseMeter">new glucose meter</a> via reddit or something, and it was only $40 so it sounded like it was worth giving a shot. It just arrived today, so I figured I'd share my initial thoughts below: <h3>Ordering</h3> My first annoyance was with buying the product. While the intro page tells you all about the meter and how much it costs, the pricing on strips was suspiciously absent. After a few minutes of clicking around the site, I finally tried hitting the [BUY] button, and was treated to actual numbers. In fairness, that could have been my first guess, but I'm easily annoyed. <br/><br/> After placing my order, I was given a stark white page saying "Your order has been placed." No order number, no tracking info, no estimated delivery date. Just a brief affirmation. Okay, whatevs, it'll come when it comes. <h3>Unboxing</h3> And it did come after only a few days, so point for promptness. The meter is just what it says on the tin, a compact housing for test strips, lancet device, and the small square dongle used for actually reading a strip and pumping it into one's phone. <br/><br/> One small quirk did pop up fairly early. The instructions indicate that the test strip cartridge should "snap" into the casing. It doesn't. In fact, when removing the outer cap, it pulls the cartridge out of the casing, then I have to pry the cap off the cartridge, so I can open the cartidge, and fish out the strip. For a device which hinges on convenience and ease of use, this isn't convenient or easy to use. Hopefully this is just an early model engineering defect and not indicative of the poor quality of the device overall. <h3>Using the app</h3> Unsurprisingly, the app wants you to register/login to their site. That's not unexpected. What surprised me here is that the account I created last week (while buying the device) wasn't usable for the actual app. Maybe that's a good thing, if the store is run by a third-party, principle of least privileges and all. But it's certainly inconvenient from a user standpoint. <br/><br/> But finally, I'm ready to take a reading... <br/><br/> <div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieSC30rFH5owN0HLwm2TbdjptO_M-zHA7mQKCtw3GsOMiyW25HERk4Wj4auG9KYxNpBgItNxLpzfRG04kEbdbn3VXrBgKfRXYsgXhiArVSBI5lK6kKzpnSXlAVCTQbGQ_gK9jRL14QeTUy/s1600/dario-fit.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieSC30rFH5owN0HLwm2TbdjptO_M-zHA7mQKCtw3GsOMiyW25HERk4Wj4auG9KYxNpBgItNxLpzfRG04kEbdbn3VXrBgKfRXYsgXhiArVSBI5lK6kKzpnSXlAVCTQbGQ_gK9jRL14QeTUy/s400/dario-fit.jpg" /></a><br/>Fuuuuuuuudge...</div> Now, don't judge me on the hipster wood case, I'm very clumsy and it helps me not break phones. I used to have plastic/vinyl wallet cases, but they fall apart in a matter of weeks, and... look, my point is, just a SLIGHTLY longer neck would have made this a lot more usable. Square knows this problem exists, which is why their 2nd gen readers don't match their 1st gen readers. <br/><br/> But it works well enough, the data matches my other meter within tolerance... Wait, no... one more gripe: The app doesn't talk to iOS's health data stream. My current go-to logbook app, <a href="https://mysugr.com/">MySugr</a> (highly recommend, btw) is able to share data with my phone's OS, which means my apps can work better together. <h3>Conclusion</h3> Not a terrible little device, I'll probably keep it in my hoodie-of-many-pockets for those random occasions where I used to use OneTouch minis, but it's not quite ready for me yet.Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-72695117404989168272015-01-08T12:36:00.002-08:002015-01-08T13:21:25.118-08:00HHVM Extension Writing, Part IVHopefully you've had some time to digest the first three parts of my HHVM Extension Writing series, and you're ready to embark into the world of Resources. Not quite as exciting as objects, they're certainly less commonly used in new extensions since Objects are so much more versatile, but there were an integral part of PHP's history prior to PHP5, and are still in heavy use by such features as streams, curl, and most database connectors.<br/> <br/> We'll be continuing with the same examples repo at <a href="https://github.com/sgolemon/hhvm-extension-writing">https://github.com/sgolemon/hhvm-extension-writing</a> where I've already landed a skeleton for <i>example3</i> with commit <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/144e9698b5acb8245882aa60b39921d81b090702">144e9698b5</a>.<br/> <br/> <h3>Resource</h3> Defining a new resource type shares some similarity with Objects, except that you won't be defining anything in systemlib initially, because a resource doesn't have an API in and of itself. It's just an opaque pointer used by seemingly unrelated functions and methods. We'll define some global functions in the second half of this post when we start accessing the resource. Any wonder Objects are winning out?<br/> <br/> As with the Object example, we'll be implementing a bit of filesystem access to demonstrate how one might use resources.<br/> <blockquote style="background-color: #DDDDDD;"><pre>class Example3File : public SweepableResourceData { public: DECLARE_RESOURCE_ALLOCATION_NO_SWEEP(Example3File) CLASSNAME_IS("example3-file") const String& o_getClassNameHook() const override { return classnameof(); } Example3File(const String& filename, const String& mode) { m_file = fopen(filename.c_str(), mode.c_str()); if (!m_file) { throw Object(SystemLib::AllocExceptionObject( "Unable to open file" )); } } ~Example3File() { sweep(); } void sweep() { close(); } void close() { if (m_file) { fclose(m_file); m_file = nullptr; } } bool isInvalid() const override { return !m_file; } FILE* m_file{nullptr}; };</pre></blockquote><br/> The first three lines of our class are basically boilerplate. <i>DECLARE_RESOURCE_ALLOCATION_NO_SWEEP</i> handles some specifics about the MemoryManager, because unlike objects which are allocated from userspace, resources are allocated from C++, and a bare c++ new won't do a "smart" allocation unless told to do so by this macro. The <i>CLASSNAME_IS</i> macro, and the <i>o_getClassNameHook()</i> virtual below it, define the "resource name" as seen from PHP when you <i>var_dump()</i> or call <i>get_resource_type</i>.<br/> <br/> As with the Object version of this example, the class destructor is automatically called when the resource variable falls out of scope, meanwhile <i>sweep()</i> is invoked if the variable is still "live" at the end of a request. Since in this case we want the same behavior to occur, we simple chain one through the other, and on to their actual purpose; Closing the file.<br/> <br/> <i>isInvalid()</i> exists for Resource types because it's very common to invoke functions like <i>fclose()</i> who's purpose is to make the resource no longer usable as its normal type. HHVM's mechanism for dealing with this is very different from PHP's, but the end result is the same. So long as your class has some way of knowing that it's "dead", HHVM can detect it, and report accordingly in var_dump() and other calls.<br/> <br/> So now that we've defined a Resource type, let's make a function to create instances and do things with them:<br/> <blockquote style="background-color: #DDDDDD;"><pre>Resource HHVM_FUNCTION(example3_fopen, const String& filename, const String& mode) { #ifdef NEWOBJ return Resource(NEWOBJ(Example3File)(filename, mode)); #else return Resource(newres&lt;Example3File&gt;(filename, mode)); #endif } void HHVM_FUNCTION(example3_fclose, const Resource& fp) { // By default, if fp is not of type "Example3File" and valid, // HHVM will throw an exception here auto f = fp.getTyped&lt;Example3File&gt;(); f->close(); } Variant HHVM_FUNCTION(example3_ftell, const Resource& fp) { // By passing "true" for "badTypeOkay", invalid resources // result in returning nullptr, rather than throwing an exception // So check the return type! auto f = fp.getTyped&lt;Example3File&gt;(true /* nullOkay */, true /* badTypeOkay */); if (!f) { raise_warning("Instance of example3-file resource expected"); return init_null(); } return (int64_t)ftell(f-&gt;m_file); }</pre></blockquote><br/> As you can see, allocating a resource is literally as simple as newing up a C++ class with the newres&lt;T&gt;() template (or if you're using an older version of HHVM, the NEWOBJ() macro) and passing it as an argument to Resource's constructor.<br/> <br/> For getting at that C++ instance, I've presented two different, equally valid methods. The first is certainly more concise, but you may not want to throw exceptions (you've probably eschewed making an Object for a reason, after all). On the other hand, while the second form allows you to handle your error cases more explicitly, this particularly common case forced us to change our return type from <i>int</i> to the far less precise <i>mixed</i>. Take this into account when designing your APIs. Many extensions follow the latter pattern, using a simple macro to avoid excessive copypasta from one function to the next. I've added an example of that to the repo.<br/> <br/> <b>What's with the <i>nullOkay</i> arg?</b> TL;DR version: It's a bit of legacy logic and doesn't really have a place in modern HHVM extensions. You can usually set it to the same value as you pass for <i>badTypeOkay</i>. Note that both of these args are <b>false</b> by default.<br/> <br/> <b>Update:</b> In the initial version of this post, I used NEWOBJ() exclusively to allocate a new resource instance, but as <a href="http://twitter.com/maide">@maide</a> pointed out in IRC, that's been removed from the newest versions of HHVM and replaced with <i>newres&lt;T&gt;()</i>. We use an #ifdef to figure out which version we're dealing with because the HHVM team is lousy at updating the API version constant. ;p<br/> <br/> The code so far is at commit <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/5f852d45cc05826b4423fab25e40ee15ffebf11c">5f852d45cc</a>.<br/> <br/> <h3>What's next...</h3> We've covered all the userspace data types, declaration of functions, classes, and resources. Next up, in Part V, we'll back up for a few moments and look at the build system so that we can start linking in external libraries. If you're already familiar with CMake, then you can probably skip that chapter. Part VI is TBD at the moment, but I'll probably pick up a lot of the parts I glossed over in Parts I-IV, we'll see what comes out...Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-527495632811903552015-01-07T15:20:00.000-08:002015-01-07T15:44:20.235-08:00HHVM Extension Writing, Part IIIIn <a href="http://blog.golemon.com/2015/01/hhvm-extension-writing-part-i.html">Part I</a> of this series, we looked at the basic building blocks of a simple HHVM extension skeleton. <a href="http://blog.golemon.com/2015/01/hhvm-extension-writing-part-ii.html">Part II</a> continued with three of the five "smart" datatypes used throughout the API. But now we get to have some fun. It's time to start looking into declaring classes with methods and properties and constants (oh my!).<br/> <br/> I'll be continuing with the same git repository at <a href="https://github.com/sgolemon/hhvm-extension-writing">https://github.com/sgolemon/hhvm-extension-writing</a> where we left off at the end of Part II with commit <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/8e82e3e416be6ca358e496e78927feaba7519647">8e82e3e416</a>.<br/> <br/> <h3>Declaring methods</h3> As with functions, the bulk of your class definition is going to appear in your systemlib file, maybe something like the following in <i>ext_example1.php</i>.<br/> <blockquote style="background-color: #DDDDDD;"><pre>class Example1_Greeter { public function greet() { echo "Hello {$this->name}\n"; } public function __construct(protected string $name = 'Stranger') {} }</pre></blockquote><br/> As you should expect by now, compiling your extension with this bit of code should mean that the <i>Example1_Greeter</i> class is now available to all requests and may be invoked like any other class definition. Let's apply what we already know from making native functions, and see how it works with methods...<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre> &lt;&lt;__Native&gt;&gt; public function getName(): string; &lt;&lt;__Native&gt;&gt; static public function DefaultGreeting(): string; </pre></blockquote><br/> While you're probably tempted to rush off and add an <i>HHVM_FE()</i> and <i>HHVM_FUNCTION()</i> implementation to the C++ file, you'd only be half right. These aren't functions, they're methods, and as such have a different set of macros.<br/> <blockquote style="background-color: #DDDDDD;"><pre>const StaticString s_Example1_Greeter("Example1_Greeter"); String HHVM_METHOD(Example1_Greeter, getName) { return this_->o_get(s_name, false, s_Example1_Greet); } String HHVM_STATIC_METHOD(Example1_Greeter, DefaultGreeting) { return "Hello"; }</pre></blockquote><br/> Meanwhile, in <i>moduleInit()</i>, we'll add:<br/> <blockquote style="background-color: #DDDDDD;"><pre> HHVM_ME(Example1_Greeter, getName); HHVM_STATIC_ME(Example1_Greeter, DefaultGreeting); </pre></blockquote><br/> The code so far is at commit <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/335dca95739d292f0f8bd54ac10e7f315002ab0b">335dca9573</a>.<br/><br/> Similarly, properties and constants may be declared directly on the Hack definition of the class in your systemlib file. I won't bother showing it here, since you all know how to write PHP code, but I'll put an example or two in the git repo.<br/> <br/> What's marginally more interesting, and something you can probably guess at from the coverage in Part I, is that you can declare class constants from C++, meaning that they can take on values defined in external headers or computed values. Let's add one from <i>moduleInit()</i>.<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre> Native::registerClassConstant<KindOfStaticString>(s_Example1_Greeter.get(), s_DEFAULT_GREETING.get(), s_Hello.get());</pre></blockquote><br/> In the examples repo, I've changed <i>-&gt;getName()</i> to now use this constant, so that you can see it propagate up.<br/> <br/> The code so far is at commit <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/007c314b2db47dfcc22c99086382363e31c3b009">007c314b2d</a>.<br/><br/> <h3>Binding internal data</h3> What we have so far is all well and good for simple classes, but most extension classes will need to store some opaque pointer from an external library somewhere on the class that can be easily referenced later on. For that, things start to get a little bit more complicated. To help clarify what we're doing, I'm going to wipe the slate clean by <a href="https://github.com/sgolemon/hhvm-extension-writing/commit/c38527764ac0d02894932df54ba9b22288fa7152">moving <i>example1</i> to its own subdirectory</a>, and <a href="https://github.com/sgolemon/hhvm-extension-writing/commit/20dc4ef8243408b3624e14f50d4357bd511c9988">starting fresh with example2</a>.<br/> <br/> Yeah, kinda messy changing my whole directory structure around midway, but would you rather I ran `git push --force`? Yeah, I thought not.<br/> <br/> The code skeleton we're starting out with is at commit: <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/20dc4ef8243408b3624e14f50d4357bd511c9988">20dc4ef824</a> in <i>example2/</i>.<br/> <br/> To illustrate something slightly less contrived than earlier examples, I'll be wrapping the POSIX <i>FILE*</i> object in a simple PHP class. Let's start with something basic in our systemlib, containing just a constructor:<br/> <blockquote style="background-color: #DDDDDD;"><pre>&lt;&lt;__NativeData("Example2_File")&gt;&gt; class Example2_File { &lt;&lt;__Native&gt;&gt; public function __construct(string $filename, string $mode): void; }</pre></blockquote><br/> A new user attribute has appeared! <i>&lt;&lt;__NativeData("Example2_File")&gt;&gt;</i> tells the runtime that this is no ordinary object. This object should be over-allocated with enough to space to handle some internal C++ object, identified by the quoted name. In practice this is usually the name of the class it goes with, but it doesn't have to be. How does this hook up to internals? That comes next, within <i>ext_example2.cpp</i> by adding an <i>#include "hphp/runtime/vm/native-data.h"</i> and the following code:</br> <blockquote style="background-color: #DDDDDD;"><pre>const StaticString s_Example2_File("Example2_File"); class Example2_File { public: Example2_File() { /* new Example2_File */ } Example2_File(const Example2_File&) = delete; Example2_File& operator=(const Example2_File& src) { /* clone $instanceOfExample2_File */ throw Object(SystemLib::AllocExceptionObject( "Cloning Example2_File is not allowed" )); } ~Example2_File() { sweep(); } void sweep() { if (m_file) { fclose(m_file); m_file = nullptr; } } FILE* m_file{nullptr}; }; void HHVM_METHOD(Example2_File, __construct, const String& filename, const String& mode) { auto data = Native::data&lt;Example2_File&gt;(this_); if (data->m_file) { throw Object(SystemLib::AllocExceptionObject( "File is already open!" )); } data->m_file = fopen(filename.c_str(), mode.c_str()); if (!data->m_file) { String message("Unable to open "); message += filename + ": errno=" + String(errno); throw Object(SystemLib::AllocExceptionObject(message)); } }</pre></blockquote><br/> And some glue code in <i>moduleInit()</i> to tie both the constructor and the data class into the class definition:<br/> <blockquote style="background-color: #DDDDDD;"><pre> HHVM_ME(Example2_File, __construct); Native::registerNativeDataInfo&lt;Example2_File&gt;(s_Example2_File.get()); </pre></blockquote><br/> The code so far is at commit <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/97b3cfd49e0d43b56d06c8f78d139c8c9761efda">97b3cfd49e</a>.<br/> <br/> We're only opening (and ultimately closing) our file at this point, but these are really important stages in an object's lifecycle, so this is worth going though slowly. When a PHP script calls <i>$o = new Example2_File(__FILE__, "r");</i>, the first thing the engine does is allocate space for the object. This is done by adding <i>sizeof(ObjectData)</i> (the standard, base object size) to the size given by any <i>NativeDataInfo</i> associated with it. We made that association by calling <i>Native::registerNativeDataInfo<T>(StringData* id);</i>, where <i>T</i> is the C++ class type to allocate with the <i>ObjectData</i>, and <i>id</i> is the symbolic name we gave it in the syetemlib file using <i>&lt;&lt;__NativeData("id")&gt;&gt;</i>.<br/> <br/> Next, the engine invokes the constructor, providing a pointer to the object via a hidden <i>ObjectData* this_</i> property in the C++ method's signature. From here, we can get access to our private data structure by using the <i>Native::data<T>()</i> accessor to jump to the correct offset from <i>this_</i>. At this point, we have access to a normal C++ object which just so happens to be bound to a PHP object, and we have constructor parameters as well!<br/> <br/> From here, there are two reasons a PHP object might die. In the expected case, it runs out of references and is destructed during the course of request's runtime. In this case, our auxiliary object has its destructor called as well, so that external pointers can be cleaned up nicely. The other time a PHP object can die is when the request is shutting down. This is somewhat more exceptional since the memory manager is sweeping ALL request-local data, not necessarily in the most ideal order. It's up to your auxiliary class to deal with non-sweepable resources, but trust that the runtime will deal with resources which are. This is resolved by having a secondary psuedo-destructor called <i>sweep();</i>. For simple implementations like ours, we want the regular destructor and sweep to do the same time, since the only members of this C++ class are external pointers. If we have sweepable resources such as an HPHP::String however, we'd want to avoid the implicit member destruction which comes with calling ~Example2_File(). It's entirely possible that the internal state of that String is no longer valid because it was sweeped first. Hence the need for a separate sweep() function.<br/> <br/> TL;DR? - Just have your destructor call sweep(), and deal with external pointers in sweep(). That's good 90% of the time.<br/> <br/> You might also have noticed that I'm throwing an exception in the assignment operator. This is normally used for handling a clone, where you'd probably duplicate the <i>FILE*</i> handle, but I realized midway that POSIX file streams don't really have that notion, so I took the easy way out and threw a standard exception. In practice, the implementation would probably look something like:<br/> <blockquote style="background-color: #DDDDDD;"><pre> Example2_File& operator=(const Example2_File& src) { /* copy/clone class members, then return self */ if (m_file) { fclose(m_file); m_file = nullptr; } if (src.m_file) { m_file = fclone(src.m_file); } return *this; } </pre></blockquote><br/> But like I said, there doesn't seem to be an fclone() as such. Instead, let's add a few more methods to flesh out our class:<br/> <blockquote style="background-color: #DDDDDD;"><pre>String HHVM_METHOD(Example2_File, read, int64_t len) { auto data = Native::data&lt;Example2_File&gt;(this_); String ret(len, ReserveString); auto slice = ret.bufferSlice(); len = fread(slice.ptr, 1, len, data-&gt;m_file); return ret.setSize(len); } int64_t HHVM_METHOD(Example2_File, tell) { auto data = Native::data<Example2_File>(this_); return ftell(data-&gt;m_file); } bool HHVM_METHOD(Example2_File, seek, int64_t pos, int64_t whence) { if ((whence != SEEK_SET) && (whence != SEEK_CUR) && (whence != SEEK_END)) { raise_warning("Invalid seek-whence"); return false; } auto data = Native::data&lt;Example2_File&gt;(this_); return 0 == fseek(data-&gt;m_file, pos, whence); }</pre></blockquote> The code so far is at commit <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/f4acf359ca527cc641731e2be175b62902888520">df4acf359ca</a>.<br/> <br/>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com2tag:blogger.com,1999:blog-1234953837283832282.post-90665174262265920672015-01-06T20:24:00.000-08:002015-01-06T20:27:31.901-08:00HHVM Extension Writing, Part IIIn our <a href="http://blog.golemon.com/2015/01/hhvm-extension-writing-part-i.html">last installment</a> I walked through setting up a dev environment and creating a simple HHVM extension which exposed some constants and global scope functions. Today, we'll expand on that by delving deeper into three of the five "Smart" types: <a href="https://github.com/facebook/hhvm/blob/master/hphp/runtime/base/type-string.h">String</a>, <a href="https://github.com/facebook/hhvm/blob/master/hphp/runtime/base/type-array.h">Array</a>, and <a href="https://github.com/facebook/hhvm/blob/master/hphp/runtime/base/type-variant.h">Variant</a>. The other two "Smart" types will be covered in Parts III(Objects) and IV(Resources) since they require a bit more explaining.<br /> <br/> All code in the following examples can be found at <a href="https://github.com/sgolemon/hhvm-extension-writing">https://github.com/sgolemon/hhvm-extension-writing</a> and we'll be starting from where Part I left off: commit <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/ad9618ac8cff194a2e01a2bf9d59d0ae5ec93939">ad9618ac8c</a>.<br/><br/> <h3>The String class</h3> <a href="https://github.com/facebook/hhvm/blob/master/hphp/runtime/base/type-string.h">HPHP::String</a> resembles C++'s std::string class in many ways, but also builds in several assumptions about how PHP strings should behave, is able to be encapsulated in a Variant (mixed) object, and performs common string related tasks, such as numeric conversion.<br/> <br/> This post is going to highlight the most common features of the String class, but you should look through the header file yourself for a more in-depth exploration.<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre>/* Basic inspection */ class String { public: const char* c_str() const; int size() const; bool empty() const { return size() == 0; } int length() const ( return size(); } bool isNumeric() const; bool isInteger() const; bool isZero() const; bool toBoolean() const; char toByte() const; short toInt16() const; int toInt32() const; int64_t toInt64() const; double toDouble() const; std::string toCppString() const; char charAt(int pos) const; char operator[](int pos) const; }; </pre></blockquote><br/> The meaning and use of these methods should all be straightforward. In practice, <span style="background-color: #DDDDDD;">c_str()</span>, <span style="background-color: #DDDDDD;">size()</span>, and <span style="background-color: #DDDDDD;">empty()</span> are going to cover 90% of your uses for reading values from the String class.<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre>/* Creation */ class String { public: String(); // empty string String(const char* cstr); String(const std::string& cppstr); String(const String& hphpstr); String(int64_t num); String(double num); static StaticString FromCStr(const char* cstr); String(size_t cap, ReserveStringMode mode); MutableSlice bufferSlice(); uint32_t capacity() const; const String& setSize(int len); }; </pre></blockquote><br/> The constructors, as you can see, are generally built around making new runtime string values from an existing string or numeric value, and are again straight-forward to use. String::FromCStr() is a somewhat special case in that it creates a StaticString, rather than a String. While a String is cleaned up at the end of the request it was created in, StaticStrings live forever, and can even be shared between multiple requests. Because overuse of StaticString could easily lead to memory bloat, they're typically only used for defining persistent features (such as constant names/values) as seen in Part I.<br/> <br/> The most interesting part of this API is the ReserveStringMode and MutableSlice. Ordinarily, you shouldn't save the pointer you get from String::c_str() as it can potentially change between calls, and you generally shouldn't go modifying a String unless you know you own it anyway. If you do have need to modify a string, call bufferSlice() on it. The MutableSlice structure you get back will contain a pointer to a (relatively) stable block of memory which can be populated. Here's an example:<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre>String HHVM_FUNCTION(example1_count_preallocate) { /* 30 bytes: 3 per number: 'X, ' */ String ret(30, ReserveString); auto slice = ret.bufferSlice(); for (int i = 0; i < 10; ++i) { snprintf(slice.ptr + (i*3), 4, "%d, ", i); } /* Terminate just after the 9th digit, overwriting the ',' with a null byte */ return ret.setSize((9*3) + 1); }</pre></blockquote><br/> This contrived example allocates enough space for 10 single-digit numbers, and a comma and space following them. It uses snprintf() to fill that buffer up, then it truncates it as 28 characters, since the final ', ' wasn't actually necessary. You'll find this pattern in use anywhere an API expects you to provide it with a buffer for it to fill, such as in the intl extension where it calls into ICU.<br/> <br/> Another approach to building up a string from parts would be to use the operator+ overload which allows you to simply concatenate Strings such as in the following:<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre>String HHVM_FUNCTION(example1_count_concatenate) { String ret, delimiter(", "); for (int i = 0; i < 10; ++i) { if (i > 0) { ret += delimiter; } ret += String(i); } return ret; }</pre></blockquote><br/> There are costs and benefits to both versions. The former is more efficient as it only does one allocation, as opposed to the latter which does at least 11, and far less copying around. On the other hand, the second version is far more readable and far less error prone. For the contrived example, I'd call the second version "better", but there are certainly cases where the first version is superior.<br/> The code so far is at commit: <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/fa82b3cd70c625b2790c920d95824ff83d7281ae">fa82b3cd70</a><br/><br/> <h3>The Array Class</h3> Arrays are the do-all bucket of "stuff" of the PHP language. They can behave like vectors, maps, sets, or weird hybrid hodgepodge containers without rhyme or reason. You already know how to interact with them from userspace, so let's take a look at how to interact with them from C++. As with Strings, we're only going to go into the most common API calls here, check out the <a href="https://github.com/facebook/hhvm/blob/master/hphp/runtime/base/type-array.h">header</a> for the full story.<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre>/* Core API */ class Array { public: static Array Create(); // array() static Array Create(const Variant& value); // array($value) static Array Create(const Variant& key, const Variant& value); // array($key => $value) /* Read */ const Variant operator[](int64_t key) const; const Variant operator[](const String& key) const; const Variant operator[](const Variant& key) const; /* count($arr) */ ssize_t count() const; /* array_key_exists($arr, $key); */ bool exists(int64_t key) const; bool exists(const String& key, bool isKey = false) const; bool exists(const Variant& key, bool isKey = false) const; /* Write */ void clear(); /* $arr[$key] = $v; */ void set(int64_t key, const Variant& v); void set(const String& key, const Variant& v, bool isKey = false); void set(const Variant& key, const Variant& v, bool isKey = false); void prepend(const Variant& v); // array_unshift($v); Variant dequeue(); // array_shift($v); void append(const Variant& v); // array_push($v); aka => $arr[] = $v; Variant pop(); // array_pop($v); /* $arr[$key] =& $v; */ void setRef(int64_t key, const Variant& v); void setRef(const String& key, const Variant& v, bool isKey = false); void setRef(const Variant& key, const Variant& v, bool isKey = false); /* $arr[] =& $v; */ void appendRef(Variant& v); /* unset($arr[$key]); */ void remove(int64_t key); void remove(const String& key, bool isKey = false); void remove(const Variant& key); }; </pre></blockquote><br/> As you can see, the Array APIs mirror PHP's userspace API very closely, down to the read API using square-bracket notation just like PHP code. Let's write a couple new methods dealing with arrays as arguments and return values.<br/> <blockquote style="background-color: #DDDDDD;"><pre>const StaticString s_name("name"), s_hello("hello"), s_Stranger("Stranger"); void HHVM_FUNCTION(example1_greet_options, const Array& options) { String name(s_Stranger); if (options.exists(s_name)) { name = options[s_name].toString(); } bool hello = true; if (options.exists(s_hello)) { hello = options[s_hello].toBoolean(); } g_context->write(greet ? "Hello " : "Goodbyte "); g_context->write(name); g_context->write("\n"); } Array HHVM_FUNCTION(example1_greet_make_options, const String& name, bool hello) { Array ret = Array::Create(); if (!name.empty()) { ret.set(s_name, name); } ret.set(s_hello, hello); return ret; } </pre></blockquote><br /> Pretty similar syntax to writing PHP code, yeah?<br/> <br/> The code so far is at commit: <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/3966bb1da16421cb653d15d38986be0a8098ea41">3966bb1da1</a><br/> <br/> <h3>The Variant Class</h3> The last "smart" class doesn't represent a single PHP type, rather it represents all types in a sort of meta-container which knows what it's holding, and knows how to convert between the concrete types. Variant is useful when you need to accept and/or return multiple possible types. For a start, let's list out the core API. Remember that there are far more methods than I'll cover here, and you can find the reset in the <a href="https://github.com/facebook/hhvm/tree/master/hphp/runtime/base/type-variant.h">header file</a>.<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre>/* Creation/Assignment */ class Variant { public: Variant(); Variant(bool bval); Variant(int64_t lval); Variant(double dval); Variant(const String& strval); Variant(const char* cstrval); Variant(const Array& arrval); Variant(const Resource& resval); Variant(const Object& objval); Variant(const Variant& val); template<typename T> Variant &operator=(const T &v); }</pre></blockquote><br/> These APIs together mean that a Variant may be initialized or assigned from any other variable type supported by userspace code. This becomes especially powerful when looking at Variant return types.<br/> <blockquote style="background-color: #DDDDDD;"><pre>Variant HHVM_FUNCTION(example1_password, const String& guess) { if (guess.same(s_secret)) { return "Password accepted: A winner is you!"; } return false; }</pre></blockquote><br/> These seemingly incompatible return types (const char* and bool) work because they are implicitly constructed into a Variant instance. Explicit types are generally preferred, because the IR can make better assumptions during optimization, but sometimes you just want your return values to be adaptable like that.<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre>/* Introspection and Unboxing */ class Variant { public: bool isNull() const; bool isBoolean() const; bool isInteger() const; bool isDouble() const; bool isNumeric(bool checkString = false) const; bool isString() const; bool isArray() const; bool isResource() const; bool isObject() const; bool toBoolean() const; int64_t toInt64() const; double toDouble() const; DataType toNumeric(int64_t &ival, double &dval, bool checkString = false) const; String toString() const; Array toArray() const; Resource toResource() const; Object toObject() const; }</pre></blockquote><br/> These APIs allow pulling a concrete data type out of a Variant so they can be operated on directly. Note that the to*() APIs will convert the type if necessary, even if the is*() call returned false, but that not all conversions make sense. Let's make a contrived example by implementing a simplistic var_dump():<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre>&lt;&lt;__Native&gt;&gt; function example1_var_dump(mixed $value): void; void HHVM_FUNCTION(example1_var_dump, const Variant &value) { if (value.isNull()) { g_context->write("null\n"); return; } if (value.isBoolean()) { g_context->write("bool("); g_context->write(value.toBoolean() ? "true" : "false"); g_context->write(")\n"); return; } if (value.isInteger()) { g_context->write("int("); g_context->write(String(value.toInt64())); g_context->write(")\n"); return; } // etc... }</pre></blockquote><br/> The code so far is at commit: <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/8e82e3e416be6ca358e496e78927feaba7519647">8e82e3e416</a><br/><br/> <h3>What's next...</h3> We'll continue in the next installment by exploring Objects. These get a bit more complicated with the introduction of visibility, properties, constants, inheritance, and internal data structures.Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-24461414581236110282015-01-06T10:21:00.000-08:002015-01-06T20:29:52.149-08:00HHVM Extension Writing, Part II've written a number of blogposts and even one book over the years on writing extensions for PHP, but very little documentation is available for writing HHVM extensions. &nbsp;This is kinda sad since I built a good portion of the latter's API. Let's fix that, starting with this article.<br /> <br/> All the code in this post (and its followups) will be found at <a href="https://github.com/sgolemon/hhvm-extension-writing">https://github.com/sgolemon/hhvm-extension-writing</a>.<br/><br/> <h3> Setting up a build environment</h3> The first thing you need to do is get all the dependencies in place. &nbsp;I'm going to start from a clean install of Ubuntu 14.04 LTS and use the prebuilt HHVM binaries for Ubuntu. Other distros should work (with varying degrees of success), but sorting them all out is beyond the scope of this blog entry.<br/> <br /> First, let's trust HHVM's package repo and pull in its package list. Then we can install the hhvm-dev package to pull in the binary along with all needed headers.<br /> <br /> <blockquote style="background-color: #dddddd;"> <pre>$ wget -O - http://dl.hhvm.com/conf/hhvm.gpg.key | \ sudo apt-key add - $ echo deb http://dl.hhvm.com/ubuntu trusty main | \ sudo tee /etc/apt/sources.list.d/hhvm.list $ sudo apt-get update $ sudo apt-get install hhvm-dev </pre> </blockquote> <br /> <h3> Creating an extension skeleton</h3> The most basic, no-nothing extension imaginable requires two files. A C++ source file to declare itself, and a config.cmake file to describe what's being built. Let's start with the build file, which is a simple, single line:<br /> <br /> <blockquote style="background-color: #dddddd;"> <pre>HHVM_EXTENSION(example1 ext_example1.cpp)</pre> </blockquote> <br /> This macro declares a new extension named "example1" with a single source file named "ext_example1.cpp". If we had multiple source files, we'd delimit them with a space (HHVM_EXTENSION(example1 ext_example1.cpp ex1lib.cpp utilex1.cpp etc.cpp))<br /> <br /> The source file has a little more boilerplate, but fortunately it's also just a handful of lines:<br /> <br /> <blockquote style="background-color: #dddddd;"> <pre>#include "hphp/runtime/base/base-includes.h" namespace HPHP { class Example1Extension : public Extension { public: Example1Extension(): Extension("example1", "1.0") {} } s_example1_extension; HHVM_GET_MODULE(example1); } // namespace HPHP </pre> </blockquote> <br /> All we're doing here is exposing a specialization of the "Extension" class which gives itself the name "example1". It doesn't do anything more than declare itself into the runtime environment. Those familiar with PHP extension development can think of this as the zend_module_entry struct, with all callbacks and the function table set to NULL.<br /> <br/> The code so far is at commit: <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/214e2e7be60747abef89fa13e5922ddf9b1ad60d">214e2e7be6</a> <h3> Building an extension and testing it out</h3> To build an extension, first run <span style="background-color: #dddddd;">hphpize</span> to generate a CMakeLists.txt file, then <span style="background-color: #dddddd;">cmake .</span> to generate a Makefile from that. Finally, issue <span style="background-color: #dddddd;">make</span> to actually build it. You should see output like the following:<br /> <br /> <blockquote style="background-color: #dddddd;"> <pre>$ hphpize ** hphpize complete, now run 'cmake . &amp;&amp; make` to build $ cmake . -- Configuring for HHVM API version 20140829 -- Configuring done -- Generating done -- Build files have been written to: /home/username/hhvm-ext-writing $ make Scanning dependencies of target example1 [100%] Building CXX object CMakeFiles/example1.dir/ext_example1.cpp.o [100%] Built target example1 </pre> </blockquote> <br /> Now we're ready to load it into our runtime. Start by creating a simple test file:<br /> <blockquote style="background-color: #dddddd;"> <pre>&lt;?php var_dump(extension_loaded('example1.php'));</pre> </blockquote> Then fire up hhvm: <span style="background-color: #dddddd;">hhvm -d extension_dir=. -d hhvm.extensions[]=example1.so tests/loaded.php</span> and you should see <span style="background-color: #dddddd;">bool(true)</span>. <h3>Adding functionality</h3> The simplest way to add functionality is to write some Hack code. You could write straight PHP code, but you'll see in a few moments why Hack is preferable for extension systemlibs. Let's introduce a new file: ext_example1.php and link it into our project:<br /> <blockquote style="background-color: #DDDDDD;"><pre>&lt;?hh function example1_hello() { echo "Hello World\n"; } </pre></blockquote><br /> Then load it in during the <span style="background-color: #DDDDDD;">moduleInit()</span> (aka MINIT) phase:<br/> <blockquote style="background-color: #DDDDDD;"><pre>class Example1Extension : public Extension { public: Example1Extension(): Extension("example1", "1.0") {} void moduleInit() override { loadSystemlib(); } } s_example1_extension; </pre></blockquote> And finally, add the following to your <span style="background-color: #DDDDDD;">config.cmake</span> file to embed it into the .so, where HHVM can load it from at runtime.<br/> <blockquote style="background-color: #DDDDDD;"><pre>HHVM_EXTENSION(example1 ext_example1.cpp) HHVM_SYSTEMLIB(example1 ext_example1.php) </pre></blockquote><br/> Rebuild your extension according to the instructions above, then try it out:<br/> <blockquote style="background-color: #DDDDDD;"><pre>$ hhvm -d extension_dir=. -d hhvm.extensions[]=example1.so tests/hello.php Hello World </pre></blockquote><br/> The code so far is at commit: <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/54782f157df7b30a8596e85b8bdf9dc2632fa22d">54782f157d</a><br/> <h3>Bridging the gap</h3> If all you wanted to do was write PHP code implementations you could create a normal library for that. Extensions are for bridging PHP-script into native code, so let's do that. Make a new entry in your systemlib file using some hack specific syntax:<br/> <blockquote style="background-color: #DDDDDD;"><pre>&lt;&lt;__Native&gt;&gt; function example1_greet(string $name, bool $hello = true): void;</pre></blockquote><br/> The <span style="background-color: #DDDDDD;">&lt;&lt;__Native&gt;&gt;</span> UserAttribute tells HHVM that this is the declaration for an internal function. The hack types tell the runtime what C++ type to pair them with, and the usual rules for default arguments apply.<br/> <br/> To pair it with an internal implementation, we'll add the following to ext_example1.cpp:<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre>void HHVM_FUNCTION(example1_greet, const String& name, bool hello) { g_context->write(hello ? "Hello " : "Goodbye "); g_context->write(name); g_context->write("\n"); }</pre></blockquote><br /> <br /> And link it to the systemlib by adding <span style="background-color: #DDDDDD;">HHVM_FE(example1_greet);</span> to moduleInit().<br/> <br/> As you can see, internal functions are declared with the HHVM_FUNCTION() macro where the first arg is the name of the function, as exposed to userspace, and the remaining map to the userspace functions argument signature. The argument types map according the following table:<br/> <br/> <table> <thead> <tr> <th>Hack type</th> <th>C++ type (argument)</th> <th>C++ type (return type)</th> </tr> </thead> <tbody> <tr><td>void</td><td>N/A</td><td>void</td></tr> <tr><td>bool</td><td>bool</td><td>bool</td></tr> <tr><td>int</td><td>int64_t</td><td>int64_t</td></tr> <tr><td>float</td><td>double</td><td>double</td></tr> <tr><td>string</td><td>const String&</td><td>String</td></tr> <tr><td>array</td><td>const Array&</td><td>Array</td></tr> <tr><td>resource</td><td>const Resource&</td><td>Resource</td></tr> <tr><td>object<br/></td><td>const Object&</td><td>Object</td></tr> <tr><td><i>ClassName</i></td><td>const Object&</td><td>Object</td></tr> <tr><td>mixed</td><td>const Variant&</td><td>Variant</td></tr> <tr><td>mixed&amp;</td><td>VRefParam</td><td>N/A</td></tr> </tbody> </table><br/><br/> Since this is Hack syntax, you may declare the types as soft (with an @) or nullable (with a question mark), but since these types are not limited to a primitive, they need to be represented internally as the more generic <span style="background-color: #DDDDDD;">const Variant&</span> for arguments or <span style="background-color: #DDDDDD;">Variant</span> or return types (essentially, mixed).<br/> <br/> Reference arguments use the <span style="background-color: #DDDDDD;">VRefParam</span> type noted above. An example of which can be seen below:<br/> <br/> <blockquote style="background-color: #DDDDDD;"><pre>&lt;&lt;__Native&gt;&gt; function example1_life(mixed &$meaning): void; </pre></blockquote><blockquote style="background-color: #DDDDDD;"><pre>void HHVM_FUNCTION(example1_life, VRefParam meaning) { meaning = 42; }</pre></blockquote><br/> The code so far is at commit: <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/df16aca35ee00cf6f84b2a3dd870582cdbdd1de5">df16aca35e</a><br/> <h3>Constants</h3> Constants, like any other bit of PHP, may be declared in the systemlib file, or if they depend on some native value (such as a define from an external library), they may be declare in moduleInit() using the Native::registerConstant() template as with the following:<br/> <blockquote style="background-color: #DDDDDD;"><pre>const StaticString s_EXAMPLE1_YEAR("EXAMPLE1_YEAR"); class Example1Extension: public Extension { public: Example1Extension(): Extension("example1", "1.0") {} void moduleInit() override { Native::registerConstant&lt;KindOfInt64&gt;(s_EXAMPLE1_YEAR.get(), 2015); } } s_example1_extension; </pre></blockquote> The use of this function should be mostly obvious, in that it takes the name of a constant as a StringData* (which comes from a StaticString's .get() accessor), and a value appropriate to the constant's type. The type, in turn, is given as the function's template parameter and is one of the DataType enum values. The kinds correspond roughly to the basic PHP data types.<br/> <table> <thead> <tr><th>DataType</th><th>C++ type</th></tr> </thead> <tbody> <tr><td>KindOfNull</td><td>N/A</td></tr> <tr><td>KindOfBoolean</td><td>bool</td></tr> <tr><td>KindOfInt64</td><td>int64_t</td></tr> <tr><td>KindOfDouble</td><td>double</td></tr> <tr><td>KindOfStaticString</td><td>StringData*</td></tr> </tbody> </table> The code so far is at commit: <a href="https://github.com/sgolemon/hhvm-extension-writing/tree/ad9618ac8cff194a2e01a2bf9d59d0ae5ec93939">ad9618ac8c</a><br/> <h3>What's next...</h3> In the <a href="http://blog.golemon.com/2015/01/hhvm-extension-writing-part-ii.html">next part</a> of this series, we'll look at the String, Array, and Variant types. Part III will continue with Objects, then Resources in Part IV.Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com7tag:blogger.com,1999:blog-1234953837283832282.post-20126133280905869702009-06-11T14:34:00.000-07:002010-05-13T16:40:54.164-07:00Slashdotted; Post-mortem<p>About a day and a half ago, <a href="http://reddit.com/">Reddit</a> user <a href="http://www.reddit.com/user/stderr">stderr</a> posted a link to <a href="http://www.php.net/manual/en/control-structures.goto.php">the PHP documentation</a> showing that GOTO will be a part of the language as of version 5.3. Somewhere in the <a href="http://www.reddit.com/r/programming/comments/8r3dz/cmon_php_at_this_point_youre_almost_a_parody_of/">reddit comments for this post</a>, a link was pasted to an entry on this blog where I announced the feature being added years earlier. This naturally brought out the usual flame wars that circle around something like GOTO and drove some new traffic to my blog. No big deal, my server is pretty low-traffic, it can handle a few extra hits.</p><br /><p>Within a few hours, <a href="http://www.reddit.com/user/burghler">burghler</a> had browsed through other entries on my blog, finding what was at the time, the most recent entry about <a href="http://saragolemon.blogspot.com/2009/06/wait-did-you-seriously-just-say-that.html">my friend's experience burying her mother</a><a>. Just like the first reddit post, which had made the front page, this one also had a </a><a href="http://www.reddit.com/r/atheism/comments/8rc1s/evil_christians_ruin_a_funeral_because_daughter/">somewhat incendiary title</a>.</p><br /><p>Okay, more than a little incendiary, but I'll get to that in a moment...</p> <h3>The lesson</h3> <p>You would think, given that I'm the Architect for Yahoo WebSearch Front-end Engineering, that I would know something about configuring a server to not fall over under load. And in fact, I spend a good portion of my time on making sure that unexpected traffic spikes aren't enough to make a server get overloaded and trigger a chain-reaction of front-ends falling over. I really have <b>no excuse</b> for not preparing my server for what happened.</p><br /><p>When I woke up Wed morning, I found that my server just didn't seem to respond to SSH or HTTP attempts. A reboot request didn't help, and the colo folks insisted that the server was simply running slow, but it was running. So I left my ssh connection attempt running and eventually I did get a login prompt. Several pained minutes later I managed to get an iptables rule in place to block off the flood of traffic (quicker than trying to stop the webserver). Suddenly, the CPU load was gone! Turns out I had my MaxClients setting much too high, and after a certain degree of concurrency, enough web-server children had spawned off to use up the available memory, which triggered disk swap, and made the CPU load 10x worse. Again, I know this effect exists, I really should have set up this server better.</p><br /><p>A few tweaks to the config later and I got my server running smoothly. I also took the time to re-run my access log statistics. Of the aproximately 3 years of stats I've got, a full <b>2%</b> of my hits were logged yesterday. Impressive reddit.... you win this round...</p><p>Bright squares indicate heavy traffic, dark squares indicate low traffic</p><br /><h4>Prior to yesterday's traffic</h4><table border="0" cellpadding="0" cellspacing="0"> <tbody> <tr> <td class="valueentry">Jan</td> <td style="background-color: rgb(60, 66, 90);"><br /></td> <td style="background-color: rgb(71, 78, 107);"><br /></td> <td style="background-color: rgb(68, 75, 103);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(81, 89, 122);"><br /></td> <td style="background-color: rgb(72, 79, 108);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(70, 77, 106);"><br /></td> <td style="background-color: rgb(78, 86, 117);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(63, 69, 95);"><br /></td> <td style="background-color: rgb(62, 69, 94);"><br /></td> <td style="background-color: rgb(39, 43, 59);"><br /></td> <td style="background-color: rgb(37, 41, 56);"><br /></td> <td style="background-color: rgb(69, 76, 103);"><br /></td> <td style="background-color: rgb(63, 70, 95);"><br /></td> <td style="background-color: rgb(64, 70, 96);"><br /></td> <td style="background-color: rgb(64, 70, 96);"><br /></td> <td style="background-color: rgb(66, 72, 99);"><br /></td> <td style="background-color: rgb(68, 75, 103);"><br /></td> <td style="background-color: rgb(71, 79, 107);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(58, 64, 87);"><br /></td> <td style="background-color: rgb(71, 78, 106);"><br /></td> <td style="background-color: rgb(83, 91, 124);"><br /></td> <td style="background-color: rgb(74, 81, 111);"><br /></td> <td style="background-color: rgb(92, 102, 139);"><br /></td> <td style="background-color: rgb(79, 87, 119);"><br /></td> <td style="background-color: rgb(78, 86, 117);"><br /></td> </tr> <tr> <td class="valueentry">Feb</td> <td style="background-color: rgb(77, 85, 116);"><br /></td> <td style="background-color: rgb(67, 73, 100);"><br /></td> <td style="background-color: rgb(63, 69, 95);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(75, 83, 113);"><br /></td> <td style="background-color: rgb(73, 80, 109);"><br /></td> <td style="background-color: rgb(80, 88, 120);"><br /></td> <td style="background-color: rgb(74, 82, 112);"><br /></td> <td style="background-color: rgb(66, 72, 99);"><br /></td> <td style="background-color: rgb(72, 79, 108);"><br /></td> <td style="background-color: rgb(71, 78, 107);"><br /></td> <td style="background-color: rgb(76, 83, 114);"><br /></td> <td style="background-color: rgb(75, 82, 112);"><br /></td> <td style="background-color: rgb(76, 84, 115);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(74, 81, 111);"><br /></td> <td style="background-color: rgb(73, 80, 109);"><br /></td> <td style="background-color: rgb(70, 77, 106);"><br /></td> <td style="background-color: rgb(65, 72, 98);"><br /></td> <td style="background-color: rgb(74, 81, 111);"><br /></td> <td style="background-color: rgb(71, 78, 106);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(71, 78, 107);"><br /></td> <td style="background-color: rgb(91, 100, 137);"><br /></td> <td style="background-color: rgb(123, 135, 184);"><br /></td> <td style="background-color: rgb(80, 88, 120);"><br /></td> <td style="background-color: rgb(82, 90, 123);"><br /></td> <td style="background-color: rgb(29, 32, 43);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> </tr> <tr> <td class="valueentry">Mar</td> <td style="background-color: rgb(98, 108, 148);"><br /></td> <td style="background-color: rgb(85, 94, 128);"><br /></td> <td style="background-color: rgb(77, 85, 115);"><br /></td> <td style="background-color: rgb(79, 87, 118);"><br /></td> <td style="background-color: rgb(78, 86, 118);"><br /></td> <td style="background-color: rgb(76, 84, 114);"><br /></td> <td style="background-color: rgb(78, 86, 118);"><br /></td> <td style="background-color: rgb(99, 109, 149);"><br /></td> <td style="background-color: rgb(71, 78, 107);"><br /></td> <td style="background-color: rgb(83, 91, 125);"><br /></td> <td style="background-color: rgb(74, 82, 111);"><br /></td> <td style="background-color: rgb(78, 86, 118);"><br /></td> <td style="background-color: rgb(73, 80, 109);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(66, 73, 99);"><br /></td> <td style="background-color: rgb(72, 79, 108);"><br /></td> <td style="background-color: rgb(74, 81, 111);"><br /></td> <td style="background-color: rgb(84, 92, 126);"><br /></td> <td style="background-color: rgb(71, 79, 107);"><br /></td> <td style="background-color: rgb(83, 91, 125);"><br /></td> <td style="background-color: rgb(78, 86, 117);"><br /></td> <td style="background-color: rgb(76, 84, 115);"><br /></td> <td style="background-color: rgb(89, 98, 133);"><br /></td> <td style="background-color: rgb(89, 98, 134);"><br /></td> <td style="background-color: rgb(88, 97, 132);"><br /></td> <td style="background-color: rgb(84, 92, 126);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(75, 82, 112);"><br /></td> <td style="background-color: rgb(84, 93, 127);"><br /></td> <td style="background-color: rgb(76, 84, 115);"><br /></td> </tr> <tr> <td class="valueentry">Apr</td> <td style="background-color: rgb(95, 105, 143);"><br /></td> <td style="background-color: rgb(116, 127, 174);"><br /></td> <td style="background-color: rgb(139, 153, 209);"><br /></td> <td style="background-color: rgb(95, 105, 143);"><br /></td> <td style="background-color: rgb(85, 93, 128);"><br /></td> <td style="background-color: rgb(74, 81, 111);"><br /></td> <td style="background-color: rgb(76, 84, 115);"><br /></td> <td style="background-color: rgb(83, 92, 125);"><br /></td> <td style="background-color: rgb(99, 109, 149);"><br /></td> <td style="background-color: rgb(98, 108, 148);"><br /></td> <td style="background-color: rgb(95, 104, 142);"><br /></td> <td style="background-color: rgb(87, 95, 130);"><br /></td> <td style="background-color: rgb(81, 89, 121);"><br /></td> <td style="background-color: rgb(96, 105, 144);"><br /></td> <td style="background-color: rgb(96, 106, 145);"><br /></td> <td style="background-color: rgb(94, 103, 141);"><br /></td> <td style="background-color: rgb(86, 94, 129);"><br /></td> <td style="background-color: rgb(89, 98, 134);"><br /></td> <td style="background-color: rgb(100, 110, 150);"><br /></td> <td style="background-color: rgb(86, 94, 129);"><br /></td> <td style="background-color: rgb(93, 102, 140);"><br /></td> <td style="background-color: rgb(89, 98, 134);"><br /></td> <td style="background-color: rgb(99, 109, 148);"><br /></td> <td style="background-color: rgb(106, 117, 159);"><br /></td> <td style="background-color: rgb(103, 113, 155);"><br /></td> <td style="background-color: rgb(116, 128, 175);"><br /></td> <td style="background-color: rgb(135, 148, 202);"><br /></td> <td style="background-color: rgb(116, 128, 175);"><br /></td> <td style="background-color: rgb(95, 105, 143);"><br /></td> <td style="background-color: rgb(93, 103, 140);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> </tr> <tr> <td class="valueentry">May</td> <td style="background-color: rgb(105, 116, 158);"><br /></td> <td style="background-color: rgb(95, 105, 143);"><br /></td> <td style="background-color: rgb(96, 105, 144);"><br /></td> <td style="background-color: rgb(138, 152, 208);"><br /></td> <td style="background-color: rgb(98, 108, 147);"><br /></td> <td style="background-color: rgb(95, 104, 142);"><br /></td> <td style="background-color: rgb(93, 103, 140);"><br /></td> <td style="background-color: rgb(101, 111, 152);"><br /></td> <td style="background-color: rgb(144, 158, 216);"><br /></td> <td style="background-color: rgb(85, 94, 128);"><br /></td> <td style="background-color: rgb(89, 98, 133);"><br /></td> <td style="background-color: rgb(104, 115, 156);"><br /></td> <td style="background-color: rgb(92, 101, 138);"><br /></td> <td style="background-color: rgb(86, 94, 129);"><br /></td> <td style="background-color: rgb(83, 91, 124);"><br /></td> <td style="background-color: rgb(89, 98, 134);"><br /></td> <td style="background-color: rgb(70, 77, 106);"><br /></td> <td style="background-color: rgb(80, 88, 121);"><br /></td> <td style="background-color: rgb(102, 113, 154);"><br /></td> <td style="background-color: rgb(99, 109, 149);"><br /></td> <td style="background-color: rgb(95, 105, 143);"><br /></td> <td style="background-color: rgb(110, 121, 165);"><br /></td> <td style="background-color: rgb(93, 102, 139);"><br /></td> <td style="background-color: rgb(92, 101, 138);"><br /></td> <td style="background-color: rgb(99, 109, 148);"><br /></td> <td style="background-color: rgb(93, 102, 139);"><br /></td> <td style="background-color: rgb(87, 96, 131);"><br /></td> <td style="background-color: rgb(93, 102, 139);"><br /></td> <td style="background-color: rgb(95, 104, 142);"><br /></td> <td style="background-color: rgb(97, 106, 145);"><br /></td> <td style="background-color: rgb(88, 96, 132);"><br /></td> </tr> <tr> <td class="valueentry">Jun</td> <td style="background-color: rgb(97, 107, 146);"><br /></td> <td style="background-color: rgb(104, 114, 156);"><br /></td> <td style="background-color: rgb(89, 98, 134);"><br /></td> <td style="background-color: rgb(95, 104, 143);"><br /></td> <td style="background-color: rgb(103, 113, 154);"><br /></td> <td style="background-color: rgb(102, 112, 153);"><br /></td> <td style="background-color: rgb(99, 109, 149);"><br /></td> <td style="background-color: rgb(135, 148, 202);"><br /></td> <td style="background-color: rgb(170, 187, 255);"><br /></td> <td style="background-color: rgb(71, 79, 107);"><br /></td> <td style="background-color: rgb(82, 90, 123);"><br /></td> <td style="background-color: rgb(76, 83, 114);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(66, 73, 100);"><br /></td> <td style="background-color: rgb(68, 75, 102);"><br /></td> <td style="background-color: rgb(71, 79, 107);"><br /></td> <td style="background-color: rgb(98, 108, 148);"><br /></td> <td style="background-color: rgb(87, 96, 131);"><br /></td> <td style="background-color: rgb(105, 116, 158);"><br /></td> <td style="background-color: rgb(95, 104, 142);"><br /></td> <td style="background-color: rgb(82, 90, 123);"><br /></td> <td style="background-color: rgb(79, 87, 119);"><br /></td> <td style="background-color: rgb(78, 85, 117);"><br /></td> <td style="background-color: rgb(78, 86, 117);"><br /></td> <td style="background-color: rgb(72, 80, 109);"><br /></td> <td style="background-color: rgb(81, 90, 122);"><br /></td> <td style="background-color: rgb(82, 90, 123);"><br /></td> <td style="background-color: rgb(84, 93, 127);"><br /></td> <td style="background-color: rgb(80, 89, 121);"><br /></td> <td style="background-color: rgb(82, 90, 123);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> </tr> <tr> <td class="valueentry">Jul</td> <td style="background-color: rgb(72, 79, 109);"><br /></td> <td style="background-color: rgb(74, 81, 111);"><br /></td> <td style="background-color: rgb(136, 150, 204);"><br /></td> <td style="background-color: rgb(103, 114, 155);"><br /></td> <td style="background-color: rgb(99, 109, 149);"><br /></td> <td style="background-color: rgb(76, 83, 114);"><br /></td> <td style="background-color: rgb(75, 83, 113);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(74, 81, 111);"><br /></td> <td style="background-color: rgb(80, 88, 120);"><br /></td> <td style="background-color: rgb(94, 103, 141);"><br /></td> <td style="background-color: rgb(75, 82, 112);"><br /></td> <td style="background-color: rgb(77, 84, 115);"><br /></td> <td style="background-color: rgb(76, 84, 115);"><br /></td> <td style="background-color: rgb(64, 71, 96);"><br /></td> <td style="background-color: rgb(36, 39, 54);"><br /></td> <td style="background-color: rgb(55, 60, 82);"><br /></td> <td style="background-color: rgb(73, 80, 109);"><br /></td> <td style="background-color: rgb(70, 78, 106);"><br /></td> <td style="background-color: rgb(66, 72, 99);"><br /></td> <td style="background-color: rgb(66, 73, 100);"><br /></td> <td style="background-color: rgb(65, 72, 98);"><br /></td> <td style="background-color: rgb(68, 75, 102);"><br /></td> <td style="background-color: rgb(86, 95, 130);"><br /></td> <td style="background-color: rgb(122, 135, 184);"><br /></td> <td style="background-color: rgb(90, 99, 135);"><br /></td> <td style="background-color: rgb(86, 95, 129);"><br /></td> <td style="background-color: rgb(75, 83, 113);"><br /></td> <td style="background-color: rgb(68, 75, 102);"><br /></td> <td style="background-color: rgb(89, 98, 134);"><br /></td> <td style="background-color: rgb(79, 87, 119);"><br /></td> </tr> <tr> <td class="valueentry">Aug</td> <td style="background-color: rgb(80, 88, 120);"><br /></td> <td style="background-color: rgb(74, 82, 112);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(62, 68, 93);"><br /></td> <td style="background-color: rgb(68, 75, 103);"><br /></td> <td style="background-color: rgb(62, 68, 93);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(73, 81, 110);"><br /></td> <td style="background-color: rgb(75, 83, 113);"><br /></td> <td style="background-color: rgb(75, 82, 112);"><br /></td> <td style="background-color: rgb(81, 89, 122);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(61, 67, 91);"><br /></td> <td style="background-color: rgb(60, 66, 91);"><br /></td> <td style="background-color: rgb(77, 84, 115);"><br /></td> <td style="background-color: rgb(80, 88, 120);"><br /></td> <td style="background-color: rgb(76, 83, 114);"><br /></td> <td style="background-color: rgb(76, 84, 114);"><br /></td> <td style="background-color: rgb(62, 68, 93);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(80, 89, 121);"><br /></td> <td style="background-color: rgb(73, 80, 110);"><br /></td> <td style="background-color: rgb(61, 67, 92);"><br /></td> <td style="background-color: rgb(60, 66, 90);"><br /></td> <td style="background-color: rgb(68, 75, 102);"><br /></td> <td style="background-color: rgb(92, 102, 139);"><br /></td> <td style="background-color: rgb(85, 93, 127);"><br /></td> <td style="background-color: rgb(81, 89, 122);"><br /></td> <td style="background-color: rgb(64, 71, 97);"><br /></td> <td style="background-color: rgb(38, 42, 58);"><br /></td> <td style="background-color: rgb(65, 72, 98);"><br /></td> </tr> <tr> <td class="valueentry">Sep</td> <td style="background-color: rgb(69, 75, 103);"><br /></td> <td style="background-color: rgb(63, 70, 95);"><br /></td> <td style="background-color: rgb(69, 76, 103);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(69, 75, 103);"><br /></td> <td style="background-color: rgb(56, 62, 84);"><br /></td> <td style="background-color: rgb(55, 60, 82);"><br /></td> <td style="background-color: rgb(64, 71, 96);"><br /></td> <td style="background-color: rgb(60, 66, 90);"><br /></td> <td style="background-color: rgb(60, 66, 90);"><br /></td> <td style="background-color: rgb(64, 71, 96);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(110, 121, 165);"><br /></td> <td style="background-color: rgb(79, 87, 118);"><br /></td> <td style="background-color: rgb(90, 99, 135);"><br /></td> <td style="background-color: rgb(91, 100, 136);"><br /></td> <td style="background-color: rgb(84, 92, 126);"><br /></td> <td style="background-color: rgb(76, 83, 114);"><br /></td> <td style="background-color: rgb(74, 81, 111);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(65, 71, 97);"><br /></td> <td style="background-color: rgb(51, 56, 77);"><br /></td> <td style="background-color: rgb(56, 61, 84);"><br /></td> <td style="background-color: rgb(82, 90, 123);"><br /></td> <td style="background-color: rgb(82, 90, 124);"><br /></td> <td style="background-color: rgb(87, 96, 130);"><br /></td> <td style="background-color: rgb(68, 75, 102);"><br /></td> <td style="background-color: rgb(63, 69, 95);"><br /></td> <td style="background-color: rgb(66, 73, 100);"><br /></td> <td style="background-color: rgb(64, 70, 96);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> </tr><tr> <td class="valueentry">Oct</td> <td style="background-color: rgb(65, 72, 98);"><br /></td> <td style="background-color: rgb(77, 85, 116);"><br /></td> <td style="background-color: rgb(68, 75, 102);"><br /></td> <td style="background-color: rgb(69, 76, 103);"><br /></td> <td style="background-color: rgb(60, 66, 90);"><br /></td> <td style="background-color: rgb(66, 73, 100);"><br /></td> <td style="background-color: rgb(61, 67, 92);"><br /></td> <td style="background-color: rgb(65, 71, 97);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(76, 84, 114);"><br /></td> <td style="background-color: rgb(68, 75, 102);"><br /></td> <td style="background-color: rgb(79, 87, 119);"><br /></td> <td style="background-color: rgb(60, 66, 90);"><br /></td> <td style="background-color: rgb(60, 66, 91);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(132, 146, 199);"><br /></td> <td style="background-color: rgb(75, 82, 112);"><br /></td> <td style="background-color: rgb(77, 85, 116);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(80, 88, 120);"><br /></td> <td style="background-color: rgb(69, 76, 103);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(64, 70, 96);"><br /></td> <td style="background-color: rgb(68, 75, 103);"><br /></td> <td style="background-color: rgb(56, 62, 84);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(76, 83, 114);"><br /></td> <td style="background-color: rgb(74, 81, 111);"><br /></td> </tr> <tr> <td class="valueentry">Nov</td> <td style="background-color: rgb(73, 80, 109);"><br /></td> <td style="background-color: rgb(73, 80, 109);"><br /></td> <td style="background-color: rgb(70, 78, 106);"><br /></td> <td style="background-color: rgb(65, 71, 97);"><br /></td> <td style="background-color: rgb(76, 84, 115);"><br /></td> <td style="background-color: rgb(123, 136, 185);"><br /></td> <td style="background-color: rgb(76, 84, 114);"><br /></td> <td style="background-color: rgb(68, 74, 102);"><br /></td> <td style="background-color: rgb(59, 65, 89);"><br /></td> <td style="background-color: rgb(68, 75, 102);"><br /></td> <td style="background-color: rgb(68, 74, 102);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(80, 89, 121);"><br /></td> <td style="background-color: rgb(71, 78, 106);"><br /></td> <td style="background-color: rgb(65, 72, 98);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(60, 66, 91);"><br /></td> <td style="background-color: rgb(67, 74, 101);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(66, 73, 100);"><br /></td> <td style="background-color: rgb(64, 70, 96);"><br /></td> <td style="background-color: rgb(60, 66, 91);"><br /></td> <td style="background-color: rgb(61, 67, 91);"><br /></td> <td style="background-color: rgb(55, 61, 83);"><br /></td> <td style="background-color: rgb(62, 68, 93);"><br /></td> <td style="background-color: rgb(69, 76, 103);"><br /></td> <td style="background-color: rgb(60, 66, 90);"><br /></td> <td style="background-color: rgb(63, 69, 94);"><br /></td> <td style="background-color: rgb(60, 66, 90);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> </tr> <tr> <td class="valueentry">Dec</td> <td style="background-color: rgb(61, 67, 91);"><br /></td> <td style="background-color: rgb(65, 71, 97);"><br /></td> <td style="background-color: rgb(66, 73, 99);"><br /></td> <td style="background-color: rgb(78, 86, 117);"><br /></td> <td style="background-color: rgb(63, 70, 95);"><br /></td> <td style="background-color: rgb(66, 73, 99);"><br /></td> <td style="background-color: rgb(70, 77, 105);"><br /></td> <td style="background-color: rgb(60, 66, 90);"><br /></td> <td style="background-color: rgb(62, 69, 94);"><br /></td> <td style="background-color: rgb(64, 71, 97);"><br /></td> <td style="background-color: rgb(72, 79, 108);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(73, 80, 110);"><br /></td> <td style="background-color: rgb(128, 141, 193);"><br /></td> <td style="background-color: rgb(76, 83, 114);"><br /></td> <td style="background-color: rgb(64, 70, 96);"><br /></td> <td style="background-color: rgb(65, 72, 98);"><br /></td> <td style="background-color: rgb(69, 76, 104);"><br /></td> <td style="background-color: rgb(68, 75, 103);"><br /></td> <td style="background-color: rgb(65, 71, 97);"><br /></td> <td style="background-color: rgb(58, 64, 87);"><br /></td> <td style="background-color: rgb(55, 60, 82);"><br /></td> <td style="background-color: rgb(63, 69, 94);"><br /></td> <td style="background-color: rgb(64, 70, 96);"><br /></td> <td style="background-color: rgb(59, 65, 89);"><br /></td> <td style="background-color: rgb(53, 58, 79);"><br /></td> <td style="background-color: rgb(59, 65, 89);"><br /></td> <td style="background-color: rgb(62, 68, 93);"><br /></td> <td style="background-color: rgb(73, 81, 110);"><br /></td> <td style="background-color: rgb(56, 62, 85);"><br /></td> <td style="background-color: rgb(54, 60, 82);"><br /></td> </tr> <tr> <td><br /></td> <td class="keyentry">01</td> <td class="keyentry">02</td> <td class="keyentry">03</td> <td class="keyentry">04</td> <td class="keyentry">05</td> <td class="keyentry">06</td> <td class="keyentry">07</td> <td class="keyentry">08</td> <td class="keyentry">09</td> <td class="keyentry">10</td> <td class="keyentry">11</td> <td class="keyentry">12</td> <td class="keyentry">13</td> <td class="keyentry">14</td> <td class="keyentry">15</td> <td class="keyentry">16</td> <td class="keyentry">17</td> <td class="keyentry">18</td> <td class="keyentry">19</td> <td class="keyentry">20</td> <td class="keyentry">21</td> <td class="keyentry">22</td> <td class="keyentry">23</td> <td class="keyentry">24</td> <td class="keyentry">25</td> <td class="keyentry">26</td> <td class="keyentry">27</td> <td class="keyentry">28</td> <td class="keyentry">29</td> <td class="keyentry">30</td> <td class="keyentry">31</td> </tr> </tbody> </table><br /><br /><h4>Current stats</h4><table border="0" cellpadding="0" cellspacing="0"> <tbody> <tr> <td class="valueentry">Jan</td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(19, 20, 28);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(19, 21, 28);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(20, 22, 31);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(10, 11, 15);"><br /></td> <td style="background-color: rgb(10, 11, 15);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(19, 20, 28);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(15, 17, 23);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(24, 27, 36);"><br /></td> <td style="background-color: rgb(21, 23, 31);"><br /></td> <td style="background-color: rgb(20, 22, 31);"><br /></td> </tr> <tr> <td class="valueentry">Feb</td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(19, 21, 28);"><br /></td> <td style="background-color: rgb(19, 20, 28);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(24, 26, 36);"><br /></td> <td style="background-color: rgb(32, 35, 49);"><br /></td> <td style="background-color: rgb(21, 23, 31);"><br /></td> <td style="background-color: rgb(21, 24, 32);"><br /></td> <td style="background-color: rgb(7, 8, 11);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> </tr> <tr> <td class="valueentry">Mar</td> <td style="background-color: rgb(26, 28, 39);"><br /></td> <td style="background-color: rgb(22, 25, 34);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(21, 23, 31);"><br /></td> <td style="background-color: rgb(20, 22, 31);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(20, 22, 31);"><br /></td> <td style="background-color: rgb(26, 29, 39);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(20, 22, 31);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(19, 21, 28);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(19, 20, 28);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(20, 22, 31);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(23, 26, 35);"><br /></td> <td style="background-color: rgb(23, 26, 35);"><br /></td> <td style="background-color: rgb(23, 25, 35);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> </tr> <tr> <td class="valueentry">Apr</td> <td style="background-color: rgb(25, 27, 38);"><br /></td> <td style="background-color: rgb(30, 33, 46);"><br /></td> <td style="background-color: rgb(36, 40, 55);"><br /></td> <td style="background-color: rgb(25, 27, 37);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(26, 29, 39);"><br /></td> <td style="background-color: rgb(26, 28, 39);"><br /></td> <td style="background-color: rgb(25, 27, 37);"><br /></td> <td style="background-color: rgb(23, 25, 34);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(25, 28, 38);"><br /></td> <td style="background-color: rgb(25, 28, 38);"><br /></td> <td style="background-color: rgb(25, 27, 37);"><br /></td> <td style="background-color: rgb(22, 25, 34);"><br /></td> <td style="background-color: rgb(23, 26, 35);"><br /></td> <td style="background-color: rgb(26, 29, 39);"><br /></td> <td style="background-color: rgb(22, 25, 34);"><br /></td> <td style="background-color: rgb(24, 27, 37);"><br /></td> <td style="background-color: rgb(23, 26, 35);"><br /></td> <td style="background-color: rgb(26, 28, 39);"><br /></td> <td style="background-color: rgb(28, 31, 42);"><br /></td> <td style="background-color: rgb(27, 30, 41);"><br /></td> <td style="background-color: rgb(30, 34, 46);"><br /></td> <td style="background-color: rgb(35, 39, 53);"><br /></td> <td style="background-color: rgb(30, 34, 46);"><br /></td> <td style="background-color: rgb(25, 27, 38);"><br /></td> <td style="background-color: rgb(24, 27, 37);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> </tr> <tr> <td class="valueentry">May</td> <td style="background-color: rgb(28, 30, 42);"><br /></td> <td style="background-color: rgb(25, 28, 38);"><br /></td> <td style="background-color: rgb(25, 28, 38);"><br /></td> <td style="background-color: rgb(36, 40, 55);"><br /></td> <td style="background-color: rgb(26, 28, 39);"><br /></td> <td style="background-color: rgb(25, 27, 37);"><br /></td> <td style="background-color: rgb(24, 27, 37);"><br /></td> <td style="background-color: rgb(26, 29, 40);"><br /></td> <td style="background-color: rgb(38, 42, 57);"><br /></td> <td style="background-color: rgb(22, 24, 34);"><br /></td> <td style="background-color: rgb(23, 26, 35);"><br /></td> <td style="background-color: rgb(27, 30, 41);"><br /></td> <td style="background-color: rgb(24, 27, 36);"><br /></td> <td style="background-color: rgb(22, 25, 34);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(23, 26, 35);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(27, 29, 40);"><br /></td> <td style="background-color: rgb(26, 29, 39);"><br /></td> <td style="background-color: rgb(25, 27, 38);"><br /></td> <td style="background-color: rgb(29, 32, 44);"><br /></td> <td style="background-color: rgb(24, 27, 37);"><br /></td> <td style="background-color: rgb(24, 26, 36);"><br /></td> <td style="background-color: rgb(26, 28, 39);"><br /></td> <td style="background-color: rgb(24, 27, 37);"><br /></td> <td style="background-color: rgb(23, 25, 34);"><br /></td> <td style="background-color: rgb(24, 27, 37);"><br /></td> <td style="background-color: rgb(25, 27, 37);"><br /></td> <td style="background-color: rgb(25, 28, 38);"><br /></td> <td style="background-color: rgb(23, 25, 35);"><br /></td> </tr> <tr> <td class="valueentry">Jun</td> <td style="background-color: rgb(25, 28, 38);"><br /></td> <td style="background-color: rgb(27, 30, 41);"><br /></td> <td style="background-color: rgb(23, 26, 35);"><br /></td> <td style="background-color: rgb(25, 27, 37);"><br /></td> <td style="background-color: rgb(27, 30, 41);"><br /></td> <td style="background-color: rgb(27, 29, 40);"><br /></td> <td style="background-color: rgb(26, 29, 39);"><br /></td> <td style="background-color: rgb(35, 39, 53);"><br /></td> <td style="background-color: rgb(45, 49, 67);"><br /></td> <td style="background-color: rgb(170, 187, 255);"><br /></td> <td style="background-color: rgb(63, 69, 95);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(19, 20, 28);"><br /></td> <td style="background-color: rgb(26, 28, 39);"><br /></td> <td style="background-color: rgb(23, 25, 34);"><br /></td> <td style="background-color: rgb(28, 30, 42);"><br /></td> <td style="background-color: rgb(25, 27, 37);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(21, 23, 31);"><br /></td> <td style="background-color: rgb(20, 22, 31);"><br /></td> <td style="background-color: rgb(20, 22, 31);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> </tr> <tr> <td class="valueentry">Jul</td> <td style="background-color: rgb(19, 21, 28);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(36, 39, 54);"><br /></td> <td style="background-color: rgb(27, 30, 41);"><br /></td> <td style="background-color: rgb(26, 29, 39);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(25, 27, 37);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(9, 10, 14);"><br /></td> <td style="background-color: rgb(14, 16, 21);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(22, 25, 34);"><br /></td> <td style="background-color: rgb(32, 35, 48);"><br /></td> <td style="background-color: rgb(23, 26, 35);"><br /></td> <td style="background-color: rgb(22, 25, 34);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(23, 26, 35);"><br /></td> <td style="background-color: rgb(21, 23, 31);"><br /></td> </tr> <tr> <td class="valueentry">Aug</td> <td style="background-color: rgb(21, 23, 31);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(16, 18, 24);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(16, 18, 24);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(21, 23, 31);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(16, 18, 24);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(15, 17, 23);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(24, 27, 36);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(10, 11, 15);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> </tr> <tr> <td class="valueentry">Sep</td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(14, 16, 22);"><br /></td> <td style="background-color: rgb(14, 16, 21);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(15, 17, 23);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(29, 32, 44);"><br /></td> <td style="background-color: rgb(20, 23, 31);"><br /></td> <td style="background-color: rgb(23, 26, 35);"><br /></td> <td style="background-color: rgb(24, 26, 36);"><br /></td> <td style="background-color: rgb(22, 24, 33);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(13, 15, 20);"><br /></td> <td style="background-color: rgb(14, 16, 22);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(21, 24, 32);"><br /></td> <td style="background-color: rgb(23, 25, 34);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> </tr> <tr> <td class="valueentry">Oct</td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(20, 22, 31);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(15, 17, 23);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(16, 18, 24);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(21, 23, 31);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(35, 38, 52);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(21, 23, 31);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(14, 16, 22);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> </tr> <tr> <td class="valueentry">Nov</td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(17, 19, 25);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(32, 36, 49);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(15, 17, 23);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(18, 19, 27);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(21, 23, 32);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(18, 20, 28);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(14, 16, 22);"><br /></td> <td style="background-color: rgb(16, 18, 24);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(0, 0, 0);"><br /></td> </tr> <tr> <td class="valueentry">Dec</td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(17, 19, 25);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(20, 22, 31);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(16, 17, 24);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(19, 21, 28);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(34, 37, 51);"><br /></td> <td style="background-color: rgb(20, 22, 30);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(17, 19, 26);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(18, 20, 27);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(15, 17, 23);"><br /></td> <td style="background-color: rgb(14, 16, 21);"><br /></td> <td style="background-color: rgb(16, 18, 25);"><br /></td> <td style="background-color: rgb(17, 18, 25);"><br /></td> <td style="background-color: rgb(15, 17, 23);"><br /></td> <td style="background-color: rgb(14, 15, 21);"><br /></td> <td style="background-color: rgb(15, 17, 23);"><br /></td> <td style="background-color: rgb(16, 18, 24);"><br /></td> <td style="background-color: rgb(19, 21, 29);"><br /></td> <td style="background-color: rgb(15, 16, 22);"><br /></td> <td style="background-color: rgb(14, 16, 21);"><br /></td> </tr> <tr> <td><br /></td> <td class="keyentry">01</td> <td class="keyentry">02</td> <td class="keyentry">03</td> <td class="keyentry">04</td> <td class="keyentry">05</td> <td class="keyentry">06</td> <td class="keyentry">07</td> <td class="keyentry">08</td> <td class="keyentry">09</td> <td class="keyentry">10</td> <td class="keyentry">11</td> <td class="keyentry">12</td> <td class="keyentry">13</td> <td class="keyentry">14</td> <td class="keyentry">15</td> <td class="keyentry">16</td> <td class="keyentry">17</td> <td class="keyentry">18</td> <td class="keyentry">19</td> <td class="keyentry">20</td> <td class="keyentry">21</td> <td class="keyentry">22</td> <td class="keyentry">23</td> <td class="keyentry">24</td> <td class="keyentry">25</td> <td class="keyentry">26</td> <td class="keyentry">27</td> <td class="keyentry">28</td> <td class="keyentry">29</td> <td class="keyentry">30</td> <td class="keyentry">31</td> </tr> </tbody> </table><br /><h4>My flickr views...</h4><br /><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuS6RjrvpK_ZA7KCG8hpWktVLkAwskHmvWW2BB6e9IyEkqCWWmffdgzyFReM3ROrbObiHNnCrYjBWci7WzqXXMPnMrzETnM6qi6JepoPwUan6eXvxir5V5Atyp968Ud7VFJMPrOftMtq_M/s640/flickr-stats.png" /><br /><h3>Clarifications</h3> <p>Despite the title of the reddit entry, I have nothing against Christians. Further, I count myself as one. So if you really want to take a piss at people who are calling Christians evil, don't aim at me. Thanks.</p><br /><p>Moreover, I don't think those people were evil, or even horrible (though I did use that word in the heat of the moment). A loved one had just died, and <i>everyone</i> was in pain. Death <b>SUCKS</b>, and it was a bad situation no matter how you slice it. I don't hate those people for trying to erase my friend though, I pity them. I pity the fact that they turned down the chance to mourn the passing of their loved one by crying on another loved one's shoulder. I pity the fact that they didn't get nearly the closure that my friend did. I pity them for willingly becoming victims of their own grief.</p><br /><p>The rest is between them and God.</p>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com4tag:blogger.com,1999:blog-1234953837283832282.post-53316879033671677252009-06-05T12:41:00.000-07:002010-05-13T16:25:59.525-07:00Wait, did you seriously just say that?<p>I grew up in California, and my state is pretty well true to it's reputation of being....less conservative than average. Granted, I've been harassed, shunned, even outright beaten over being queer, but I always thought that when it comes to certain things, especially family, there's a line that just isn't crossed. This is why the events of the past weekend so thoroughly amazed me.</p><p>Several months ago, when she told me that her mother had Cancer, I promised my kinda-ex-nevermind-it's-complicated-girlfriend that when the time came, I'd jump on a plane and fly out to the Midwest to help her bury her mom. So it was, that when she called me from Toronto (she was in the middle of a business trip), I booked a one-way flight (we weren't sure how long things would take), and flew off to Cleveland to meet her and offer my sympathies.</p><p>Death is hard, no matter how prepared for the end you are, but for my friend it was doubly difficult. For the past four years, she'd only rarely been able to see her mom, due to both distance and conflict between her and other people in her mother's family. We'd hoped that in the darkness of losing a loved one, those hard feelings and prejudices could be set aside, that family could come together for just one day to share grief and say good-bye. We were unbelievably wrong...</p><p>We landed together at CLE on friday night and headed over to her dad's house (her parents had been long divorced). He greeted us, filled her in on the past few months and her mother's last days, and we left feeling the weight of the occasion, but ready to grieve. On the way out, her sister called saying, "Some of the family have asked that you don't show up to the viewing. They say you can come into the funeral home for a few minutes afterward, but not during the general viewing."</p><p>I know, take a minute to absorb that statement. Who are these family members? Vague mumbles always answered this question, but they were certainly not going to tolerate my friend's presence. We resolved to call around the next day...</p><p>It was Saturday and the obituary had come out. Anachronistic newsprint mourned the loss of a sainted mother to my friend's sister, "special mother" to my friend's ex-spouse, but no mention of my friend. <b>They had simply erased her</b>, with a nod to her ex as a special kind of insult.</p><p>My friend called her sister back, wanting to understand how so much animosity could survive and flourish like that. She wasn't going to be erased, so she told her sister that the family could just deal with the fact that she <i>would</i> be present at the viewing. It was her mother that'd died, and she intended to say good-bye. A few hours later, news had filtered through to her step-father who called and delivered a matter-of-fact edict. "Here's your options, and I'm only going to say this once. You can come at 7, after everyone else is done, and take your 15 minutes to say good-bye, or you can not come at all. I'll have a cop at the door to make sure of that. *click*"</p><p>Yes, you heard that right, he was hiring an off-duty police officer to keep her out of her own mother's services.</p><p>And before you ask, yes, her step-father had that legal right, as the funeral home was private property contracted by him. So it was, that the next day, we (My friend, another friend of her's, and myself), drove to a shopping center near the funeral home and parked where our cars couldn't be identified and vandalized, and we walked to the funeral home at the official start time. Sure enough, a uniformed police officer stood guard at the entrace.</p><br /><h3>Arriving</h3> <p>We sent in the third member of our party, as she comes off much less threatening than I, and hadn't been explicitly told not to show up. She went inside, and formally asked the funeral home's director (with the step-father standing nearby), if we could be allowed inside. "Oh my God! They're here! They're here!", I wasn't present, of course, but I'm told the step-father reacted like we'd just stormed the beach at Normandy. "Those people are not to be allowed inside under any circumstances!", the funeral director could only agree with his client's request, so our other friend walked back to the sidewalk, where we sat down and initiated project Ghandi.</p><p>Okay, we didn't have any clever name for it at the time, but the premise was simple enough. We were in a labor state where the right to strike is sacrosanct. Being a public easement, we knew we were in our legal rights to remain there. This was the crux moment. Although we had legal rights, the police officer could have trumped up a reason to remove us for the duration of the ceremony, and we'd have lost our gamble completely, but he just stood at the door, showing no hint of emotion of sympathies for either side. A credit to his uniform.</p><p>I'm told, from those who were inside, that the step-father was yelling at the cop to have us removed and/or arrested. The cop answered that we were on public grounds and not causing a disturbance, so there was nothing he could do. I'm told he would get up every 15 minutes to look out the window and see if we were still there. We were. I'm told he called the police department to have additional officers sent around to "round us up", each new arrival told him the same story, no laws were being broken.</p><br /><h3>The challenge</h3> <p>About half an hour into the services, we were approached by the ex, who calmly flipped through the binder full of memories my friend had brought with her. They hugged, but didn't say much... What can be said at that point? Then my friend's ex-mother-in-law came over and just as I thought we were going to see another calm, but sad episode of sharing... "<i>You know, you brought this on yourself.</i>"</p><p>I felt numb for a moment. I couldn't possibly have heard that come out of her mouth, not with my friend's mother lying in a casket 20 feet away... My friend managed to utter, "No", then turned away and began crying. "Yes, you did. You brought this on yourself." Our other friend interjected, "That's really not necessary..." "Yes it is", the mother in law replied, "this situation is because of her actions..."</p><br /><p>She didn't get to finish that sentence. Something inside me snapped and a wellspring of rage came over me. I stood directly in front of the MiL, and found the most even temped voice I could manage, "Ma'am, I understand that you've just lost someone dear to you. I understand that you're in pain, and I understand that you think you're doing good, so I'm going to try to say this in the nicest way possible.", from here, my decorum ran out of rehearsed speeches and my rage took over, "You are an unchristian, bad...bad... Horrible person."</p><p>"No she isn't.", I heard from my firend's ex, but I ignored that. I stared at, and through her MiL, almost wanting a physical confrontation. "Sara..." The plaintive caution of our other friend snapped me out of my anger and I stepped away, andrenaline thundering through my blood. The MiL gave up trying to berate my friend and began to walk away. "I'm sorry", I offered, "That was unnecessary and uncalled for, I apologize." I don't remember much of the next several minutes, as my stress level slowly calmed down.</p><p>"Thank you", my friend told me. "I needed some strength right there and that's exactly what you gave me." After a moment she smiled, "You did unload on her a bit though...". "Yeah", I smiled back, "I was killing a few of my own demons there at the same time, might've over-estimated the firepower...</p><p>Soon afterward, her step-grandmother came around. "Why don't you just leave? You've making a spectacle of your mother's viewing!" She continued and I held myself back as long as I could. My friend held her own against this onslaught much better, continually drawing the conversation back to her mother, and trying to remember the best of her mother and focus on the mourning of her passing. The step-grandmother continued to catalouge all the (made-up) offenses my friend had allegedly committed against the family (all of which were thinly veiled reproachments for her queerness), until I spoke up, asking her if Jesus taught forgiveness, "Yes, BUT... we someone, when you have, when the person that has brought you into this world..." Honestly, even though I captured video of the entire event, I couldn't tell you for certain what she said as her argument descended and crashed into incoherency. Eventually she threw her hands up and walked away. Our other friend looked at me a little dumb-founded... "That... Was almost a thing of beauty the way she fell apart..." Yeah, I felt a little guilty at the efficacy of it too....</p><br /><h3>Redemption</h3> <p>Next up, was my friend's cousin. "What are y'all doing out here? Aren't you coming inside?" We told him what the step-father had said. His curious smile fell into disbelief, then anger rose in his face until his aura throbbed a dark crimson red. "That's ridiculous, I'll be right back." He stormed inside, and I'm told he raised holy hell on our behalf. Shortly thereafter, several more family members came out until there was all but a line queuing up around my friend to console her and share grief over her mother's death. For myself, I began to worry that we'd start to qualify as an unruly mob and give the police something to raise a fuss about. Nothing happened though, I think I even saw the cop smile.</p><h3>Vindication</h3> <p>Eventually her mother's nurse, who was no part of the family, pulled my friend aside. "I only knew your mother for a few months, but she told me things about you in that time. Good things.", and she proceeded to list off things that she couldn't have made up. Things that proved to my friend that her mother really had loved her at the end, but was too afraid to show it.</p><p>My friend visibly brightened after that. As though she'd let go of something sour and offal. A few more hours passed in the pleasant late-spring weather. People came out and went back in, arrived and departed, noone else said an unkind word after her step-grandmother. Finally, when the funeral home was nearly empty, the cop approached us, "The funeral director's told me it's okay for you to come in now. I'm sorry for your loss." We thanked him and shook his hand and made our way inside. The step-father and other naysayers were nowhere to be seen, but there at the end of an array of fold-out chairs, was my friend's mother.</p><br /><h3>A poetic ending</h3> <p>I won't go into the details of her weeping, but she cried out the last of her grief. After hours of sharing, but not having access to the body, my friend had excised the worst of her grief already. She had prepared herself and found the peace she needed to see her mother's still form. For all her step-father's machinations and attempts to rob her of her mother and her place in her family, my friend was able to grieve her mother's passing in a way that couldn't possibly have been any better or more complete. If we had been inside the whole time, the shock of seeing her with so many unresolved things would have been devastating. If we had simply taken orders and not shown up until the last few minutes, there would not have been nearly enough time, and there would have been no family to lean on. By standing on the sidewalk, we took away that power, we reclaimed it as our own, and we amplified it even further. We said good-bye.</p><p>And the naysayers? They just looked like monsters. Love: 1, Hate: 0; Schadenfreude, I haz it.</p>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com8tag:blogger.com,1999:blog-1234953837283832282.post-64817540905185259022009-01-08T23:16:00.000-08:002010-08-04T10:48:18.226-07:00Yahoo!, doing something right!<p>I love it when Yahoo! manages to just <b>get something right</b>. Checkout the awesome sauce of a <a href="http://mediaplayer.yahoo.com/">media player</a> widget. By doing <i>nothing more</i> than linking to an external script. All three of these P3 podcasts become magically playable directly from my blog.</p> <ul><li><a href="http://devzone.zend.com/content/audio/zendcon_sessions/zendcon_sessions_podcast_022.mp3">ZendCon 2008, PHP Extension Writing</a> (Apologies for the gay-disco lead-in)</li><li><a href="http://c7y.phparch.com/c/attachment/1/p3,20060602/external/20060602.mp3">Interview following php|tek 2006</a></li><li><a href="http://c7y.phparch.com/c/attachment/1/p3,20070531/external/20070531.mp3">Guest hosting for the newscast</a></li><li><a href="http://c7y.phparch.com/c/attachment/1/p3,20071018/external/20071018.mp3">Ramblecast 2.0</a></li></ul> <p>Click the play buttons, but don't forget to notice the little slide-out player in the lower-left corner. Swanky, n'est-ce pas?</p><script src="http://mediaplayer.yahoo.com/js" type="text/javascript"></script>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-22200634547456996882008-12-30T20:49:00.000-08:002010-05-13T16:16:37.859-07:002008 Lookback<p>It's been a helluva year... No, seriously, it's been... unique.</p><br /><p>Last <b>January</b>, I started off the year with a presentation at <a href="http://geeksessions.com/">geekSessions-1.3</a>, along with some associated "hanging-out" in San Francisco (a city I normally avoid, because I dislike large crowds of people). I also got a brand new insulin pump to replace the seven year old beast who's LCD display had finally died. The pump itself didn't die (kudos to Medtronic for that), just the display. I've enjoyed my new "pocket pancreas" about as much as the old one, though of course the incremental improvements to its design haven't gone unappreciated.</p><p><br /></p><p><b>February</b> was a fairly quiet month for me professionally. A mixup in some of my medications (not related to the new pump), left me really distracted and irritable. The medications have been sorted, but I got to <i>like</i> the fear I induced in my coworker's eyes to the point that I keep up the show for appearances sake. :)</p> <p><b>March</b> was a flurry of activity getting things sorted at home and at work so that I could take a much needed month off in <b>April</b>. This month would ultimately turn into a mere three weeks since I refuse to relax, but it was time well spent anyway. During <b>March</b> I met two new friends who I've since come to appreciate deeply, and in <b>April</b> I got to spend a lot of time with my grandma; Something I do far too little of.</p><br /><p><b>May</b> saw the launch of <a href="http://developer.search.yahoo.com/">Yahoo! Search Monkey</a> move into full swing, bringing a lot of time and effort by the entire Search team along with it. As part of the launch, we held a developer event in Sunnyvale, and I learned that if you blog <strike title="wow, bad choice of words...">long and hard</strike> enough, random strangers will eventually <a href="http://saragolemon.blogspot.com/2008/05/i-have-officially-arrived.html">show you naked pictures of themselves</a>.</p><br /><p><b>June</b> was a hard month to handle. My wife of more than 10 years got herself a 1 bedroom apartment and moved out. We're both feeling the repercussions of that, and while reconciliation is a possibility, we really haven't fixed any of the things were wrong with our relationship. See, we're just as F-ed up as a straight couple, but with twice the estrogen! It was also at this time that my grandfather's cancer took a turn for the worse and he found himself spending a great deal of time in ICU. His children and grandchildren did their best to avoid feeling useless as we waited outside him room, not sure if he was going to pull out of it...</p><br /><p>In <b>July</b>, my grandfather was feeling good enough that we threw a small party for my cousin's graduation, and brought him along for what we all knew was going to be his last summer. There was a lot of smiling outdoors and crying in the bathroom. During this party my grandfather pulled me aside and gave me a gift that... I'm not even sure he understood how much it meant to me... My sister took photos, and we all doted a bit more than we should have, but we built a new memory around him that we can carry with us.</p><br /><p>Having managed some pretty downer months so far this summer, I took a week at the end of <b>August</b> to take a <i>real</i> vacation to south Utah where I went hiking with four complete strangers I'd met over the internet. Hrmmm... desert... strangers... dangerous climbs? Not the most sane thing I've ever done, but I made some really good friendships that'll last for years to come, and if all goes well, I'll be taking another trip with them this coming June, back to Zion National Park.</p><br /><p><b>September</b> saw <a href="http://zendcon.com/">ZendCon08</a> and <a href="http://phpappalachia.org/">PHP Appalachia</a>, the latter of which spawned far too many anecdotes about the rowdiness of the PHP community. Where should I start? The semi-naked toxic hot tub? The jagged piece of glass that Chris tried to kill Ben with but got stuck in Cal's foot instead? The fire department we bribed with a cute blonde? Nonono, what happens in Pigeon Forge, stays in Pigeon Forge...</p><br /><p>In <b>October</b>, my grandfather fell ill again, holding on just long enough to say good-bye to his children and grandchildren. Sadly his great-grandchildren aren't likely to remember him very well, but thankfully I know that my last words to him were, "I love you", and at least he went quickly. We can all hope for so much when out time eventually comes.</p><br /><p><b>November</b> led to another rowdy conference get together. This time it was <a href="http://phparch.com/">php|works</a> in Atlanta, GA, and I dunno about you, but Marco <i>really</i> needs to stop letting me near the open bar. I'm not a frequent drinker, so my tolerance is really very low, and I do... unconventional things when I've had my ninth vodka/cranberry... Doesn't hurt that the bartender was <i>very</i> generous... Oh well, at least I didn't throw up in the taxi on the way to Cal's going-away party. ((Note: I didn't say who did... assuming anyone did... which I didn't say either...))</p><br /><p>And oh yeah... the election... biggest sack full of mixed emotions in a long time... Okay, sure, let me get it out, YAY! Obama won. I have HUGE hopes for this guy, and given what I've seen of his transition process, I think he <i>might</i> just stand a chance of living up to his hype. <b>YES. WE. CAN.</b> There, now that that's out of my system, I can go back to being really dissapointed in the One Million californians who voted in favor of Proposition 8. I can go back to slamming my face into the desk whenever I hear the <b>FALSE</b> argument that equal marriage rights will lead to sodomy in the streets and pedophilia in the classrooms. I can go back to being disappointed by my fellow Christians who call homosexuality <a href="http://www.americamagazine.org/blog/entry.cfm?blog_id=2&amp;id=603830D3-1438-5036-4F5E0C8022A6327E">as significant a threat to humanity as global warming</a> and <a href="http://www.bgay.com/news/index.php?option=com_content&amp;task=view&amp;id=85&amp;Itemid=23">a threat to world peace</a>. That's not just any Christian saying that by the way, that's the ever-lovin' POPE.</p><br /> <p>And then here it is: <b>December</b>. My wife and I are talking about getting back together, though I'm not sure we're making progress... I'm doing my best to focus back on my work. I've gotten back into rock climbing, and am eagerly awaiting the snow season to start in earnest. I got up to my dad's for another Christmas with the family, strained by not having gramps around, but I can tell my dad's trying to step up and fill the patriarchal void that's been left. I've spent the past week fighting off a cold and baking cookies. Tomorrow I'll head back into the office and get some work done before having friends over for a new year's eve bash. On thursday, it'll all start over again...</p><br /><p>Happy new year.</p>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-51626807210271966582008-05-15T23:26:00.000-07:002010-05-13T16:11:18.966-07:00I have officially arrived<p>I was at the <a href="http://developer.yahoo.com/searchmonkey/event.html/">Search Monkey Developer Launch</a> tonight to answer questions and give demos to the attendees. The event itself was a nice success, but that's not what I'm writing about. What I'm writing about is one particular attendee. Apologies to this guy if I embarrass him, but he had to know I'd say something.</p><br /><p>After getting through the initial queue of developers asking about Search Monkey, I was approach by a tall young man:</p> <blockquote style="font-size: small"><pre>&lt;guy&gt; Hey, you're Sara Golemon, right?<br />&lt;me&gt; Yeah...<br />&lt;guy&gt; Nice to meet you, I really like your blog entries about how PHP works,<br /> and I bought your book!<br />&lt;me&gt; Oh fantastic! Are you enjoying it?<br />&lt;guy&gt; Yeah, it's really heavy, and I haven't had a chance to use a lot,<br /> but I was experimenting with writing my own libssh2 wrapper...<br /></pre></blockquote> <p> By this point, I'm suitably blushing. I'm easily swayed by compliments thanks to my lack of self-esteem. Then he takes a turn... </p><blockquote><pre>&lt;guy&gt; I almost emailed this to you, it's a photo my girlfriend took of me without warning...<br /> Let me just find it in my camera<br />* guy shows photo of himself lying in a water-filled bathtub, naked, covering his... bits<br /> with a copy of my book<br /></pre></blockquote> <p>I... um... well... okay... I can see why you wouldn't email that out of the blue.... Now, I try to think of myself as a fairly progressive, hard-to-phase sort. I can tell a dirty joke with the best of 'em, but this just... left me giggly for the next hour. Like, um...wow... okay... Thanks for sharing...</p><br /><p>Is this a sign? Is it a measure of notoriety when strangers show you naked photos of themselves at random tech gatherings?</p>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-23541859509128538442008-01-19T01:59:00.000-08:002011-04-14T01:10:30.409-07:00Understanding Opcodes<p>A blog reader (I have readers???) recently shared his wishlist, "<i>I'm trying to figure out how to show the opcodes like you have in your post...</i>". I promised that I'd throw something together, so here it is:</p><br /><h3>Slow down, wtf is an "Opcode"?</h3> <p>Short answer: It's the compiled form of a PHP script, similar in principle to Java bytecode or .NET's MSIL. For example, say you've got the following bit of PHP script:</p><blockquote style="font-size: small"> <pre>&lt;?php<br /> echo "Hello World";<br /> $a = 1 + 1;<br /> echo $a;<br /></pre> </blockquote> <p>PHP (and it's actual compiler/executor component, the Zend Engine) are going to go through a multi-stage process: </p><ol><li>Scanning (a.k.a. Lexing) - The human readable source code is turned into tokens.</li><li>Parsing - Groups of tokens are collected into simple, meaningful expressions.</li><li>Compilation - Expressions are translated into instruction (opcodes)</li><li>Execution - Opcode stacks are processed (one opcode at a time) to perform the scripted tasks.</li></ol> Side note: Opcode caches (like <a href="http://pecl.php.net/package/APC">APC</a>), let the engine perform the first three of these steps, then store that compiled form so that the next time a given script is used, it can use the stored version without having to redo those steps only to come to the same result. <br /><h3>Er... okay... can you elaborate a little? What's lexing? I thought superman put him in jail...</h3> <p>That's <b>Lex Luthor</b> you nit-wit! The most expedient way to explain lexing is by example. Take a look at the manual page for <a href="http://php.net/function.token-get-all">token_get_all()</a>, this gem is actually a wrapper around the Zend Engine's own language scanner. Play around with it a bit, and you'll notice that plugging the short script above into it will produce:</p> <blockquote style="font-size: small"> <pre>Array<br />(<br /> [0] => Array<br /> (<br /> [0] => 367<br /> [1] => &lt;?php<br /> )<br /> [1] => Array<br /> (<br /> [0] => 316<br /> [1] => echo<br /> )<br /> [2] => Array<br /> (<br /> [0] => 370<br /> [1] =><br /> )<br /> [3] => Array<br /> (<br /> [0] => 315<br /> [1] => "Hello World"<br /> )<br /> [4] => ;<br /> [5] => Array<br /> (<br /> [0] => 370<br /> [1] =><br /> )<br /> [6] => =<br /> [7] => Array<br /> (<br /> [0] => 370<br /> [1] =><br /> )<br /> [8] => Array<br /> (<br /> [0] => 305<br /> [1] => 1<br /> )<br /> [9] => Array<br /> (<br /> [0] => 370<br /> [1] =><br /> )<br /> [10] => +<br /> [11] => Array<br /> (<br /> [0] => 370<br /> [1] =><br /> )<br /> [12] => Array<br /> (<br /> [0] => 305<br /> [1] => 1<br /> )<br /> [13] => ;<br /> [14] => Array<br /> (<br /> [0] => 370<br /> [1] =><br /> )<br /> [15] => Array<br /> (<br /> [0] => 316<br /> [1] => echo<br /> )<br /> [16] => Array<br /> (<br /> [0] => 370<br /> [1] =><br /> )<br /> [17] => ;<br />)<br /></pre> </blockquote> <p>In the array returned by token_get_all(), you have two types of tokens: Single character non-label characters are returned as just that. The character that was found in the source file at that point. Everything else, from labels, to language constructs, to multi-character operators (like >>, +=, etc...) are returned as an array containing two elements: The token ID (which corresponds to T_* constants -- e.g. T_ECHO, T_STRING, T_VARIABLE, etc...), and the actual text which that token came from. What the engine actually gets is slightly more detailed than what you see in the output from token_get_all(), but not by much...</p> <h3>Okay, tokenization just breaks the script into bite-size pieces, how does parsing work then?</h3> <p>The first thing the parser does is throw away all whitespace (Unlike some other P* language...). From the reduced set of tokens, the engine looks for irreducible expressions. How many expressions do you see in the example above? Did you say three? <b>WRONG</b> There are three <i>statements</i>, but one of those statements is made of two distinct expressions. In the case of <span style="font-family: courier;">$a = 1 + 1;</span> the first expression is the addition, followed by the assignment to the variable as a second, distinct expression. All together our expression list is: </p><ol><li>echo a constant string</li><li>add two numbers together</li><li>store the result of the prior expression to a variable</li><li>echo a variable</li></ol> <h3>Hey! That's starting to sound familiar! Did I see that kind of description before?</h3> <p>Oh, you must mean my post about <a href="http://blog.golemon.com/2006/06/how-long-is-piece-of-string.html">strings</a> (plug). That's correct, because these expressions are exactly the pieces which go into making up oplines! Given the expression list we've just reached, the resulting opcodes look something like: </p><ul><li>ZEND_ECHO 'Hello World'</li><li>ZEND_ADD ~0 1 1</li><li>ZEND_ASSIGN !0 ~0</li><li>ZEND_ECHO !0</li></ul> <h3>What happened to $a? What's the difference between ~0 and !0?</h3> <p>Short answer: !0 <b>is</b> $a</p><br /><p>So here's the deal.... oplines have five principle parts:</p> <ul><li><b>Opcode</b> - Numeric identifier which distinguishes what the opline will do. This is what coresponds to ZEND_ECHO, ZEND_ADD, etc...</li><li><b>Result Node</b> - Most opcodes perform "non-terminal" actions. That is; after executing there's some result which can be consumed as an input to a later opline. The result node identifies what temporary location to place the result of the operation in.</li><li><b>Op1 Node</b> - One of two inputs to the given opcode. An input may be a constant zval, a reference to a previous result node, a simple variable (CV), or in some cases a "special" data element, such as a class definition. Note that an opcode may use both, one, or neither input node. (Some even use more, see ZEND_OPDATA)</li><li><b>Op2 Node</b> - Ditto</li><li><b>Extended Value</b> - Simple integer value used to differentiate specific behaviors of an overloaded opcode.</li></ul><br /><p>So obviously the nodes are the most complicated parts of an opline, here's the important parts of what they look like:</p> <ul><li><b>op_type</b> - One of IS_CONST, IS_TMP_VAR, IS_VAR, IS_UNUSED, or IS_CV</li><li><b>u</b> - A union of the following elements (the one which is used depends on the value of op_type): <ul><li><b>constant</b> (IS_CONST) - zval value. This node results which you include a literal value in your script, such as the 'Hello World' or 1 values in the example above.</li><li><b>var</b> (IS_VAR or IS_TMP_VAR or IS_CV) - Integer value corresponding to a temporary slot in a lookup table used by the engine.</li></ul> </li></ul> <p>Now let's look at the difference between those optypes, particularly with respect to u.var:</p> <ul><li><b>IS_TMP_VAR</b> - These ephemeral values are strictly for use by non-assignment non-terminal expressions. They don't support any refcounting because they're guaranteed not to be shared by any other variable. These are denoted in the examples I use on this site (and in VLD output) as tilde characters (~)</li><li><b>IS_VAR</b> - Usually the result of a ZEND_FETCH(_DIM|_OBJ)?_(R|W|RW), or one of the assignment opcodes (which are technically non-terminal expressions since they can be used as inputs to other expressions. Since these are tied to real variables, they have to respect reference counting and are passed about at an extra degree of indirection. They're stored in the same table though. These are denoted by the string symbol ($)</li><li><b>IS_CV</b> - "CV" stands for "Compiled Variables". These are basicly cached hash lookups for fetching simple variables from the local symbol table. Once a variable is actually looked up at runtime, it's stored at an extra level of indirection in an even faster lookup table using an index into a vector. That's what the number in this node denotes. These types of nodes are distinguished by a bang (!)</li></ul><br /><h3>Boggle... You...so lost me there...</h3> <p>Yeah, that explanation sort of got away from me didn't it? What can I clear up?</p><br /><h3>All I really want to know is how to translate some source code into an opcode..list...thingy...</h3> <p>Heh, okay... first off, that "opcode list thingy" is called an op_array, and you can generate those really easily using one of two PECL packages. You can use my <a href="http://pecl.php.net/package/parsekit">parsekit</a> package, which is useful for programmatic analysis of script compilation, but frankly... it's not what you're looking for and there's not much call for scripts analyzing other scripts anyway. I recommend Derick's <a href="http://pecl.php.net/package/vld">VLD (Vulcan Logic Disasembler)</a> which is what'll actually generate the kinds of opcode lists you'll see me use in blog posts.</p><br /><p>Once you've got it installed (it installs like any other PECL extension), you can run it with a command like the following:</p> <blockquote><pre>php -d vld.active=1 -d vld.execute=0 -f yourscript.php<br /></pre></blockquote> <p>Then sit back and watch the opcodes fly! Important note: Using -r with command line code may not work due to a quirk of the way the engire parses files in older versions of PHP (and with older versions of VLD). Be sure to put your script on disk and reference it using -f if -r doesn't work for you.</p><br /><h3>Holy schnikies! That's a lot of opcodes! How can I tell what they all do?</h3> <p>Take a look at Zend/zend_vm_def.h in your PHP source tree. In here you'll find a meta-definition of every single opcode used by the engine. Side note: It's used as a source for zend_vm_gen.php which generates the actual code file zend_vm_execute.h. How's that for chicken and egg? Every version of PHP since 5.1.0 has required PHP be already built in order to build it!</p>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com3tag:blogger.com,1999:blog-1234953837283832282.post-77449650555963838142008-01-17T22:40:00.000-08:002010-05-12T12:43:37.906-07:00I'm syndicated, and it has nothing to do with PHP!I was running some test queries using my usual spread of values and came across a result of "<a href="http://www.loe.org/shows/segments.htm?programID=08-P13-00002&amp;segmentID=7">Lieutenant Fluffy?</a>" which was far too whimsical a topic to ignore. Turns out someone thought my holiday photos made for a good creative commons pick. Kudos to the news site for respecting licensing terms properly!Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-22565838792804016352008-01-07T12:14:00.000-08:002010-05-12T12:41:22.100-07:00Houston, we have a bolus<p>It took three weeks to filter through <a href="http://www.kp.org/">Kaiser</a>, but my new insulin pump finally arrived (the purple one on top in the photo below is the new one). My old pump was about seven years old when it broke down for the second time (the first was under warranty, about four years ago). By the way, the old one *IS* turned on, and you *SHOULD* see something on the display.... Hence my problem... Anyway, I wasn't expecting it till tomorrow, but christmas came early (well, I guess technically late) 'cause the FedEx guy rang the bell just as I was headed out the door for work.</p><p>SHINEY!!!!!! I haven't had a decent basal profile in nearly a month, so I ripped open the packaging, read the important parts of the instructions and plugged in.... Ah, that's the stuff... I'm now reading through the rest of the manual, here's my thoughts so far:</p> <p> </p><ul><li>Batteries: <b>A+</b> The MiniMed508 takes three Energizer 357s which... while not terribly hard to find, can be pretty annoying when you forget to replace them and the unit shuts down at 1AM... The new 722 fixes that by taking the larger, but more readily available AAA size.</li><li>Menus: <b>B</b> There's more complexity to the menus (driven by a wider feature set), but the most common task (bolusing) is a quick single-button action. <strike>I'd have marked this as an A, but for the fact the bolus amounts no longer wrap-around. Pressing down from 0.0 stays at 0.0, and pressing up from 10.0 stays at 10.0.</strike><b>Update</b>: Turns out the wrap-around does work. You just have to enable it.... Not sure how I enabled it though... Anyway, I'm leaving this at B since the the nag-messages are a bit too insistent... I know what I'm doing damnit... you don't have to be so annoying!</li><li>Backlight: <b>C</b> Same backlight, still not quite enough contrast...</li><li>Delivery mechanics: <b>A</b> Blatently stolen from Diesetronic (a competitor), but a good steal. Smoother, quicker delivery and a nice resevoir window to boot.</li><li>Loading mechanics: <b>A-</b> The resevoir-vial interface is a tiny bit temperamental, but once I get the knack of it, I expect cartridge loading will be twice as fast and accurate as it used to be. The priming mechanics also manage to overcome one of my long standing gripes about volume detection.</li><li>Interface/API: <b>F</b> Same gripe as I had with the MM508. The system is capable of interfacing with external equipment, but the company won't share anything useful about their specs (I knew this from research before hand, but I can still complain about it).</li><li>Durability: <b>TBD</b> We'll see how this one puts up with my.... abuse is too harsh a word.... "active lifestyle".... Let's go with that... At least it's (supposedly) more water-proof than the old one...</li></ul> <br /><a href="http://flickr.com/photos/pollita/2176268408/"><img src="http://farm3.static.flickr.com/2179/2176268408_ea5147dfe8_m.jpg" border="0" /></a>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-15810348620156705832007-09-27T23:31:00.000-07:002010-05-12T12:38:49.471-07:00Hey Facebook! What gives?<p>One of the ways in which I prepare for an interview is to research the person I'm going to be interviewing. The first step is to toss their name into one (or more) search engines and see what falls out. Not getting any useful results isn't a negative (many people work on internal stuff with no concern for fame fortune and glory, and they're very good at it), but if I happen to learn a little about them from a blog entry or a profile on a social networking site, then I can direct my questions in a way that more precisely fits the candidate.</p><p>So today, I sat down to read through the resumé of an upcoming candidate and punched his name into <a href="http://search.yahoo.com/">the best damned search engine ever</a>. What's relatively unique about this candidate is that his name contains an accented character. The search results handled this accented character cleanly of course, and when I followed the first result for "José Smith" (Not his real name -- I'm respecting privacy here), I found myself looking at his <a href="http://www.facebook.com/">Facebook</a> profile. All well and great of course, but then I noticed the URL in the address bar: <u>http://www.facebook.com/Jos_Smith/123456789</u>.</p><p>Jos??? The only Jos I know is a director with a penchant for vampires.</p><p>Now, I get not putting the UTF8 or latin1 literal in the URL, browsers don't agree nearly enough on URL encoding to be able to expect it to work reliably. And putting the urlencoded version would just be silly since /Jos%C3%A9_Smith/ looks even uglier. I get that. But what if his name were entirely non-ascii? Perhaps a chinesse name? Would it be so hard to do a transliteration? I seem to recall Derick <a href="http://derickrethans.nl/transliteration_in_use.php">sharing a few ideas</a> on the subject a couple years ago...</p>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-71254879071458068702007-09-15T10:41:00.000-07:002010-05-12T12:36:02.402-07:00Dress Code<h3>The background</h3> <p>As most know, I work for <a href="http://www.yahoo.com/">Yahoo!</a>, the <a href="http://search.yahoo.com/">US Web Search</a> front-end team specifically. The company as a whole has (to my knowledge) no more of a dress code than "Don't show up naked". Consequently, most employees, especially those in engineering/operations roles, tend to dress pretty casually. During the summer, this progresses clear into the realm of shorts and sandals.</p><p>There's one coworker in our team, Venu, who insists on "dressing for success". Not ultra formal mind you, but we've never seen him wear anything short of a dress shirt, slacks, and nice shoes. At the other end of the spectrum is our boss, Lalit, who has only rarely been seen wearing anything other than shorts (and then, only in the coldest of winters). It was from this, that another coworker, Venkat, hatched his plan.</p><br /><h3>The setup</h3> <blockquote style="font-size: small"><pre>To everyone but Lalit:<br /><venkat> Hey, we should play a prank on Lalit, next monday, everyone should show up in shorts and flip-flops<br /> to make fun of him for his slacker dressing...<br /><br /><br />Meanwhile, to everyone but Venu:<br /><venkat> Okay, Venu's falling for it, now the REAL prank will be on him. He'll show up in shorts,<br /> and the rest of us will dress formally...<br /></pre></blockquote><p>Sure enough, today Venu "the slob" showed up in shorts and some (but not all) the rest of us showed him up in our "business professional" attire. Looking at my towering stature in this photo, I'm doubting the wisdom of wearing heels, but somehow flats just didn't create the right effect ;)</p><p><br /></p><center> <a href="http://www.flickr.com/photos/vudayas/1254271679/"><img src="http://farm2.static.flickr.com/1398/1254271679_abfd50c09e_m_d.jpg" border="0" /></a><br /><span style="font-size: small">Lalit, Me, Venu, Ryan, Venkat, Nam</span> </center>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-30766112471647969812007-07-26T20:06:00.000-07:002010-05-12T12:31:06.064-07:00Fun with Unicode<p>I caught a <a href="http://blog.milkfarmsoft.com/?p=63">blog entry</a> by Alexey Zakhlestin via <a href="http://www.planet-php.net/">Planet-PHP</a> today which asked the question "Why aren't unicode math symbols supported by programming languages?". The obvious answer, of course, is that they work just fine with the symbols they have and there's no need to mess with a good thing (it doesn't help that typing these symbols is a pita on your average terminal).</p><p>Being a whimsical sort, I decided that actually <a href="#implementation">implementing</a> his request would be more fun than simply pish-poshing it. I'm not suggesting this be part of PHP6 (I still don't personally think it's a good idea), but it's a fun exercise and good for a conversation starter... Here's just a few of the things I can do now:</p><blockquote style="font-size: small"><pre>&lt;?php<br />var_dump(¼, ½ ¾);<br />// float(0.25)<br />// float(0.5)<br />// float(0.75)<br /><br />var_dump(1 ≤ 2, 2 ≯ 3, 5 ≠ 6);<br />// bool(true)<br />// bool(true)<br />// bool(true)<br /><br />var_dump(3 × 4, 15 ÷ 5);<br />// int(12)<br />// int(3)<br /><br />var_dump(1 « 3);<br />// int(8)<br /><br />/* Your font may be too small,<br /> * but that's a skull and crossbones<br /> */<br />☠('aka die/exit');<br /></pre></blockquote><p>P.S. - Just FYI, I am aware that the patch referenced above has flaws... The problems are fixable, I just didn't bother fixing 'em because this isn't a serious patch anyway.... Best spend that time on something worthwhile...</p><br /><p>P.P.S. - Since the original posting of this article, PHP6-Unicode has been... well, essentially scrapped. So the patch itself is no longer relevant.</p><br /><a name="implementation"><pre style="font-size: small">Index: Zend/zend_language_scanner.l<br />===================================================================<br />RCS file: /repository/ZendEngine2/zend_language_scanner.l,v<br />retrieving revision 1.167<br />diff -u -p -r1.167 zend_language_scanner.l<br />--- Zend/zend_language_scanner.l 12 Jul 2007 09:23:48 -0000 1.167<br />+++ Zend/zend_language_scanner.l 27 Jul 2007 05:05:47 -0000<br />@@ -402,6 +402,109 @@ ZEND_API int zend_copy_scanner_string(zv<br /> return 1;<br /> }<br /> <br />+/* Used by {LABEL} for converting unicode operator symbols */<br />+static inline int zend_scan_unicode_operator(zval *zendlval, char *str, zend_uint str_len, UConverter *conv, int *oplen, int have_equal TSRMLS_DC)<br />+{<br />+ int ret = 0;<br />+<br />+ switch (Z_USTRVAL_P(zendlval)[0]) {<br />+ case 0x2260: /* NOT EQUAL TO */<br />+ ret = T_IS_NOT_EQUAL;<br />+ break;<br />+<br />+ case 0x2264: /* LESS-THAN OR EQUAL TO */<br />+ case 0x226F: /* NOT GREATER-THAN */<br />+ ret = T_IS_SMALLER_OR_EQUAL;<br />+ break;<br />+<br />+ case 0x2265: /* GREATER-THAN OR EQUAL TO */<br />+ case 0x226E: /* NOT LESS-THAN */<br />+ ret = T_IS_GREATER_OR_EQUAL;<br />+ break;<br />+<br />+ case 0x00AB: /* LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */<br />+ case 0x226A: /* MUCH LESS THAN */<br />+ ret = have_equal ? T_SL_EQUAL : T_SL;<br />+ break;<br />+<br />+ case 0x00BB: /* RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */<br />+ case 0x226B: /* MUCH MORE THAN */<br />+ ret = have_equal ? T_SR_EQUAL : T_SR;<br />+ break;<br />+<br />+ case 0x2270: /* NEITHER LESS-THAN NOR EQUAL TO */<br />+ ret = '>';<br />+ break;<br />+<br />+ case 0x2271: /* NEITHER GREATER-THAN NOR EQUAL TO */<br />+ ret = '<';<br />+ break;<br />+<br />+ case 0x2276: /* LESS-THAN OR GREATER-THAN */<br />+ case 0x2277: /* GREATER-THAN OR LESS-THAN */<br />+ ret = T_IS_NOT_EQUAL;<br />+ break;<br />+<br />+ case 0x2278: /* NEITHER LESS-THAN NOR GREATER-THAN */<br />+ case 0x2279: /* NEITHER GREATER-THAN NOR LESS-THAN */<br />+ ret = T_IS_EQUAL;<br />+ break;<br />+<br />+ case 0x00D7: /* MULTIPLICATION SIGN */<br />+ ret = have_equal ? T_MUL_EQUAL : '*';<br />+ break;<br />+<br />+ case 0x00F7: /* DIVISION SIGN */<br />+ ret = have_equal ? T_DIV_EQUAL : '/';<br />+ break;<br />+<br />+ case 0x00BC: /* VULGAR FRACTION ONE QUARTER */<br />+ zval_dtor(zendlval);<br />+ ZVAL_DOUBLE(zendlval, 0.25);<br />+ ret = T_DNUMBER;<br />+ break;<br />+<br />+ case 0x00BD: /* VULGAR FRACTION ONE HALF */<br />+ zval_dtor(zendlval);<br />+ ZVAL_DOUBLE(zendlval, 0.5);<br />+ ret = T_DNUMBER;<br />+ break;<br />+<br />+ case 0x00BE: /* VULGAR FRACTION THREE QUARTERS */<br />+ zval_dtor(zendlval);<br />+ ZVAL_DOUBLE(zendlval, 0.75);<br />+ ret = T_DNUMBER;<br />+ break;<br />+<br />+ case 0x2620: /* SKULL AND CROSSBONES */<br />+ ret = T_EXIT;<br />+ break;<br />+ }<br />+<br />+ if (ret) {<br />+ /* How much of the input do we need to consume for this codepoint in this character set? */<br />+ UChar buffer[2], *buffer_ptr = buffer;<br />+ UErrorCode status = U_ZERO_ERROR;<br />+ const char *str_ptr = str;<br />+<br />+ ucnv_toUnicode(conv, &buffer_ptr, buffer + 1, &str_ptr, str + str_len, NULL, TRUE, &status);<br />+ if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {<br />+ /* Shouldn't happen... */<br />+ ret = 0;<br />+ } else {<br />+ *oplen = (str_ptr - str);<br />+ }<br />+ }<br />+<br />+ if (ret && ret != T_DNUMBER) {<br />+ zval_dtor(zendlval);<br />+ ZVAL_NULL(zendlval);<br />+ }<br />+<br />+<br />+ return ret;<br />+}<br />+<br /> static inline int zend_check_and_normalize_identifier(zval *zendlval)<br /> {<br /> UChar *norm;<br />@@ -2217,13 +2320,47 @@ HEREDOC_CHARS ("{"*([^$\n\r\\{]|("<br /> return T_ENCAPSED_AND_WHITESPACE;<br /> }<br /> <br />-<ST_IN_SCRIPTING>{LABEL} {<br />- if (!zend_copy_scanner_string(zendlval, yytext, yyleng, UG(unicode)?IS_UNICODE:IS_STRING, SCNG(output_conv) TSRMLS_CC)) {<br />- return 0;<br />+<ST_IN_SCRIPTING>{LABEL}"="? {<br />+ zend_bool have_equal = 0;<br />+<br />+ if (yytext[yyleng-1] == '=') {<br />+ have_equal = 1;<br /> }<br />- if (UG(unicode) && !zend_check_and_normalize_identifier(zendlval)) {<br />+<br />+ if (!zend_copy_scanner_string(zendlval, yytext, yyleng - have_equal, UG(unicode)?IS_UNICODE:IS_STRING, SCNG(output_conv) TSRMLS_CC)) {<br /> return 0;<br /> }<br />+<br />+ if (UG(unicode)) {<br />+ UChar *norm;<br />+ int norm_len;<br />+<br />+ if (!zend_is_valid_identifier(Z_USTRVAL_P(zendlval), Z_USTRLEN_P(zendlval))) {<br />+ int oplen = 0, ret = zend_scan_unicode_operator(zendlval, yytext, yyleng, SCNG(output_conv), &oplen, have_equal TSRMLS_CC);<br />+<br />+ if (ret) {<br />+ yyless(oplen + have_equal);<br />+ return ret;<br />+ }<br />+<br />+ zval_dtor(zendlval);<br />+ zend_error(E_COMPILE_WARNING, "Invalid identifier syntax: %r", Z_USTRVAL_P(zendlval));<br />+ return 0;<br />+ }<br />+<br />+ if (zend_normalize_identifier(&norm, &norm_len, Z_USTRVAL_P(zendlval), Z_USTRLEN_P(zendlval), 0) == FAILURE) {<br />+ zend_error(E_COMPILE_WARNING, "Could not normalize identifier: %r", Z_USTRVAL_P(zendlval));<br />+ efree(Z_USTRVAL_P(zendlval));<br />+ return 0;<br />+ }<br />+<br />+ if (norm != Z_USTRVAL_P(zendlval)) {<br />+ efree(Z_USTRVAL_P(zendlval));<br />+ ZVAL_UNICODEL(zendlval, norm, norm_len, 0);<br />+ }<br />+ }<br />+ yyless(yyleng - have_equal);<br />+<br /> return T_STRING;<br /> }<br /> <br /></pre></a>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-44405238392868200562007-05-19T07:13:00.000-07:002010-05-12T12:21:03.239-07:00create_function() is not your friend, buddy<p>While browsing through <a href="http://www.phpdeveloper.org/">PHP Developer</a> today, I came across this blog entry: <a href="http://www.thinkingphp.org/2007/05/18/my-new-best-friend-phps-create_function/">"My new best friend"</a> extolling the virtues of <a href="http://www.php.net/create_function">create_function()</a>. Let me tell you why create_function() is <b>not</b> my best friend...</p><p>First, despite the disclaimer in the mentioned blog entry, it <i>is</i> as bad as its kissing-cousin <a href="http://php.net/eval">eval()</a>. Let's take a look at what create_function() actually does by translating it into userland code:</p> <blockquote style="font-size:small"><pre>function create_function($args, $code)<br />{<br /> static $id = 0;<br /> eval("function __lambda_func($args) { $code }");<br /> while (!<a href="http://php.net/runkit_function_rename">runkit_function_rename</a>('__lambda_func', "\0lambda_" . (++$id)));<br /> return "\0lambda_$id";<br />}<br /></pre></blockquote><p>I'll let you contemplate on that awhile....You should be noticing the following sets of problems: </p><ul><li>Prone to <i>critical</i> abuse by user-supplied code</li><li>Skips opcode cache optimizations</li></ul><p>You should also be thinking about the practical issues with it: </p><ul><li>Code lives inside quoted strings which means awkward escaping of embedded quotes</li><li>Encourages not using comments (evil)</li><li>100% blind to reflection or PHPDoc style documentation generation</li><li>I'm sure you can come up with a couple more...</li></ul><br /><p>"If eval() is the answer, then you're asking the wrong question"</p>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com1tag:blogger.com,1999:blog-1234953837283832282.post-74630552422725844432007-01-26T12:21:00.000-08:002010-05-12T12:14:00.759-07:00You're being lied to<p>If you're among the crowd who have migrated an OOP based application from PHP4 to PHP5, then I'm sure you've heard the expression "Objects are copied by reference by default in PHP5". Whoever told you that, was lying.</p><p>Now, to be fair, it's an innocent lie, since objects do <b>behave</b> in a <i>reference-like manner</i>, but references are NOT what they are. Let's start with a simple illustration proving that they aren't references:</p><blockquote style="font-size: small"><pre>&lt;?php<br /> $a = new stdClass;<br /> $b = $a;<br /> $a-&gt;foo = 'bar';<br /> var_dump($b);<br /> /* Notice at this point, that $a and $b <b>are</b>,<br /> * indeed sharing the same object instance.<br /> * This is their reference-like behavior at work.<br /> */<br /><br /> $a = 'baz';<br /> var_dump($b);<br /><br /> /* Notice now, that $b is still that original object.<br /> * Had it been an actual reference with $a,<br /> * it would have changed to a simple string as well.<br /> */<br />?&gt;</pre></blockquote><p>What's going on here? Well, the answer is easiest to explain by explaining what the underlying structure of objects are. In PHP5, a variable containing an object identifies the instance by storing a simple numeric value. When an action is going to be performed on an object, that numeric value is used with a lookup table to retreive the actual instance. In PHP4, by contrast, a variable containing an array identifies that object by carrying around the actual properties table itself. What this means in practice is that when you assign (not by reference) a PHP5 object to a new variable, that integer handle is copied into the new variable, but it still points at the same instance, because it's still the same number. Assigning a PHP4 object however, means copying all the properties, effectively generating a new instance, since changes to one will not effect the other.</p><p>To put this another way, PHP4 objects are basically Arrays with functions associated with them, PHP5 objects are basicly Resources (a la MySQL result handles, or file pointers) again with functions loosely associated to them. Consider the following code in PHP4 (or any version):<br /></p><blockquote style="font-size: small"><pre>&lt;?php<br /> $fp = fopen('foo.txt', 'r');<br /> $otherVar = $fp;<br /> fwrite($fp, "One\n");<br /> fwrite($otherVar, "Two\n");<br /> fclose($fp);<br /><br /> /* This fails, because the file is closed */<br /> fwrite($otherVar, "Three\n");<br />?&gt;</pre></blockquote><p>You'd fully expect data to be written to the same file, as though you'd used $fp everywhere, rather than interchanging the variables right? Well, PHP5 objects are the same. The instance itself isn't duplicated when you assign to a new variable, just the unique identifier.</p><br /><h3>I'm lying to you also</h3><p>"Copying" a variable doesn't exactly mean copying. Take the following code block:</p><blockquote style="font-size: small"><pre>&lt;?php<br /> $a = 'foo';<br /> $b = $a;<br /> $a = 'bar';<br />?&gt;</pre></blockquote><p>Now, you know PHP well enough to know that by the end of this code block, the value of <span style="font-family:courier;">$b</span> will still be <span style="font-family:courier;">'foo'</span>. What you may not know, is that the original copy of <span style="font-family:courier;">'foo'</span> that was in <span style="font-family:courier;">$a</span>, was <i>never actually duplicated</i>.</p><p>To understand what PHP is doing, you need to understand the internal structure of the variable and how it relates to userspace visible variable names ('a' and 'b' in this case). First off, the actual contents of a variable (known as a <span style="font-family:courier;">zval</span>) consists of four parts: <i>type</i> (e.g. NULL, Boolean, Integer, Float, String, Array, Resource, Object), a specific <i>value</i> (e.g. 123, 3.1415926535, etc...), <i>is_ref</i> - a flag indicating if the value is a reference or not, and <i>refcount</i> which tells how many times this value is being shared.</p><p>What you think of as a variable (e.g. <span style="font-family:courier;">$x</span>) is actually just a label, that label ('x' in this case) is used as a lookup to find the <span style="font-family:courier;">zval</span> which conatins the actual value. These are just like keys in an associative array, in fact, the mechanisms are identical.</p><p>With me so far? Good. Now, when you first create a variable (e.g. <span style="font-family:courier;">$x = 123;</span>, PHP allocates a new <span style="font-family:courier;">zval</span> for it, stores the specific value, and associates the label with the value:</p><blockquote style="font-size: small"><pre> 'x' => zval ( type => IS_LONG,<br /> value.lval => 123,<br /> is_ref => 0,<br /> refcount => 1 )</pre></blockquote><p>So far, refcount is 1 since the zval value is only being referenced by one label. If we now put this value into a full-reference set using <span style="font-family: courier">$y =&amp; $x;</span>, the same zval is reused. It's simply associated with a new label and it's reference counters are adjusted properly.</p><blockquote style="font-size: small"><pre> 'x' => zval ( type => IS_LONG,<br /> | value.lval => 123,<br /> | is_ref => 1,<br /> | refcount => 2 )<br /> 'y' /</pre></blockquote><p>This way, when you later change the value of $x, $y appears to change as well because it's looking at the same internal value. But what if we hadn't done a reference assignment, what if we'd done a normal assignment: <span style="font-family:courier;">$y = $x;</span>, surprisingly, the result would be <b>almost</b> the same.</p><blockquote style="font-size:small"><pre> 'x' => zval ( type => IS_LONG,<br /> | value.lval => 123,<br /> | is_ref => 0,<br /> | refcount => 2 )<br /> 'y' /</pre></blockquote><p>Again, the original zval associated with $x is reused, the only difference this time is that is_ref is not set to 1. This is known as a <u>copy-on-write reference set</u> (as opposed to the full-reference set described above). This 0 flag tells the engine that if anyone tries to change this value (regardless of which label they use to reach it), any other references to it should be left alone. Here's what happens if we take that current state and do <span style="font-family:courier;">$x = 456;</span></p><blockquote style="font-size: small"><pre> 'y' => zval ( type => IS_LONG,<br /> value.lval => 123,<br /> is_ref => 0,<br /> refcount => 1 )<br /> 'x' => zval ( type => IS_LONG,<br /> value.lval => 456,<br /> is_ref => 0,<br /> refcount => 1 )</pre></blockquote><p>$x has been disassociated from the original zval (thus dropping its refcount back to 1), and new zval has been created for it.</p><br /><h3><a name="badrefs">Why referencing when you don't have to is a bad idea.</a></h3><a name="badrefs"></a><p>Let's consider one more situation, take a look at this code block:</p><blockquote style="font-size: small"><pre>&lt;?php<br /> $a = 'foo';<br /> $b = $a;<br /> $c = &amp;$a;<br />?&gt;</pre></blockquote><p>At the first instruction, a single zval is created, associated to a single label:</p><blockquote style="font-size: small"><pre> 'a' => zval ( type => IS_STRING, value.str.val = 'foo', is_ref = 0, refcount = 1 )</pre></blockquote><p>At the second intstruction, that zval is associated to a second label, so far so good:</p><blockquote style="font-size: small"><pre> 'a' => zval ( type => IS_STRING,<br /> | value.str.val => 'foo',<br /> | is_ref => 0,<br /> | refcount => 2 )<br /> 'b' /</pre></blockquote><p>At the third intstruction, however, we run into problems. Since this zval is already tied up in a copy-on-write reference set which include $b, that zval can't be simply promoted to is_ref==1. Doing so would drag $b into $a and $c's full-reference set, and that would be wrong. In order to resolve this, the engine is forced to duplicate that zval into two identical copies, from which it can begin to shuffle around reference flags and counts:</p><blockquote style="font-size: small"><pre> 'b' => zval ( type => IS_STRING,<br /> value.str.val =>'foo',<br /> is_ref => 0,<br /> refcount => 1 )<br /> 'a' => zval ( type => IS_STRING,<br /> | value.str.val => 'foo',<br /> | is_ref => 1,<br /> | refcount => 2 )<br /> 'c' /</pre></blockquote><p>Now you've got two copies of the same literal value, so you're wasting memory for the storage, and processing time required to actually make the duplication. Since a LOT of events lead to copy-on-write uses (including simply passing an argument to a function), this sort of forced duplication actually happens very commonly when you start involving actual references.</p><br /><h3>The moral of the story</h3><p>Assigning values by references when you don't need to (in order to later modify the original value through a different label) is <b>NOT</b> a case of you outsmarting the silly engine and gaining speed and performance. It's the opposite, it's you <b>TRYING</b> to outsmart the engine and failing, because the engine is already doing a better job than you think.</p><p>How does this reflect on objects? They're not special. They're not different from other variables. They are not pretty snowflakes. In this code block:</p><blockquote style="font-size: small"><pre>&lt;?php<br /> $a = new stdClass;<br /> $b = $a;<br />?&gt;</pre></blockquote><p>The labels are still placed into copy-on-write reference sets. What's important, is that even when a duplication does occur, (A) only that unique integer is copied (which is cheap), and (B) the duplicated integer still points to the same place. Hence you get <i>reference-like behavior</i>, but not an actual reference by default.</p><p>Hungry for more? Check out my <a href="http://devzone.zend.com/node/view/id/1022">coverage of the zval</a>.</p>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com12tag:blogger.com,1999:blog-1234953837283832282.post-68978642224440704542006-12-28T18:54:00.000-08:002010-04-23T15:20:47.140-07:00PHP-2006: A look back<div class="serendipity_entry_body"> <span class="entry-content"><p>Well, <a href="http://pixelated-dreams.com/">Davey Shafik</a> started us off with <a href="http://pixelated-dreams.com/archives/279-A-Year-in-Review.html">his year-end wrapup</a> so I'll follow suit with mine. The thoughts below are mine and mildly influenced by alcohol. They represent a foggy review of how I experienced the year through the imperfect recollection of mailing list archives.</p> <p><b>January</b> began with releases of 4.4.2 and 5.1.2. Version 5.1.2 was especially close to my heart since it was the first version to ship an <a href="http://pecl.php.net/package/hash">extension</a> of mine not only bundled, but enabled by default. I've had my hands in most of the PHP runtime, but this was the first time I could point at a standard extension and say that it was basicly my work (Note: Mike Wallner did a fair bit of work adding to the number of hash algorithms supported, don't let me discount his efforts). Tim Starling <a href="http://news.php.net/php.internals/21441">wrote</a> to ask why PHP4 refcounts are 16bit, and what he could do about getting that counter increased in future versions of PHP4. Since such a change would break binary compatability and since the PHP4 branch is already quite dead, the request was ultimately left alone with an admonishment to "not do that".</p> <p>A few other requests were broached or continued, such as support for <a href="http://news.php.net/php.internals/21469">Friend Classes</a>, Named Arguments, and <a href="http://news.php.net/php.internals/21565">Naming Arguments</a> (The last of which would eventually be implemented). It was also this month in which Rasmus <a href="http://news.php.net/php.internals/21591">suggested</a> adding JSON to the standard distribution, this quietly morphed into votes for including filter; Both were eventually linked in. James Crane had the <a href="http://news.php.net/php.internals/21715">idea</a> that Array Literals in PHP could use sprucing up, meanwhile Sean Coates <a href="http://news.php.net/php.internals/21659">planted the seed</a> for what would become the PHP6 <a href="http://www.php.net/%7Escoates/unicode/">Unicode Progress</a> tracker.</p> <p><b>February</b> came in fairly quiet, mostly wrapping up the topics from January. Appearantly Steph didn't like the quiet and decided to stir up the hornet's nest with some four letter words cleverly disguised under the heading of <a href="http://news.php.net/php.internals/21912">True Labeled Breaks</a>. For those who have blocked out 2005, this was easily one of the longest threads of that year and this resumption promised to be just as bad. By <a href="http://blog.libssh2.org/index.php?/archives/2-GOTO...No,-seriously,-for-real-this-time..html">March</a>, this thread would finnally be brought to a halt as the functionality was slipped quietly into the engine. I did not see that coming...</p> <p>Amidst this unexpected addition, others were busily pulling things out, with Andi <a href="http://news.php.net/php.internals/21935">slashing away at safe_mode</a> in HEAD, and Marcus integrating support for <a href="http://news.php.net/php.internals/21939">function deprecation</a> into the 5.2 branch.</p> </span> <div class="serendipity_entry_extended"><p><b>March</b> came in like a lion with Marcus <a href="http://news.php.net/php.internals/22044">poking the list</a> about Late Static Binding with less argument about Why, and a healthy focus on How. Johannes Schlueter made a very pragmatic and uncontroversial <a href="http://news.php.net/php.internals/22103">suggestion</a> to change the internal symbol prefix applied to methods in order to distinguish them from functions. Pierre ran with some PDM recommendations shouting <a href="http://news.php.net/php.internals/22122">Adieu register_globals</a> and <a href="http://news.php.net/php.internals/22162">Adieu a la magie</a>. Sebastian Bergmann was frustrated with trying to <a href="http://news.php.net/php.internals/22242">apply streams filters</a> to include and require, until I pointed him at the <a href="http://www.php.net/wrappers.php">php://filter</a> wrapper. Though this met his need, he made a strong case for being able to set an automatic filter to be applied to all streams. I promised to do this after streams in HEAD had been cleaned up, and while I've gotten streams where I want them, I still havn't fullfilled his feature request... Shame on me.</p> <p>All this time, the GOTO debate was still raging and similar types of language altering features were being put on the table. Finally, Zeev decided he'd <a href="http://news.php.net/php.internals/22275">had enough</a> calling for people to "Give the language a rest". This managed to have a decent effect which Rasmus soon channeled into his <a href="http://news.php.net/php.internals/22357">call for performance geeks</a> to narrow the performance gap between 4.4 and 5.1. Towards the end of the month, I suggested adding an <a href="http://news.php.net/php.internals/22572">open_basedir_for_include</a> directive. While there was some interrest for this, Ilia quickly convinced me that my approach to the problem was flawed and wouldn't give any real benefit.</p> <p><b>April</b> showered the internals list with <a href="http://news.php.net/php.internals/22617">Round 2</a> of the Late Static Binding discussion, and Thomas Boutell's anouncement that he'd be turning over primary development of the GD library to the PHP project, spearheaded by Pierre. I made some more noise <a href="http://news.php.net/php.internals/22649">complaining</a> of RETURN_RT_STRING() (and family)'s leakage of memory under certain not-uncommon conditions, while Nuno brought the infamous <a href="http://news.php.net/php.internals/22665">Coverity Report</a> to attention.</p> <p>Richard Lynch shared his <a href="http://news.php.net/php.internals/22728">WTF</a> concerning the oft misunderstood <i>tsrm_ls</i> parameter in PHP's sources. Sadly, I hadn't written my <a href="http://blog.libssh2.org/index.php?/archives/22-What-the-heck-is-TSRMLS_CC-anyway.html">summary</a> of the topic yet, but although noone gave him a detailed description (It's a bit long to go into in an email), several helpful links were supplied. Round about the middle of the month, Rasmus <a href="http://news.php.net/php.internals/22774">announced</a> PHP's eminent participation in the Google Summer of Code project (wait, don't you work for Yahoo!?). After signing up as a mentor (though I never actually mented -- is that a word?), I gathered up my materials and <a href="http://blog.libssh2.org/index.php?/archives/13-phptek-Day-One.html">flew off</a> to <a href="http://blog.libssh2.org/index.php?/archives/14-phptek-Day-Two.html">php|tek</a> 2006 in <a href="http://blog.libssh2.org/index.php?/archives/15-Bow-to-your-neighbor,-do-see-do....html">Orlando</a>.</p> <p><b>May</b> opened to the initial planning rounds for PHP 5.2.0, and the <a href="http://news.php.net/php.internals/23101">resumption</a> of the ifsetor() request, now dressed up as coalesce(). By the way, why havn't we embraced this feature request yet? About a week into the month, Ilia <a href="http://news.php.net/php.internals/23169">branched</a> the PHP_5 tree leaving room for the earnest development of 5.2.</p> <p>William Candillon wrote in to <a href="http://news.php.net/php.internals/23239">request</a> a PHP version of C's #line macro, but was drowned out by the roar of Derick's <a href="http://news.php.net/php.internals/23242">plea</a> to "Stop breaking our apps for the sake of OO". Once the din of that had quieted down a little, Jason Garber decided to ask about making it possible to mark properties as <a href="http://news.php.net/php.internals/23291">read only</a>. In case anyone was curious, none of these proposals gained footing.</p> <p><b>June</b> now, and <a href="http://www.amazon.com/dp/067232704X">my book</a> is finally released bringing with it an invitation to appear on php|architect's <a href="http://podcast.phparch.com/main/index.php/episodes:20060602">Pro::PHP podcast</a>. Marcus Boerger <a href="http://news.php.net/php.internals/23875">suggested</a> that array indices could benefit from implicit __toString() calls, but he was eventually shot down. Dimitry's <a href="http://news.php.net/php.internals/23988">suggestion</a> however, which provided for automatic module global registration via the module entry, did receive a warm welcome and you'll see it if you look inside PHP 5.2.0.</p> <p>Clearly this was the month of bright ideas, because Nuno Lopes tossed in gcc <a href="http://news.php.net/php.internals/24149">branch prediction</a>. It got a warm initial reception from the engine folk including Zeev stating "I actually like how it makes the code more readable hinting which branches are rare.". Andi agreed that it was a nice idea, but brought in the sobering reality that it didn't really do much for performance and could potentially bring unexpected results if applied heavily.</p> <p><b>July</b> was a busy month for me as I <a href="http://blog.libssh2.org/index.php?/archives/31-Moving-on....html">changed jobs</a> for the first time in over six years. I'm loving the new job by the way. Fantastic coworkers and....interresting challenges.... Laupretre François thought it'd be a <a href="http://news.php.net/php.internals/24307">good idea</a> to extend include_path to support stream wrappers. Those of us who know what trouble include_path already causes for performance and security were quick to nix that particular idea; Short version: No.</p> <p>Marcus popped in mid-month with an <a href="http://news.php.net/php.internals/24402">implementation</a> of the #line directive suggested back in May. Still no love from the internals community at large though; Short version: no #line for you. Dmitry committed a large patch to the Zend Engine to change how non-persistent memory is allocated and freed within a request. The good news is that emallocs are now faster, the bad news is that a hack I'd made in PHP5.1 for manipulating the non-persistent memory pool no longer worked...Grrrr.....</p> <p>On July 27th, Jani said <a href="http://news.php.net/php.internals/25023">Good-Bye</a>.</p> <p><b>August</b> picked up Mike Wallner's July <a href="http://news.php.net/php.internals/24804">post</a> decrying the state of OO strictness building up in PHP. This maelstrom took up most of the first week with no real conclusion (at least, not a satisfactory one). A <i>different</i> Mike made some more noise later in the month <a href="http://news.php.net/php.internals/25277">asking</a> why accessing non-existant functions/methods has to be so darned fatal. The good news is that they aren't so much anymore. Yay for E_RECOVERABLE_ERROR.</p> <b>September</b> brought a <a href="http://blog.libssh2.org/index.php?/archives/38-PHP6-News-from-the-front....html">landslide</a> of movement in the PHP6 function migration/review process. There was some degree of question as to whether unicode.semantics should be SYSTEM, PERDIR, or USER. Noone really considered the latter as a possibility as it wrecks way too many assumptions, but the SYSTEM/PERDIR debate raged for awhile with the idealists wanting PERDIR support to aid migration, and the realists clinging to the maintainable simplicity of SYSTEM. In the end, SYSTEM won. <p><b>October</b> was a relatively slow month, leading up to conference season such as it was. Midmonth sometime I tossed out the <a href="http://news.php.net/php.internals/26040">idea</a> of allowing open_basedir to be tightened (but not loosened) during runtime and it was green-lighted surprisingly quietly. Ilia got a little <a href="http://news.php.net/php.internals/26214">frustrated</a> with the mounting delays holding back the release of PHP 5.2.0 which was already way behind schedule. Tragicly, this plea made the delay last even longer. Finally at the end of the month, he rolled final.</p> <p><b>November</b> was a big conference month for me. First I crashed the nearby <a href="http://blog.libssh2.org/index.php?/archives/41-ZendCon06-Wrapup.html">ZendCon</a>, then turned right around and flew off to Germany for the <a href="http://blog.libssh2.org/index.php?/archives/44-International-PHP-Conference-2006,-Frankfurt-am-Main.html">International PHP Conference</a>. Sean poked the <a href="http://news.php.net/php.internals/26472">namespaces</a> topic with a sharp stick and managed to generate a week's worth of noise resulting in no actual commitments by any capable/interrested parties. No sooner had that comotion died down than did Antony Dovgal bemoan a regression in fgets()'s behavior which I introduced in HEAD. I still think it's a silly behavior for a userspace function, but BCs are BCs, so the regression's been reverted.</p> <p><b>December</b>'s most exciting event was a <a href="http://news.php.net/php.internals/26756">debaucle</a> over the backward/forward compatability of serialize given the additional escaping concerns that processing unicode strings imposes. Ilia brought forward a <a href="http://news.php.net/php.internals/26845">proposal</a> to finally remove the unnecessary COM/Sockets/MHash extensions from the distribution bundle. After some light debate it was decided to keep COM around and only nix Sockets and MHash as of PHP6 (possibly 5.3 if such a version comes about). Wietse Venema brought back the <a href="http://news.php.net/php.internals/26979">concept</a> of introducing a taint mode for PHP. The topic is still being discussed, but things don't look good for Wietse...</p> <p>And now here we are, at year's end. PHP6's unicode function migration process has passed the 50% mark and a preview release is sure to come once we handle a couple more extensions. PHP 5.2 is grabbing hold amongst the serious PHP shops, and even web hosters are trickling away from PHP4. There's still a huge, bright future ahead for this language, together we can make it happen.</p> <p>¡Feliz Año Nuevo!</p></div> </div>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-88913100872710998682006-11-29T13:26:00.000-08:002010-04-23T15:18:24.700-07:00When good encodings go bad<div class="serendipity_entry_body"> <span class="entry-summary"><p>In the past year, I've been doing some work with Unicode as part of the PHP6 upgrade. I've learned more than I wanted to know about all sorts of encodings from UTF-7 to koi8-r to good old iso-8859-1. I've picked apart the picayune differences between UCS-2 and UTF-16, and played the game of surrogate pairing and orphaning. Despite all that exposure however, I wasn't prepared when a question crossed my inbox about a lesser known encoding called AL32UTF8.</p> <p>I'd never heard of this one before, so I went to my favorite search engine for some answers. Turns out it's something Oracle came up with and later got adopted as a proper standard with the name CESU-8. At first glance, CESU-8 looks identical to UTF-8 in the same way that UCS-2 looks a lot like UTF-16. In fact every codepoint from U+0000 to U+FFFF is encoded identically under both sets of rules: 16 bits, split up over one, two, or three bytes, with leftover bits framing the encoding protocol.</p> <p>When you jump up above U+FFFF however, into the realm of CJK codepoints and the like (such as my personal nom du pointe: <a href="http://www.fileformat.info/info/unicode/char/236ba/index.htm" alt="Get a new font if you can't see this">𣚺</a>) something funny starts to happen. In the UTF8 world, these codepoints are accomodated by adding one extra byte to the mix which allows for up to 22bits of data (All of unicode only requires 21). In the CESU-8 world however, the code point is split according to UTF-16 surrogacy rules making two separate unicode points (each in the range U+D800 - U+DFFF). These two unicode points are then encoded <i>individually</i> into UTF-8 sequences. This means that we've now promoted our variable length (4 max) multibyte encoding to a variable length (6 max) multibyte-multibyte encoding. Thank you Oracle. Thank you for adding complexity to encoding rules while increasing data storage requirements. What would the world do without you?</p> <p>P.S. - Java is at fault too... its 'Modified UTF-8' uses nearly identical rules.</p></span> </div>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-62328440345188881662006-11-05T23:08:00.000-08:002010-04-23T15:17:32.663-07:00Don't worry, I slept last Thursday<div class="serendipity_entry_body"> <span class="entry-summary"><p>On this, my first foray to Europe, indeed my first real trip outside the US (Those couple hours in Tijuana don't count), I'm faced with one undeniable, inexcapable fact. <b><u>Jet lag sucks.</u></b></p> <p>It doesn't help that all last week I was staying up past my normal bedtime partying with the attendees of ZendCon06, but I think my real mistake was trying to outsmart my own circadian rhythm. See, I figured "I've got this long flight across the atlantic, it'll go faster if I can fall asleep at some point." Seems reasonable so far. How to ensure sleep? Why, stay up all night before the flight. Brilliant! But wait, what if I can't fall asleep during the flight?</p> <p>Sometime sunday morning, my plane lands in Frankfurt and I wander, zombie-like, through passport control, baggage claim, and customs somehow managing to board the right shuttle to reach the conference hotel. Based on advices from battle-hardened globetrotters, I was planning to put in a one hour power nap, then go for a walk to "reset" my internal clock. Unfortunately the sixty-plus hour run of consciousness had other plans and by the time I awoke, the sun had set.</p> <p>Finally this morning (Monday), I managed to put in that walk, touring a nearby suburb which reminded me somewhat spookily of the setting from "Shaun of the Dead" (more zombie tie-ins). Five euros and three liter bottles of diet coke later and I'm almost coherent enough to....what the hell was I gonna say? Sorry, my brain has been cutting out a lot the past few days...</p> <p>Guten Morgen!</p></span> </div>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-67814345321202604032006-07-05T13:34:00.000-07:002010-04-23T15:13:44.101-07:00Moving right along<span class="entry-summary">After well over half a decade at the <a href="http://www.berkeley.edu/">University of California at Berkeley</a>, I'm moving on to greener pastures. Well, maybe not greener (Berkeley is full of evergreens and...other verdant plant substances), but pastures at the very least. Next monday, my life's journy will bring me to that farmland come technopolis known the world over as Silicon Valley. I'll be cubefarming private sector style for one of the few dotcom survivors, a little mom &amp; pop outfit called <a href="http://search.yahoo.com/">Yahoo!</a>. I've worked in education and the public sector since 1997 and while I'm optimistic everything will go smoothly, well....I think Dan Aykroyd put it best as Dr. Raymond Stantz in <a href="http://imdb.com/title/tt0087332/">Ghostbusters</a>: "You don't know what it's like out there, you've never worked in the private sector, They expect <b>results</b>! (shudder)". If nothing else it'll be nice to get my commute back down to the 20minute zone. At any rate, I've got 2 days and as many going-away parties to get through, then it's on to the enemy camp (Yahoo's birth {not as a company, but as the index site it started out as} was at UCBerkeley's long-time rival, Stanford University).</span>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com0tag:blogger.com,1999:blog-1234953837283832282.post-66188931327542400912006-06-18T19:05:00.000-07:002010-04-23T15:11:53.093-07:00How long is a piece of string<div><span><p>Sunday morning I was asked by an IRC regular: "Where does the engine parse quoted strings?". Being a sunday morning, I began to launch into a sermon on the distinction between <span style="font-family:courier;">CONSTANT_ENCAPSED_STRING</span> and the problems which befall a single-pass compiler when you start to introduce interpolation. Not what he asked precisely, but an important component in answering his question. Unfortunately, at the time I was busy watching the Brasil-Australia game so I didn't go into the kind of detail I would have. Now, some 12 hours later, since Angela is off buying toe-socks in Santa Cruz, I'll bore anyone with little enough life to read my blog by explaining the pitfalls of using PHP's string interpolation without using an optimizer.</p></span><div><p>To start things off, let's take a page from my earlier discourse on <a href="http://saragolemon.blogspot.com/2006/05/last-month-at-phptek-i-gave.html">Compiled Variables</a> and look at the opcodes generated by a few simple PHP scripts:</p><blockquote><pre style="font-family:courier new;font-size:78%;line-spacing:0">&lt;?php<br />echo "This is a constant string";<br />?&gt;<br /></pre></blockquote><br /><p>Yields the nice, simple opcode:</p> <blockquote><pre style="font-family:courier new;font-size:78%;">ECHO 'This is a constant string'<br /></pre></blockquote> <p>No problem... Exactly what you'd expect... Now let's complicate the expressions a little:</p> <blockquote><pre><span style="font-family:courier new;font-size:78%;">&lt;?php</span><span style="font-size:78%;"><br /></span><span style="font-family:courier new;font-size:78%;">echo "This is an interpolated $string";</span><span style="font-size:78%;"><br /></span><span style="font-family:courier new;font-size:78%;">?&gt;</span><br /></pre></blockquote> <p>Yields the surprisingly messy instruction set:</p> <blockquote><pre style="font-family:courier new;font-size:78%;">INIT STRING ~0<br />ADD_STRING ~0 ~0 'This'<br />ADD_STRING ~0 ~0 ' '<br />ADD_STRING ~0 ~0 'is'<br />ADD_STRING ~0 ~0 ' '<br />ADD_STRING ~0 ~0 'an'<br />ADD_STRING ~0 ~0 ' '<br />ADD_STRING ~0 ~0 'interpolated'<br />ADD_STRING ~0 ~0 ' '<br />ADD_VAR ~0 ~0 !0<br />ECHO ~0<br /></pre></blockquote> <p>Where !0 represents the compiled variable named <span style="font-family:courier;">$string</span>. Looking at these opcodes: <span style="font-family:courier;">INIT_STRING</span> allocates an <span style="font-family:courier;">IS_STRING</span> variable of one byte (to hold the terminating <span style="font-family:courier;">NULL</span>). Then it's realloc'd to five bytes by the first <span style="font-family:courier;">ADD_STRING</span> ('This' plus the terminating <span style="font-family:courier;">NULL</span>). Next it's realloc'd to six bytes in order to add a space, then again to eight bytes for 'is', then nine to add a space, and so on until the temporary string has the contents of the interpolated variable copied into its contents before being used by the echo statement and finally discarded. Now let's rewrite that line to avoid interpolation and use concatenation instead:</p> <blockquote><pre style="font-family:courier new;font-size:78%;">&lt;?php<br />echo "This is a concatenated " . $string;<br />?&gt;<br /></pre></blockquote> <p>Which yields the significantly shorter and simpler set of ops:</p> <blockquote><pre style="font-family:courier new;font-size:78%;">CONCAT ~0 'This is a concatenated ' !0<br />ECHO ~0<br /></pre></blockquote> <p>A vast improvement already, but this version still creates a temporary <span style="font-family:courier;">IS_STRING</span> variable to hold the combined string contents meaning that data is duplicated when it's being used in a <span style="font-family:courier;">const</span> context anyway. Now let's try out this oft-overlooked use of the echo statement:</p> <blockquote><pre><span style="font-family:courier new;font-size:85%;">&lt;?php</span><span style="font-size:85%;"><br /></span><span style="font-family:courier new;font-size:85%;">echo "This is a stacked echo " , $string;</span><span style="font-size:85%;"><br /></span><span style="font-family:courier new;font-size:85%;">?&gt;</span><br /></pre></blockquote> <p>Look close, there is a meaningful difference from the last one. This time we're using a comma rather than a dot between the operands. If you don't know what the comma is doing there, ask <a href="http://www.php.net/echo">the manual</a> then check back here. Here's the resulting opcodes:</p> <blockquote><pre><span style="font-family:courier new;font-size:78%;">ECHO 'This is a stacked echo '</span><span style="font-size:78%;"><br /></span><span style="font-family:courier new;font-size:78%;">ECHO !0</span><br /></pre></blockquote><p>Same number of opcodes, but this time no temporary variables are being created so there's no duplication and no pointless copying (unless of course <span style="font-family:courier;">$string</span> wasn't of type <span style="font-family:courier;">IS_STRING</span>, in which case it does have to be converted for output, but don't get picky now). Think this is bad? Consider the average heredoc string which spans several lines of prepared output embedding perhaps a handful of variables along the way. Here's one of several such blocks found in <span style="font-family:courier;">run-tests.php</span> within the PHP distribution source tree:</p> <br /><blockquote><pre style="font-family:courier new;font-size:78%;">&lt;?php<br />echo &lt;&lt;NO_PCRE_ERROR<br /><br />+-----------------------------------------------------------+<br />| ! ERROR ! |<br />| The test-suite requires that you have pcre extension |<br />| enabled. To enable this extension either compile your PHP |<br />| with --with-pcre-regex or if you've compiled pcre as a |<br />| shared module load it via php.ini. |<br />+-----------------------------------------------------------+<br /><br />NO_PCRE_ERROR;<br />?&gt;<br /></pre></blockquote> <br /><p>Notice that we're not even embedding variables to be interpolated here, yet does this come out to a simple, single opcode? Nope, because the rules necessary to catch a heredoc's end token demand the same careful examination as double-quoted variable substitution and you wind up (in this case) with SEVENTY-EIGHT opcodes! One <span style="font-family:courier;">INIT_STRING</span>, 76 <span style="font-family:courier;">ADD_STRING</span>s. and a final <span style="font-family:courier;">ECHO</span>. That means a malloc, 76 reallocs, and a free which will be executed every time that code snippet comes along. Even the original contents take up more memory because they're stored in 76 distinct zval/<span style="font-family:courier;">IS_STRING</span> structures.</p><p>Why does this happen? Because there are about a dozen ways that a variable can be hidden inside an interpolated string. Similarly, when looking for a heredoc end-token, the token can be an arbitrary length, containing any of the label characters, and may or may not sit on a line by itself. Put simply, it's too difficult to encompass in one regular expression.</p> <p>The engine <i>could</i> perform a second-pass during compilation, however the time saved reassembling these strings will typically be about the same amount of time spent actually processing them during runtime (if one assumes that each instance will execute exactly once). Rather than complicate the build process (potentially slowing down overall run-times in the process), the compiler leaves this optimization step to opcode caches which can achieve exponentially greater advantage cleaning up this mess then caching the results and reusing the faster, leaner versions on all subsequent runs.</p><p>If you're using <a href="http://pecl.php.net/package/apc">APC</a>, you'll find just such an optimizer built in, but not enabled by default. To turn it on, you'll need to set <span style="font-family:courier;">apc.optimization=on</span> in your php.ini. In addition to stitching these run-on opcodes back together, it'll also add run-time speed-ups like pre-resolving persistent constants to their actual values, folding static scalar expressions (like 1 + 1) to their fixed results (e.g. 2), and simpler stuff like avoiding the use of JMP when the target is the next opcode, or boolean casts when the original expression is known to be a boolean value. (It should be noted that these speed-ups also break some of the runtime-manipulation features of <a href="http://pecl.php.net/package/runkit">runkit</a>, but that was stuff you....probably should have been doing anyway)</p><p>Can't use an optimizer because your webhost doesn't know how to set php.ini options? You can still avoid 90% of the <span style="font-family:courier;">INIT_STRING</span>/<span style="font-family:courier;">ADD_STRING</span> dilema by simply using single quotes and concatenation (or commas when dealing with echo statements). It's a simple trick and one which shouldn't harm maintainability too much, but on a large, complicated script, you just might see an extra request or two per second.</p><br /></div></div>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com1tag:blogger.com,1999:blog-1234953837283832282.post-44890186639090335302006-06-07T14:20:00.000-07:002010-04-20T16:54:17.160-07:00Extending and Embedding PHP<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://farm1.static.flickr.com/75/174928046_e59eef7e6d_o.jpg"><img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 154px; height: 113px;" src="http://farm1.static.flickr.com/75/174928046_e59eef7e6d_o.jpg" alt="" border="0" /></a><br /><span class="entry-summary">It's official!!!! After a year in development Extending and Embedding PHP is now shipping from fine book stores everywhere.<br /><p><a href="http://blog.libssh2.org/uploads/eephp_stack.jpg" target="_new"><br /></a></p> <p>I've gotten good reviews from the half dozen people I know who've gotten their hands on it, and I <i>am</i> really satisfied with most of it. Do I think it could be better? Of course I do, but I don't think I was ever going to be completely satisfied.</p><br /><p>I've learned a lot through this process and while I don't see any more titles in my immediate future (there are things coming down the pipe which are likely to change my availability), I do expect that my next book, should it materialize, will be even better.</p><br /><p>If you pre-ordered it, you should see it soon, if you've already got your copy, let me know what you think! Did I skim over some topic too quickly? Did I belabour something else? If this book eventually finds it's way into a 2<sup>nd</sup> edition (no promises mind you), are there topics you want to see added? Tossed out? Expanded/Compressed?</p></span>Sara Golemonhttp://www.blogger.com/profile/14557640555210645219noreply@blogger.com1