Monday, February 28, 2005

566.aspx

Sick hardware on egilh.com

My server was sympathetic with me lately as it was getting sicker by the day:



  • Its cough CPU noise was loud enough to hear in a different room with two young kids in the house!

  • The secondary HD on my server had several bad sectors and Win2k decided to unmount it whenever it was unable to flush the write operations to the disk.  I blame it on the case design as the HDs were too close. The second HD mounted directly over the first one in the case got way too hot. The HD container I bought worked like a charm so the HD still worked. Sort of. Rebooting the server each time it lost the HD was a pain so I decided to fix the HD problem first.

This weekend I scheduled a thorough chkdsk, crossed my fingers and rebooted. The HD is 80GB but less than half full so I thought 1/2 hour should be enough for the chkdsk. Boy was I wrong. One hour later I got worried and shut down the server and lugged it to the kids room where I could connect a screen and keyboard to see what was happening 



(The server only has a mouse attached as I do all management via Terminal Server. Someone please tell me why Win2k refuses to work without a mouse which you cannot do anything with but boots happily without the keyboard you need to log in...)


Since I already had it handy I decided to fix the fan first. I had bought a replacement fan a few weeks earlier on PrimeStore.it where I bought the server several years ago. One neat thing is that they, like ePrice.it, offer free shipping if you can pick up the goods in one of their deposits.


I had never swapped a CPU fan before so I didn't really know what to expect.  I sure didn't expect this mess when I took of the heat sink:

Who put a chewing gum on my CPU? Same color, same consistency. Yuck!


Turns out it is a feature not a bug. The documentation in the box didn't mention anything but I found a tube of white stuff hidden below the new fan. I thought it as the usual humidity absorbing stuff that is included in most boxes with electronic devices. I had a look at the fan constructors web site and discovered that the chewing gum is quite important: put on a thin layer of the silicon stuff on the CPU before replacing the heat sink. I guess it's increases conductivity.


As soon as the CPU fan shut up I could hear that the hard disk was terminally ill. During chkdsk it made nasty clicking noises when it tried to read certain areas of the disk. I managed to find a 200GB in a local shop for 100 euro and recover 99% of my data. I only “lost“ one of my CDs that I had ripped to MP3. A few minutes later and I was able to play my entire CD collection in my Kiss player again :-)


At least I have an idea what to use the old hard disk for. Change of plans: the disk seems to work OK after the latest format so it stays attached as temporary backup disk for stuff that I can afford to loose.

Friday, February 25, 2005

557.aspx

COM+ Call Timers: the unofficial way

Not satisfied with the official way, I went looking for a simpler way. The Component Services Console displays the data so the information must be there somewhere. It is a waste of time and resources me to collect the data and calculate the call times when the information I want is already available.


As it turns out the unofficial way is a lot simpler to implement than the official way.


Step 1: include the comsvc.dll library
// ComSvcs library for internal com+ method call tracking
#import "c:\windows\system32\comsvcs.dll" exclude("IAppDomainHelper")



Step 2: get the statistics



  • Get an instance of the internal com+ tracker:  COMSVCSLib::IGetAppDataPtr ptrAppData(_T("{ecabafb9-7f19-11d2-978e-0000f8757e2a}"));
  • Get a list of applications and lop through them:  ptrAppData->GetApps(&nAppData, &appData);

    • Get the application data you are interested in: calls per second, total calls, etc
    • Get a list of classes in the application and loop on them, extracting the information you want: ptrAppData->GetAppData(oneApp.m_idApp, &nClsIDs, &aClsidData);

Example
This simple routine builds a XML string the brutal way with the com+ performance information (most error handling code removed for clarity)



/*


 Get the statistics from the hidden COM+ interface


 */


void CTracker::getStatistics(BSTR *output)


