Friday, July 1, 2011

PowerShell Custom Object Factory

I needed to create custom objects with specific properties for programming against a WPF GUI, because WPF programming is simplest when it binds properties of controls to properties of an object. But I loathed the thought of writing a function to create a custom object, and having to duplicate the parameter name as a Property name in the new-object call. I also wanted to have default values for the object’s Properties.

The following solution is a template – Use it when you need a New-BlahBlahObject function. Simply add the parameters you want to the Param() block, and this function will hand you back a PSObject with the Parameters turned into Properties. As a bonus, the ParameterSetName property allows you to use this as a kind of object factory – depending on which arguments you supply, different Properties will get created, and the value of the ParameterSetName will tell you which properties are present.

You can do a lot more customization before the call to new-object in the last line of the function, by modifying the $p2 string. You can also add functions to the object before you return it, like Add, so you can “add” two of these custom objects together.

[Author’s note: If anybody know how to add a “code” widget to a Blogger Blog, please tell me so in a comment!. The following formatting is terrible… I hope word-wrap doesn’t prevent cut’n’paste from working]

function New-CustomDemoObject {
  [CmdletBinding(DefaultParametersetName='All')]
  Param (
   [parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] $Workbook = 'T1.xlsx'
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][switch] $isVisible
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][switch] $isKeepOpen
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][string] $SortDirection = 'Ascending'
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][string[]] $DatabaseConnectionStrings = @('connecstr1','connectstr2')
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] $ParameterWithDefaultArray = @('connecstr5','connectstr6')
  ,[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][hashtable] $CommandStringsHash  = @{cmd1=@{cmdstr='a command';vo=1;so=2};cmd2=@{cmdstr='b command';vo=2;so=1}}
  ,[parameter(ParameterSetName='PathToDir',ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] $PathToDir = 'C:/Logs'
  ,[parameter(ParameterSetName='PathToZip',ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] $PathToZip = 'C:/Logs/Backups'
  ,[parameter(ParameterSetName='PathToZip',ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] $PathInZip = './'
  ,[parameter(Position=0, ValueFromRemainingArguments=$true)] $RemainingArgs
  )
  # Snippet that creates an object from the parameters
  # Create a list of the common parameters as a RegExp match string
  $cp = iex 'function gcp{[CmdletBinding()]Param()$p1=@();((gci Function:$($pscmdlet.MyInvocation.MyCommand)).Parameters).Keys|%{$p1+=$_};$p1|%{"^$_`$"}};[string]::join("|",(gcp))'
  # Get a list of this function's parameters, eliminate the commonparameters, make a hash with the parameter name and type
  $p1=@{};((gci Function:$($pscmdlet.MyInvocation.MyCommand)).Parameters).Keys|?{$_ -notmatch $cp}|%{$p1.$_=((gci Function:$($pscmdlet.MyInvocation.MyCommand)).Parameters).$_.ParameterType.Name}
  # Iterate this function's real parameters, turn switches to bools, remove ActionPreference (and you can do whatever else you want to in this loop)
  $p2=$p1.Clone();$p1.Keys|%{$key=$_;switch ($p1.$_) {'SwitchParameter' {$p2.$key='Bool';break}'ActionPreference' {$p2.Remove($key);break}}}
  # Add the parameterset as a property (Tells you which parameters are present), and create a new object using the hash of argument names and values
  # This version of the last line creates strongly-typed Properties, but won't handle Parameters/Properties of type arrary or hashtable
  # new-object psObject -Property (&(iex $('{@{ParameterSet=[string]'+ "'$($PsCmdlet.ParameterSetName)';" +[string]::join(";",($p2.Keys|%{("{0}="-f $_+'['+$p2.$_+']'+'"$'+$_ +'"')}))+'}}')))
  # This version of the last line creates untyped Properties, and does handle hashtables and arrays. see also this blog https://jamesone111.wordpress.com/2011/01/11/why-it-is-better-not-to-use-powershell-parameter-validation/
  new-object psObject -Property (&(iex $('{@{ParameterSet=[string]'+"'$($PsCmdlet.ParameterSetName)';"+[string]::join(";",($p2.Keys|%{("{0}="-f $_+"`$$_")}))+'}}')))
}

$newobj1 = New-CustomDemoObject # If none of the specific ParamterSet arguments are supplied, the default ParamterSetName 'All' causes all parameters to be created on the PsObject as empty Properties
$newobj2 = New-CustomDemoObject -Workbook 'another.xlsx' -PathToDir 'D:\Logs'
$newobj3 = New-CustomDemoObject -isVisible -PathToZip 'D:\Logs\Backups'
#$newobj4 = New-CustomDemoObject -Workbook 'another.xlsx' -PathToZip 'D:\Logs\Backups' -PathToDir 'D:\Logs' # Uncomment this - you will see an error about paramterset cannot be resolved

# Type $newobj1 ( and $newobj2, and  $newobj3) at the comamnd prompt to see the objects that got created

Thursday, June 9, 2011

Search Server–Main thread proc crashed on cmd …

I was recently asked what these errors mean (you may see them in the PortalStatus report, or the application event logs):

(16:25 Search Server) - TeaLeaf Search Server Ver: 7.2.12.7296 - Main thread proc crashed on cmd /serverstatus. See TLSrchSrv log. 
(16:19 Search Server) - TeaLeaf Search Server Ver: 7.2.12.7296 - Main thread proc crashed on cmd /wordlist. See TLSrchSrv log.
(16:18 Search Server) - TeaLeaf Search Server Ver: 7.2.12.7296 - Main thread proc crashed on cmd /wordlist. See TLSrchSrv log.

That is not an unusual error. Historically, we started seeing the Search Server “crash” about version 4.x. The developers ended up buttressing the SS service with a number of error recover routines. You are seeing one of them here. The SS is the gateway between the Tealeaf software and the CTREE database that stores the persistent session data service. It is also the heart of the communications between the different  servers and routinely sends very large messages around the network. Depending on what kinds of queries are running, the tealeaf user load, etc, the SS may “crash”, but the error recovery routines are really good about getting it right back up.

The SS logs can be opened, and you can look for the crash in the log. It will reference back to the command that caused the crash, and the user/IP address that issued the command. You can take that step if you want more information.  However, I usually ignore these errors unless they repeat multiple times a day spread across many hours. If the errors are systemic, then you should contact Tealeaf support to get to the root cause.

Saturday, May 7, 2011

CUI Events and JavaScript Exceptions

What happens when a JavaScript exception occurs on a page hosting the Tealeaf Client UI events?

You may have seen a HTTP header called X-TeaLeaf-Page-Cui-Exceptions in the request when the CUI “phones home” with the request to the Tealeaf tlurl (/TealeafTarget.aspx by default), and wondered what this does. Here’s the answer- This HTTP Header (visible in the Request block) will report if an unhandled exception has occurred in any JavaScript executing on that page in the browser. Here are the details.

  1. At the time the Tealeaf CUI setup functions execute on the page, the function TeaLeaf.Event.EventSetup is called. This checks to see if a JavaScript function has been attached to the window.onerr property.
    • If a function is attached to this property, then the CUI considers that the application developers already have an onerror handler, so CUI won’t do anything with it. In this case X-TeaLeaf-Page-Cui-Exceptions will always be zero. (not very interesting!)
    • If there is no function attached to this property, then the CUI will attach it’s own onerror event handler, TeaLeaf.Event.tlErrorHandler
  2. This window.onerr is the exception handler of last resort. Every time a JavaScript exception occurs (one that is not caught by an exception handler), then window.onerr is called. Once the TeaLeaf.Event.tlErrorHandler is attached to window.onerr, then the TeaLeaf.Event.tlErrorHandler function is called every time a unhandled JavaScript exception occurs.
  3. TeaLeaf.Event.tlErrorHandler function increments an internal exception counter and it creates an entry in the CUI events collection. This entry has the type “INFO” and the subtype “EXCEPTION”, and it has the text of the exception message, the URL of the page on which the exception occurred, and the line number on that page where it occurred (pretty cool!).
  4. The tlErrorHandler function then forces the CUI to phone home right away, and passes any existing CUI events that have been queued, along with this exception message.

So, a JavaScript exception forces a call to /TealeafTarget.aspx (or whatever your application has configured for the URL where the CUI phones home), and reports the page URL, the JavaScript exception message, and where it occurred on the page, as well as the running total of JavaScript exceptions that have occurred on the page so far. Note that the exception count is not reset to zero when the CUI phones home, so this field will increment if there are multiple JavaScript errors on the page (and each JavaScript error will cause its very own call to /TealeafTarget.aspx).

Replay rule that stops RTV from trying to contact Omniture servers (or other third-party servers)

 

Here’s a tip – if a page is taking a long time to load, it may be trying to contact a 3rd-party tracking site like Omniture. Look at the “view page load detail”s when the replay view of a page seems to hang. You may see calls to 2o7.net, or other domains that are not part of your domain. These calls are RTV replaying embedded web-bugs – typically 1-pixel transparent gifs that are used to track visitors. Very often, I’ve seen RTV sit and wait a long time for a response – but because RTV is running on your desktop behind your corporate internet access rules, the company network disallows calls to those domains. So RTV just sits there waiting for it’s time-out to expire.

You can avoid this long wait, simply right-click the offending URL in the “view page load details”, and select “Don’t allow RTV to visit this URL…”  When the dialog box pops up, you should use wild cards to replace everything to the right of the domain. [example]. You can also replace part of the host name with a wild card too.

This is also the same method you use if you want to be sure that replaying a session does not affect your Omniture statistics. By telling RTV not to visit that URL, your RTV replays don’t get sent to the 3rd party site – hence they don’t impact the statistics reported.

Friday, April 1, 2011

Persisting Application State in Tealeaf Session Attributes

One big problem with events in the Tealeaf model has been that an event on page N of a user session cannot know about user choices made on previous pages in the session. This post will explore a way around this problem, using Tealeaf session attributes to store state information about the user’s interaction with web application.

Lets start with a hypothetical application, explore the problem, and walk through a solution. The application allows three different ways for a user to search for a group of products: Quick, Guided, and Advanced. At the end of the three search processes, the last page of each process all use the same URL and provide the same response page. Nothing in the final page URL, formfields, or response identifies if the user came down the Quick, Guided, or Advanced search path. So how should we create the “last page” event for the three search processes? Making just one “last page” event will not work well for calculating independent conversion rates for the three processes. We need separate events for AT:S:Q:CompleteV:CP,AT:S:GU:CompleteV:CP,AT:S:A:CompleteV:CP [1] For these, we need some way of knowing what choice the user made when they selected the type of search to perform.

Session Attributes store data. If we can store a unique pattern into a session attribute for each search path, then we can test the attribute for each pattern and trigger an event when the attribute contains a recognized pattern.

Caveats first: This solution uses events with the category “Session Attribute Event – Per Page”. Events built from this category are evaluated on every single hit. On sites with large traffic, having a lot of these events can put a strain on the CPU. If the Event Processor process cannot keep up with the demand put on it, the Canisters will start to spool during the busy hours of the day, and the message in the application event logs will be similar to “Number of unprocessed events is greater than 20,000…” (where 20,000 is a limit set in the DecoupleEX session agent of the TealeafCaptureSocket.cfg file).

If it is possible to get the development group to add tags to the response, so that there is a specific pattern in the Response page that identifies both the page and the process, then this Session Attribute events solution will not be necessary - simply create events that recognize the tags found in the Response. Unfortunately, it is sometimes not possible to get tags added, due to time or staff resource constraints, in which case, we need an “all-Tealeaf” solution.

1) Create a Session Attribute, call it AT:Current Search Method:Rsp