{


      const unsigned long MAX_APP_DATA = 500;


      const unsigned long PUSH_RATE = 1000;


 



      unsigned long nAppData = MAX_APP_DATA;


      LPSTR lpString;


      LPWSTR lpwString;


 


      COMSVCSLib::appData *aAppData;


 


      CString csStatistics = "";


 


      // Get an instance of the internal com+ tracker objet


      COMSVCSLib::IGetAppDataPtr ptrAppData(_T("{ecabafb9-7f19-11d2-978e-0000f8757e2a}"));


      csStatistics.Append("\r\n");


 


      // Step through the list of running application


      ptrAppData->GetApps(&nAppData, &aAppData);


      for (unsigned long idxApp=0; idxApp < nAppData; idxApp++)


      {


            unsigned long nClsIDs;


            COMSVCSLib::CLSIDDATA *aClsidData;


            COMSVCSLib::appData oneApp = aAppData[idxApp];


 


            csStatistics.Append(_T("\r\n"));


            UnicodeToAnsi(oneApp.m_szAppGuid, &lpString);


            csStatistics.AppendFormat(_T("%s\r\n"), lpString);


            CoTaskMemFree(lpString);


 


            // Application information


            csStatistics.AppendFormat(_T("%ld\r\n"), oneApp.m_idApp);


            csStatistics.AppendFormat(_T("%ld\r\n"), oneApp.m_dwAppProcessId);


           


            COMSVCSLib::APPSTATISTICS appStatistics = oneApp.m_AppStatistics;


            csStatistics.AppendFormat(_T("\r\n"));


            csStatistics.AppendFormat(_T("\t%ld\r\n"), appStatistics.m_cCallsPerSecond);


            csStatistics.AppendFormat(_T("\t%ld\r\n"), appStatistics.m_cTotalCalls);


            csStatistics.AppendFormat(_T("\t%ld\r\n"), appStatistics.m_cTotalClasses);


            csStatistics.AppendFormat(_T("\t%ld\r\n"), appStatistics.m_cTotalInstances);


            csStatistics.AppendFormat(_T("\r\n"));


 


            // Get class information for this application


            ptrAppData->GetAppData(oneApp.m_idApp, &nClsIDs, &aClsidData);


            csStatistics.AppendFormat(_T("\r\n"));


            for (unsigned long idxClass = 0 ; idxClass < nClsIDs; idxClass++)


            {


                  COMSVCSLib::CLSIDDATA oneClass = aClsidData[idxClass];


                  csStatistics.AppendFormat(_T("\r\n"));


                 


                  // Get the progID for the guid.


                  // This FAILS during recyling on Win2k3


                  if (!FAILED((ProgIDFromCLSID(oneClass.m_clsid,&lpwString))))


                  {                 


                        UnicodeToAnsi(lpwString, &lpString);


                        csStatistics.AppendFormat(_T("\t%s\r\n"), lpString);


                        CoTaskMemFree(lpString);


                  }


 


                  // Performance information


                  csStatistics.AppendFormat(_T("\t%ld\r\n"), oneClass.m_cBound);


                  csStatistics.AppendFormat(_T("\t%ld\r\n"), oneClass.m_cInCall);


                  csStatistics.AppendFormat(_T("\t%ld\r\n"), oneClass.m_cPooled);


                  csStatistics.AppendFormat(_T("\t%ld\r\n"), oneClass.m_cReferences);


                  csStatistics.AppendFormat(_T("\t%ld\r\n"), oneClass.m_dwRespTime);


                  csStatistics.AppendFormat(_T("\t%ld\r\n"), oneClass.m_cInCall);


                  csStatistics.AppendFormat(_T("\t%ld\r\n"), oneClass.m_cInCall);


                 



                  csStatistics.AppendFormat(_T("\r\n"));


            }


            CoTaskMemFree(aClsidData);


            csStatistics.AppendFormat(_T("\r\n"));


            csStatistics.AppendFormat(_T("\r\n"));


      }


      csStatistics.Append("\r\n");


      CoTaskMemFree(aAppData);


 


      *output = csStatistics.AllocSysString(); 


}


 
That's it!
Less than a 100 lines of code to build a XML document with the call times for the running COM+ applications on the system.


This approach as several benefits over the official way:



  • It is a lot simpler to implement and maintain (100 lines of C++ vs 1.500)
  • I have found no memory leaks after 9 months in production
  • Less resource usage. COM+ already maintains the counters, I just get them when I need them.

Keep in mind that this is an undocumented API so it may change one day in the future (Longhorn?).

Thursday, February 24, 2005

552.aspx

COM+ Call Timers: the official way

As the title suggests, there are more than one way to get access to the COM+ call timers. This post gives and overview of how to get the call times using the official Microsoft COM+ Instrumentation APIs.


Why mess with the COM+ call timers?
I had a serious problem with components accessing an Oracle DB using OLEDB. In certain cases we queries never ended. Query timeouts are not supported in the Oracle OLEDB nor Microsoft OLEDB provider and changing the resource constraints in Oracle did not improve the situation. If the query went in tilt, it stayed in tilt -forever-. That is a bad, bad, thing in a system with hundreds of calls per second.. We tracked down the problem to a specific case: the Oracle stored procedure never returned if the stored procedure "header" was OK but the stored procedure "body" was invalid. The stored procedure call waited forever for the the stored procedure to get recompiled. In normal situation it recompiled by itself but there were cases with linked DBs etc where the Oracle stored procedure body stayed invalid until someone recompiled it by hand.


We tried asynchronous queries but they did not fix the situation. If the query was stuck, it stayed stuck and the abort method hung as well. We found one way that did work: use a separate thread for the query and kill the thread if it took to much time. The query did not timeout anymore but the memory leaks were enormous when we brutally terminated the thread.


The only fix I found was to implement COM+ recycling on steroids. Monitor the call times and shut down (recycle on Win2k3) the com+ package if the call times got too high. The stored procedure was still broken but the rest of the system continued working. No memory leak problems either as the com+ process was shut down in an orderly manner.