2) Identify a unique pattern in each of the three Requests that start each of the search processes. If the application implements the Client UI events, then the ID or Name of the control clicked are likely candidates. Another possibility may be the URL-path, or a URLField, may be unique and sufficient to identify each path.

3) Create three events that trigger when a user REQUESTS the first page of each process. In each event, place the unique pattern identifying each process in the AT:Current Search Method:Rsp attribute. You may use the pattern defined in the event, or you may want to use the Start Tag and End Tag. If the Start/End tag method is used, the string between the two tags will be moved to the session attribute. If you don’t use the Start/End tag method, the Pattern data will be moved to the session attribute. Use the Pattern if the pattern that triggers each event is unique. Start/End tags let you use one pattern that recognizes the kind of search started, but use different part of the same buffer to extract a shorter, uniquely identifying string for the session attribute

4) Create the event AT:S:G:CompleteV:B that recognizes the View of the final search request/result . Make this a building block event – it will fire regardless of the process taken.

5) Create three events AT:S:Q:CompleteV:SAEP:B, ,AT:S:GU:CompleteV:SAEP:B,AT:S:A:CompleteV:SAEP:B   using the “Session Attribute Per Page” event type, specifying the AT:Current Search Method:Rsp attribute. Each of the three events specify one of the three unique patterns. Therefore, only one (or none) of the three events will fire on every hit.