How to get the com+ call timers - the official way
The Component Services Console displays all sort of useful information like the number of objects in call, average call time etc. But, there is no official way to get the com+ call timers directly. You have to do the dirty work yourself and calculate the average call times.


The COM+ Spy example in the \Samples\Com\Administration\Spy in the Platform SDK shows how to get all the COM+ information you desire. Below I show the basic steps but please download the Platform SDK for a complete working example.


The COM+ internal information is made available via Loosely Coupled Events. You subscribe to one or more COM+ events and implement the corresponding interface to get notified. There are a whole set of COM+ Instrumentation Interfaces. The important ones for COM+ call tracking are:



They allow you to monitor COM+ application activation/shutdown and method call/return and provide you with all you need to calculate the call time. I used COM+ Spy as a skeleton and choose the following approach to tracking the call times:



  • Implement a tracking object (CMethodMon in the example below)  for each com+ application you want to monitor. The class:

    • Implements the IComMethodsEvent interface to track call times
    • Implements the IComAppEvents interface to clear the current calls when an application shuts down
    • Subscribes to the IComMethodsEvent and IComAppEvents
    • Has one timer that fires every X seconds (configurable). The timer:

      • Calculates the average call time (highestCalltimes below)
      • Take appropriate action if the call times are too high (shut down component etc)


IComMethodsEvents
This is THE interface for call time tracking. It is called when a method is called or returns and when an exception occurs. You get a lot of information in the COMSVCSEVENTINFO structure like the process ID, current time stamp etc.


Keeping track is quite simple:



  • Create a stack for each activate object (OID stack):
  • Push call information on the OID stack in OnMethodCall. In my case I only care about the call time so I only push the performance counter
  • Pop from the OID stack in OnMethodReturn

// Stack definition (only contains the time stamp as that's all I care about in this app)


typedef map TimeMap;


imeMap m_map;


 


STDMETHODIMP CMethodMon::OnMethodCall (COMSVCSEVENTINFO * pInfo, ULONG64 oid, REFCLSID cid, REFIID rid, ULONG iMeth)


{


      EnterCriticalSection(&m_csMapLock);


      TimeStack * pStack = m_map[oid];


      if (!pStack)


      {


            pStack = new TimeStack;


            m_map[oid] = pStack;


      }


      pStack -> push_front(pInfo->perfCount);


      LeaveCriticalSection(&m_csMapLock); 


      return S_OK;


}


 


STDMETHODIMP CMethodMon::OnMethodReturn (COMSVCSEVENTINFO * pInfo, ULONG64 oid, REFCLSID cid, REFIID rid, ULONG iMeth, HRESULT hr)


{


      TimeStack * pStack = m_map[oid];


      pStack -> pop_front();


     


      // Remove the entry for the oid if it's call stack is empty


      if (pStack -> empty())


      {    


            delete pStack;


            m_map.erase(oid);


      }


      return S_OK;


}


 


IComAppEvents
Only the OnAppShutdown event is of interest in the  IComAppEvents:


STDMETHODIMP CMethodMon::OnAppShutdown(COMSVCSEVENTINFO * pInfo, GUID guidApp)


{


      ClearCallTimes();


      return S_OK;


}


void CMethodMon::ClearCallTimes()


{


      EnterCriticalSection(&m_csMapLock);


     


      // Clear all the call times


      TimeMap::iterator iter;


      for (iter = m_map.begin( ); iter != m_map.end( ); iter++ )


      {


            TimeStack *pStack = (TimeStack *) (*iter).second;


 


            while (!pStack->empty())


            {


                  pStack -> pop_front();


            }


      }


      m_map.clear();


      LeaveCriticalSection(&m_csMapLock);


}


Calculating the highest call time is straightforward:


unsigned long CMethodMon::highestCallTime(void)


{


     


      long lHighestCallTime = 0;


      LONGLONG PerformanceFrequency;


      QueryPerformanceFrequency((LARGE_INTEGER *)&PerformanceFrequency);


 


      LONGLONG lNow;               


      QueryPerformanceCounter((LARGE_INTEGER *) &lNow);


 


      EnterCriticalSection(&m_csMapLock);


 


      // Step through all the oid's currently in call


      TimeMap::iterator iter;


      for (iter = m_map.begin( ); iter != m_map.end( ); iter++ )


      {


            TimeStack *pStack = (TimeStack *) (*iter).second;


 


            // Get the LAST element in the call stack as it will be the oldest element


            if (pStack)


            {


                  LONGLONG lOldest = pStack->back();


                  unsigned long lThisCallTime = (unsigned long)((1000*(lNow - lOldest))/


                                                                 PerformanceFrequency);


 


                  if (lThisCallTime > lHighestCallTime)


                  {


                        lHighestCallTime = lThisCallTime;


                  }


            }


      }          


     


      LeaveCriticalSection(&m_csMapLock);


     



      return lHighestCallTime;


}


 


There are known issues with the implementation above. Some error handling code has been removed and it does not take into account the fact that the PerformanceCounter will wrap sooner or later.


 