6) Create the three final events AT:S:Q:CompleteV:CP,AT:S:GU:CompleteV:CP,AT:S:A:CompleteV:CP. These are compound events that combine the search global event AT:S:G:CompleteV:B with each of the three events in AT:S:Q:CompleteV:SAEP:B, ,AT:S:GU:CompleteV:SAEP:B,AT:S:A:CompleteV:SAEP:B  

That’s it! You have the three events that clearly identify that the search results were delivered to the client, and which kind of Search process generated the final result view. Now that the “Current Search Process” events exist for each kind of search, you can use them anyplace in the search processes where there is ambiguity in the URL, URLField, or Response, to clearly identify what kind of search process is being used.

Related Posts:

A Naming Convention for Events

Bookmark and Share

Monday, March 21, 2011

Why do I see multiple similar sessions when displaying search results in RTV

This is caused by session fragmentation, combined with Automerge. Session fragmentation occurs in the persistent data storage (the Long Term Canister LTC) because users walk away from their browser for long periods. They may return over an hour later, but have not closed their browser window. After a period of inactivity, Tealeaf is configured to move the user's hits from RAM (Short Term Canister (STC)) over to the LTC. These hits are given a FragmentID (AKA Session ID) – an integer with values that range from “1” up to around 2 billion. Every hit in this fragment also has a copy of the TLTSID and all hits in this fragment have the same TLTSID value. When the user returns after an hour, and starts sending hits again, Tealeaf holds these hits in STC (RAM) with a different FragmentID, but every hit still has the same TLTSID value as the hits of the previous fragment. This may happen four or five times over the course of a work day. As long as the browser remains open, all hits have the same TLTSID value, but different groups of hits will have a different FragmentID. Now when you search the LTC for a session, the system returns to RTV the FragmentID where the search terms match. RTV then asks the Tealeaf system for all of the hits in that fragment, AND if Automerge is ON, RTV asks the Tealeaf system for all of the hits of all the fragments within an 6-hour window on either side of the fragment having the match. In the RTV search results panes, each line is a session that the search found. The left column is the Session Identifier. If Automerge succeeded, the value shown in this column for the session is the 32 character TLTSID, then a dash, then the FragmentID. If Automerge didn't find any other fragments, the value of this column for the session shows only the FragmentID integer. But why does RTV sometimes show two or more identical rows having nearly identical Session Identifier values? If the search matched in two or more fragments of the same session, then Automerge runs for each fragment that matches. If the session lasts long enough, some fragments are outside of the six-hour window on either side of the fragment that matches. So Automerge is going to reassemble different sets of fragments depending on which fragment the search term was found in. You will see a row for each session fragment that matched, and the Session Identifier column for each row will have the same 32 character TLTSID, but different FragmentIDs! The other columns of each row shows how many hits were assembled on each side of the matching fragment, the timestamp of the first fragment, and the duration of the merged fragments. And this is why you may see multiple rows which look a whole lot alike, and when you replay these, you are seeing the same session -just different portions of it, with a lot of overlap between each merged set of fragments.

Another thing you should be aware of regarding FragmentIDs, AKA Session IDs: These integers are assigned on a per-Canister basis, start at “1” with a brand new canister, and simply increment. If your system has multiple Canister servers, these integers will increment at different rates, depending on how the traffic is sent to each Canister. If a new Canister is added, the FragmentID for that new Canister starts at ‘”1”, regardless of the values in any other Canister. Tealeaf Portal and RTV both let you search for a “Session ID”,, which is searching the value of the FragmentID. But I’d suggest you avoid using this search term – you can end up finding totally disparate sessions if the same integer appears in different Canisters for completely unrelated session fragments from different users.

Thursday, October 7, 2010

How can I figure out what is causing a high number of One-Hit Sessions consisting of Non-Pages (*.gif, *.jpg)

The following question came up today: “The activity chart for live user sessions is showing  a large number of one-hit sessions and a very large number of “Non-pages”. How can I figure out what is going on”?

image

You are assuming that just because the portal’s report says “non-Pages (.gifs and .jpgs)”, that the 1-hit sessions really are gifs and jpgs. But in fact, this activity statistics report is misleading – and I can never get a good explanation of “non-pages”. It has something to do with the Content-Type (one of the HTTP Response headers), and the file suffix, and some arcane algorithm the PCA has for deciding if a hit is a page or a “non-page”. This chart is telling you there are 20,000 hits that are considered “non-pages”. You will have to use RTV to get a better understanding. Look first in the Portal for “Sessions per hour” activity chart. Find the hours (probably small of the night), where the session count is smallest, and the curve is “flattest”. Use RTV to Search for sessions having a hit count = 1, for a 30 minute period right in the middle of the quietest time. Most of the sessions returned should be “whatever” is generating these 1-hit non-page sessions.

Once you have the list of 100 such sessions downloaded to RTV, customize the results pane (lower pane), to include the following columns: REMOTE_ADDR; HTTP_USER_AGENT; env\CONTENT_TYPE and env\StatusCode. That set of additional columns, along with the URL already present, should give you a good start towards finding out who/what is hitting the site all day long and generating 1-hit non-page sessions.

One of the most common sources of “non-page” hits is the F5 Load Balancer from BigIP. It uses port 80 by default for “keep-alive” connections to the web servers that it is balancing. It seems that by default, it sends lots (maybe once per second?) per web server. If you suspect this, the Request block, in RTV, will  be absolutely minimal; the F5 is just doing a connect/waiting for the Ack, and then doing a disconnect. ReqCancelled=Client, StatusCode=0, and the REOTE_ADDR will be the IP address of the F5 Load Balancer (probably two similar but different REMOTE_ADDR, because these load balancers are normally deployed in pairs – active/standby).