Issues with the official approach
The component gets called for each COM+ call (in the applications you monitor). Interesting in itself but you have to be a 110% sure you don't introduce deadlocks, memory leaks or anything else that affects the stability of the system.


I used the official approach for more than a year. It worked like a charm for weeks on end, but at random times I would get an enormous memory leak (>1GB) in the Windows RPC sub system on Win2k (I have not tried it on Win2k3). The leak may have been due to another process making heavy use of using COM+ notifications, a bug in my code or something else. It only happened when the machine was under heavy stress (100% CPU, and more than 1000 notifications per second for a few hours) so my -wild- guess is that other tasks got higher priority and the notifications continued to queue up until the system went in tilt.


The solution is not trivial to implement and requires the developer to know C++, ATL, COM admin APIs as well as Loosely Coupled Events. Finding skilled C++ COM+ programmers is hard so I decided to look for an easier to maintain solution. I will post the unofficial way I found tomorrow. It is a lot simpler and drops the source code from 1.500 lines of C++ to less than 150.

Wednesday, February 23, 2005

550.aspx

RootkitRevealer

SysInternals does it again and delivers another impressive tool: RootkitRevealer



RootkitRevealer is an advanced root kit detection utility. It runs on Windows NT
4 and higher and its output lists Registry and file system API discrepancies that
may indicate the presence of a user-mode or kernel-mode rootkit. RootkitRevealer successfully detects all persistent rootkits published at www.rootkit.com, including AFX, Vanquish and HackerDefender (note: RootkitRevealer is not intended to detect memory-based rootkits like Fu that don't survive reboots).


Via [Sysinternals]

Tuesday, February 22, 2005

543.aspx

Visual Studio 2005 Device Command Shell [v0.98]

The Device Command Shell (DCS) is a very useful tool if you do Pocket PC, aka Smart Device, development using Visual Studio 2005.


DCS is a Command Window addin that gives you command line access to the most common operations during PPC development: copy files, edit the registry, list/start/stop processes, install DLLs etc. The list of commands continues to grow with each release:
CE Batch
CE Certs
CE Config
CE Connect
CE Copy
CE CreateRegKey
CE CreateRegKeyValue
CE Del
CE DeleteRegKey
CE DeleteRegKeyValue
CE Depends
CE Get
CE GetRegKey
CE Help
CE Install
CE IP
CE List
CE MD
CE Ping
CE Query
CE RD
CE RegSvr (new in 0.98)
CE SetPolicy
CE SetProxy
CE SetRegKey
CE Start
CE Stop
CE TList


You can download the Device Command Shell and documentation from my GotDotNet workspace. Set up the aliases above by performing these steps after installing DCS:



  • Bring up the command window: go to the "View" menu, select "Other Windows", then "Command Window”.

  • Enter the following command: DeviceCommandShell.Connect.DeviceCommandShell alias

I have one wish for DCS team: I hope they create a version that works 2003 as well. This thing is just too useful to have to wait for VS2005!


Via [Visual Studio For Devices]


 

Monday, February 21, 2005

542.aspx

Community Server 1.0 released

Community Server 1.0 was released this weekend. The source code is not available yet but I have downloaded the binaries so I can try the various components: blog, forum and photo gallery.


I will post my experience with the .TEXT migration as soon as the migration tool/scripts for Community Server 1.0 have been released.


Via [Duncan Mackenzie]

Sunday, February 20, 2005

540.aspx

Citibank fights phishing the wrong way

Citibank must be one of the most common targets of phishing scams around. I have lost track of the fake mails I have received and forwarded to Citibank security.


The Citibank on screen keyboard described by BetaNews smells like a publicity stunt to show that they take security seriously and that they are doing something. Or are they really clueless enough to think that this online keyboard will improve security?


It is true that some basic keyboard loggers do not work with an on screen keyboard but it is lot less secure than a normal password field:



  • It is easer to see which password the user enters as you just have to follow the mouse on screen as it clicks the characters one by one. Discovering my password by shoulder surfing is a lot more difficult as I touch type pretty fast.

  • It limits which letters can be inserted. There is no Shift key so you are stuck with uppercase letters only and a very limited set of special characters. I am not paranoid enough to use AltGr to enter random characters but I do use a mix of upper case, lower case, numbers and extended European characters.

  • This keyboard does not prevent phishing. The JavaScript keyboard will show up on the phishing sites and the phishers will continue to get the clear text username and password like they have in the past.

You cannot type in the password field, but all is not lost as they did not disable paste functionality. Good password managers like Password Safe and the one I'm working on in my spare time, continue to work as they allow you to paste the password without ever displaying it on the screen.


I feel a lot safer with other banks that offer some sort of two factor authentication:



My credit card company sends me a free SMS alert when someone charges my card which makes me feel pretty much in control.


Click here to see the folly on screen keyboard at work

538.aspx

Scatole (boxes) exhibition

We went to see the Scatole (boxes) exhibition with the kids the weekend before I got sick. 


A bunch of boxes may not sound like a lot of fun but the kids enjoyed it. The exhibition is targeted at kids between 4 and 10 years. It is by reservation only and each session lasts about 75 minutes. The English description sounds more artistic than it is:



It presents a double aspect: a scientific one, the exploration of bidimensionality and tridimensionality, and a creative one, boxes are seen as containers to be explored or filled with ideas, wishes, secrets, and therefore they are to be imagined, built, decorated, and transformed.



The exhibition is open until 14 May 2005 at



Villa Ghirlanda
Cinisello Balsamo


Booking: 02.61 73 005
Scatole site


Muba also has a COLORS exhibition in Milano. We are going as soon as I am better.

Saturday, February 19, 2005

537.aspx

Coming down from my cotton cloud

It has been one long week with high fever 24x7, feeling like I was living in a world of cotton. Everything was slow and fluffy, including my speech. Not that I normally mind fever as it makes me sleep 24 hours a day. But walking around in a permanent state of confusion is not an option when you have to do something useful like watching the kids so I have pretty much lived on paracetamol, vitamins, water and the love of my family.


The fever is gone but some bugs took advantage of my condition to give me a nasty airway infection. A few days on antibiotics and I will be back in business.


I thought all the time relaxing doing nothing would give me plenty of time to think about new things to code and blog. No way: my brain was in no mood to do serious work. The few moments I was awake I spent looking out the window on the crew restoring a house next door and reading. After re-reading the same page in my Japanese course four times, I went back to no-brainer books and finished my current inventory:



I started reading the eBook Down and out in the magic kingdom by Cory Doctrow of BoingBoing fame. I loved the part I managed to read (until the main character has been restored after being killed in Disneyland). My copy of the eBook is corrupted so I hope there still is a free download on Cory Doctrow's site  when I get well enough to spend some time in front of a computer looking for it.


I have kept up with e-mail on my Pocket PC so I don't feel the call of the computer and the internet (yet).  I do feel guilty for not updating my blog, Cool Or What? and Crap And Crapability but I should be back in shape for posting after the weekend.


Thanks to my wife and kids for taking care of me and letting me rest as much as I have!

Tuesday, February 8, 2005

532.aspx

Process Explorer 9.0

What can I say: the Process Explorer from SysInternals is a great tool for understanding what is happening on a system. Version 9.0 has several new features:



  • System information dialog has per-CPU graph option with hyperthreaded and NUMA processor information

  • A Users menu duplicates the functionality of Task Manager's Users tab, showing Terminal Services session information and supporting logoff, disconnect, and sending messages

  • On XP SP2 and higher the TCP/IP tab displays the thread stack at the time an endpoint was opened

  • The tray icon context menu includes the shutdown menu
    Search engine option to use Google or MSN Search

  • Object address column is available for the handle view

  • Image signatures can be checked on-demand in the process properties dialog

  • Process explorer is digitally signed with Sysinternals' Verisign Class 3 signing certificate

531.aspx

Fresh spaghetti code

The Daily WTF just cracks me up sometimes . Todays code sniplet, Yet Another "Hex To Ascii",  is a perfect example of how a simple task can be made complex with goto. It is difficult to be the worst though as this example shows: look at me, I know how to write loops.


One of the worst sql statements I have seen in a production environment does a simple date filter:


CREATE VIEW dbo.V_News_Details
AS
SELECT     TOP 100 PERCENT dbo.Details.idDetail, dbo.Details.sTitle, dbo.Details.sText, dbo.Details.dValidFrom,
                      dbo.Details.dValidTo, dbo.Details.dNewsHour, dbo.Details.dValidHour, dbo.Details.idService,
                      dbo.Details.lPos, dbo.Details.idImage, b.Status
FROM         dbo.Details LEFT OUTER JOIN
                      dbo.Images b ON b.Id = dbo.Details.idImage
WHERE     (CONVERT(char(10), GETDATE(), 111) BETWEEN CONVERT(char(10), dbo.Details.dValidFrom, 111) AND CONVERT(char(10),
                      dbo.Details.dValidTo, 111)) AND (dbo.Details.dValidHour IS NULL) AND (dbo.Details.dNewsHour IS NULL) OR
                      (dbo.Details.dValidFrom IS NULL) AND (dbo.Details.dValidTo IS NULL) OR
                      (CONVERT(char(10), GETDATE(), 111) <= CONVERT(char(10), dbo.Details.dValidTo, 111)) AND (dbo.Details.dValidHour IS NULL)
                      AND (dbo.Details.dNewsHour IS NULL) AND (dbo.Details.dValidFrom IS NULL) OR
                      (CONVERT(char(10), GETDATE(), 111) >= CONVERT(char(10), dbo.Details.dValidFrom, 111)) AND (dbo.Details.dValidHour IS NULL)
                      AND (dbo.Details.dNewsHour IS NULL) AND (dbo.Details.dValidTo IS NULL) OR
                      (CONVERT(char(10), GETDATE(), 111) > CONVERT(char(10), dbo.Details.dValidFrom, 111)) AND (CONVERT(char(10), GETDATE(), 111)
                      < CONVERT(char(10), dbo.Details.dValidTo, 111)) OR
                      (CONVERT(char(10), GETDATE(), 111) > CONVERT(char(10), dbo.Details.dValidFrom, 111)) AND (dbo.Details.dValidTo IS NULL) OR
                      (CONVERT(char(10), GETDATE(), 111) < CONVERT(char(10), dbo.Details.dValidTo, 111)) AND (dbo.Details.dValidFrom IS NULL) OR
                      (dbo.Details.dValidHour IS NOT NULL) AND (dbo.Details.dNewsHour IS NOT NULL) AND
                      (dbo.Details.dValidFrom IS NOT NULL) AND (dbo.Details.dValidTo IS NOT NULL) AND (CONVERT(datetime, CONVERT(char(4),
                      DATEPART(yyyy, GETDATE())) + '/' + CONVERT(char(2), DATEPART(mm, GETDATE())) + '/' + CONVERT(char(2), DATEPART(dd, GETDATE()))
                      + ' ' + CONVERT(char(2), DATEPART(hh, GETDATE())) + ':' + CONVERT(char(2), DATEPART(mi, GETDATE()))) BETWEEN CONVERT(datetime,
                      CONVERT(char(4), DATEPART(yyyy, dbo.Details.dValidFrom)) + '/' + CONVERT(char(2), DATEPART(mm, dbo.Details.dValidFrom))
                      + '/' + CONVERT(char(2), DATEPART(dd, dbo.Details.dValidFrom)) + ' ' + CONVERT(char(2), DATEPART(hh, dbo.Details.dNewsHour))
                      + ':' + CONVERT(char(2), DATEPART(mi, dbo.Details.dNewsHour))) AND CONVERT(datetime, CONVERT(char(4), DATEPART(yyyy,
                      dbo.Details.dValidTo)) + '/' + CONVERT(char(2), DATEPART(mm, dbo.Details.dValidTo)) + '/' + CONVERT(char(2), DATEPART(dd,
                      dbo.Details.dValidTo)) + ' ' + CONVERT(char(2), DATEPART(hh, dbo.Details.dValidHour)) + ':' + CONVERT(char(2), DATEPART(mi,
                      dbo.Details.dValidHour)))) OR
                      (dbo.Details.dValidHour IS NULL) AND (dbo.Details.dNewsHour IS NOT NULL) AND
                      (dbo.Details.dValidFrom IS NOT NULL) AND (dbo.Details.dValidTo IS NOT NULL) AND (CONVERT(datetime, CONVERT(char(4),
                      DATEPART(yyyy, GETDATE())) + '/' + CONVERT(char(2), DATEPART(mm, GETDATE())) + '/' + CONVERT(char(2), DATEPART(dd, GETDATE()))
                      + ' ' + CONVERT(char(2), DATEPART(hh, GETDATE())) + ':' + CONVERT(char(2), DATEPART(mi, GETDATE()))) BETWEEN CONVERT(datetime,
                      CONVERT(char(4), DATEPART(yyyy, dbo.Details.dValidFrom)) + '/' + CONVERT(char(2), DATEPART(mm, dbo.Details.dValidFrom))
                      + '/' + CONVERT(char(2), DATEPART(dd, dbo.Details.dValidFrom)) + ' ' + CONVERT(char(2), DATEPART(hh, dbo.Details.dNewsHour))
                      + ':' + CONVERT(char(2), DATEPART(mi, dbo.Details.dNewsHour))) AND CONVERT(datetime, CONVERT(char(4), DATEPART(yyyy,
                      dbo.Details.dValidTo)) + '/' + CONVERT(char(2), DATEPART(mm, dbo.Details.dValidTo)) + '/' + CONVERT(char(2), DATEPART(dd,
                      dbo.Details.dValidTo)) + ' ' + '23:59')) OR
                      (dbo.Details.dValidHour IS NOT NULL) AND (dbo.Details.dNewsHour IS NULL) AND
                      (dbo.Details.dValidFrom IS NOT NULL) AND (dbo.Details.dValidTo IS NOT NULL) AND (CONVERT(datetime, CONVERT(char(4),
                      DATEPART(yyyy, GETDATE())) + '/' + CONVERT(char(2), DATEPART(mm, GETDATE())) + '/' + CONVERT(char(2), DATEPART(dd, GETDATE()))
                      + ' ' + CONVERT(char(2), DATEPART(hh, GETDATE())) + ':' + CONVERT(char(2), DATEPART(mi, GETDATE()))) BETWEEN CONVERT(datetime,
                      CONVERT(char(4), DATEPART(yyyy, dbo.Details.dValidFrom)) + '/' + CONVERT(char(2), DATEPART(mm, dbo.Details.dValidFrom))
                      + '/' + CONVERT(char(2), DATEPART(dd, dbo.Details.dValidFrom)) + ' ' + '00:00') AND CONVERT(datetime, CONVERT(char(4), DATEPART(yyyy,
                      dbo.Details.dValidTo)) + '/' + CONVERT(char(2), DATEPART(mm, dbo.Details.dValidTo)) + '/' + CONVERT(char(2), DATEPART(dd,
                      dbo.Details.dValidTo)) + ' ' + CONVERT(char(2), DATEPART(hh, dbo.Details.dValidHour)) + ':' + CONVERT(char(2), DATEPART(mi,
                      dbo.Details.dValidHour)))) OR
                      (dbo.Details.dValidHour IS NULL) AND (dbo.Details.dNewsHour IS NOT NULL) AND
                      (dbo.Details.dValidFrom IS NOT NULL) AND (dbo.Details.dValidTo IS NULL) AND (CONVERT(datetime, CONVERT(char(4),
                      DATEPART(yyyy, GETDATE())) + '/' + CONVERT(char(2), DATEPART(mm, GETDATE())) + '/' + CONVERT(char(2), DATEPART(dd, GETDATE()))
                      + ' ' + CONVERT(char(2), DATEPART(hh, GETDATE())) + ':' + CONVERT(char(2), DATEPART(mi, GETDATE()))) BETWEEN CONVERT(datetime,
                      CONVERT(char(4), DATEPART(yyyy, dbo.Details.dValidFrom)) + '/' + CONVERT(char(2), DATEPART(mm, dbo.Details.dValidFrom))
                      + '/' + CONVERT(char(2), DATEPART(dd, dbo.Details.dValidFrom)) + ' ' + CONVERT(char(2), DATEPART(hh, dbo.Details.dNewsHour))
                      + ':' + CONVERT(char(2), DATEPART(mi, dbo.Details.dNewsHour))) AND '2100/12/31' + ' ' + '23:59') OR
                      (dbo.Details.dValidHour IS NOT NULL) AND (dbo.Details.dNewsHour IS NULL) AND (dbo.Details.dValidFrom IS NULL)
                      AND (dbo.Details.dValidTo IS NOT NULL) AND (CONVERT(datetime, CONVERT(char(4), DATEPART(yyyy, GETDATE())) + '/' + CONVERT(char(2),
                      DATEPART(mm, GETDATE())) + '/' + CONVERT(char(2), DATEPART(dd, GETDATE())) + ' ' + CONVERT(char(2), DATEPART(hh, GETDATE()))
                      + ':' + CONVERT(char(2), DATEPART(mi, GETDATE()))) BETWEEN '1800/01/01' + ' 00:00' AND CONVERT(datetime, CONVERT(char(4), DATEPART(yyyy,
                      dbo.Details.dValidTo)) + '/' + CONVERT(char(2), DATEPART(mm, dbo.Details.dValidTo)) + '/' + CONVERT(char(2), DATEPART(dd,
                      dbo.Details.dValidTo)) + ' ' + CONVERT(char(2), DATEPART(hh, dbo.Details.dValidHour)) + ':' + CONVERT(char(2), DATEPART(mi,
                      dbo.Details.dValidHour))))
ORDER BY dbo.Details.lPos, dbo.Details.dValidFrom DESC, dbo.Details.dNewsHour DESC,
                      dbo.Details.idDetail DESC


The developers are so scared by the monster that the code is still used as the base view for most selects...


KISS people!

524.aspx

Ask Jeves buys Bloglines

The Ask Jeeves blog has the official announcement that they have bought Bloglines after the rumors started this weekend. According to Ask Jeeves:



There will be no short-term changes to Bloglines that weren't already on their roadmap. Over the long-term, Mark will now, as general manager of Bloglines for AJ, Inc., be responsible for that roadmap in the future.


I threw out my old RSS reader and imported all my feeds to Bloglines before going on Christmas vacation. Never looked back again. It is just great to be able to read my feeds any time, any place using Skweezer when I'm on the road. Better search in Bloglines would be cool, but what worries me is this section:



We will take our time determining the optimal business model for the service. We will continue to put the user experience first. As part of a bigger company there will be more options for Bloglines - from indirect monetization (through increased usage of our other brands) to direct if there is a model that makes sense for everyone.


Will Bloglines continue to stay free or will they limit the number of feeds to a ridiculously low number unless you subscribe to a premium service? I will continue to use Bloglines for now but I'm ready to roll my own system if things go down the drain.

Thursday, February 3, 2005

523.aspx

You can if you want!

You can if you want is one of my mottos. The amazing story of Alison Wright in Outside Magazine shows what people can accomplish when they really put their mind to it.



"My God, someone do something! This woman is bleeding to death!" I silently prayed that someone would help her. I turned my head to look at my watch and saw the gashes. My arm looked like it had been attacked by a shark, the denim shirt soaked red. It was then that I realized the woman they were yelling about was me
...
He stopped counting the broken ribs after six. He confirmed that my lungs were collapsed, and my diaphragm punctured. I had fractured teeth and sustained huge contusions all down the left side of my body. My spleen was also ruptured, my back, pelvis, and coccyx all broken. Most alarming was that all my internal organs, including my heart, and even my bowels, had been smashed up into my left shoulder.
...
The ER doctors called me "miracle kid," but it's taken fifteen months of hard work and a few more surgeries to recover. On Halloween, which seemed appropriate, I had another surgery to rearrange my intestines. The surgeons sewed my stomach lining up with plastic mesh to hold everything in place. I'm still picking out bits of glass and gravel that continue to work their way out of my arm, giving me these terrible bouts of blood poisoning. After months of physical therapy, I can now walk. I've gotten my lungs back, too, and except for some nasty scars and lingering pain, I should be able to climb mountains and scuba dive again.


And i thought I had a hard time when I broke my back a year ago...
 
Via [The Furrygoat Experience]
P.S. Check out the travelers advice on the last page.

522.aspx

Extracting files from a remote ZIP archive

The Extracting files from a remote ZIP archive article on Code Project is both interesting and useful. It explains how you can download only the files you are interested in from a large .zip file on a remote server. The program explains how to implement it using HTTP, but the same approach can be used for FTP or files hosted on a file server.


The beauty of the program is that all the logic is on the client so it works with all web servers that supports HTTP resume. The program downloads the header part of the .zip to know which files are in the archive. It then uses the HTTP Range header (RFC 2616) to download only the parts of the zip it needs for the file(s) you want.


Via [Code Project newsletter]

Wednesday, February 2, 2005

519.aspx

Java vs .NET, Microsoft vs SUN and other loaded development issues

This is not a happy post. You better skip reading this if you're a Java fan or otherwise don't feel like listening to my gripes Java.


There is one thing I love about programming for Microsoft platforms (client/server/web/PDA/…): Microsoft's online documentation is vastly superior to anything else out there. It used to be true of Borland and it is the reason I taught myself programming from the Turbo Pascal help and examples files. There are tons of example code available in the various SDKs, MSDN and other online forums. Microsoft knows that the success of Windows depends of a horde of happy developers spewing out solutions using easy to use tools like VB.


Being (sort of) open source, I would have expected Java to be even better. Not so. I am in in deep shit these days for a making a 3rd party solution work, that goes in production Sunday morning at 4am. Failure is not an option so I have to Make The Damn Thing Work. Easier said than done as the SUN Java Virtual Machine explodes below my butt leaving no trace. Now, I don't give up easily, so I have tried every trick I can think of to the SUN JVM log what makes it commit suicide:



  • use a class running in the JVM to redirect stderr to a file

  • redirect stderr/stdout to a file from my host using fredir()

  • use the JNI version 1.2 JNI options:


    • vfprintf

    • exit

    • abort

No way. It JVM kills the host process without logging anything or calling any of the callbacks.. A global try/catch doesn't catch any JNI errors so it's definitely the JVM that kills the host for some reason.


The official online documentation on java.sun.com regarding the corner stone of Java, the JVM, still contains refers to an ancient version of Java: This document is written from the perspective of 1.3.1 JVM on the Solaris (SPARC Platform Edition) on the subject of Garbage Collection. GC is, as the document states, the principal bottleneck in many solutions so I can't understand why they document it better. .NET from this point of view is nothing short but fantastic. MSDN and other Microsoft publications/sites are full of details on .NET Garbage Collections and how it works in version 1.0, 1.1 and the still to be published 2.0.


I was working on a critical server component last year and used Performance Monitor in Win2k3 to keep an eye on how various versions worked out. It covers all issues, including how much time is spent in Garbage Collection, memory usage etc. Use Process Explorer and you can see anything a process does.


Not so with the Java 1.4.2 bug I have been fighting the last few days. The JVM shuts down without leaving a trace; no errors to stderr, no logs to the HotSpot log file. It just silently commits suicide. After several sleepless nights, it looks like I hope we have found the problem. The JVM bombs without logging anything Garbage Collection if it is running low on memory. This JVM bug was fixed in version 1.3.1, 1.4.0 and 1.4.1 so I hope it is not still haunting us. Since it does not log any thing it's pretty much like hunting for the famous needle in the haystack. We will try a mix of Java GC and Java Performance settings tomorrow to see if it is possible to make it run for more than 5 minutes before crashing.


Reading the JVM options guide it is clear why Java runs a faster on Solaris than in wintel. Several of the tuning options are only available on Solaris and the other ones have different default values (>2MB for default new gen in Solaris but Intel is limited to 640kb, 32mb max new gen on SPARC, 2mb on Intel)


Don't get me wrong. Java is a neat language. C# copied all of it's cool features just like Java world copied the latest Microsoft features from the last millennium (jsp, data sets and transactions etc).


Time to get a grip on infant mortality and other nasty concepts before going to bed.