Combining changes from various places
[dasher.git] / Src / DasherCore / UserLog.cpp
blobf7ff2b6f0d730fca85d344725e07dccb96b80f50
2 #include "../Common/Common.h"
4 #include "UserLog.h"
5 #include <fstream>
6 #include <sys/time.h>
8 // Track memory leaks on Windows to the line that new'd the memory
9 #ifdef _WIN32
10 #ifdef _DEBUG
11 #define DEBUG_NEW new( _NORMAL_BLOCK, THIS_FILE, __LINE__ )
12 #define new DEBUG_NEW
13 #undef THIS_FILE
14 static char THIS_FILE[] = __FILE__;
15 #endif
16 #endif
18 static UserLogParamMask s_UserLogParamMaskTable [] = {
19 {SP_ALPHABET_ID, userLogParamOutputToSimple},
20 {SP_COLOUR_ID, userLogParamOutputToSimple},
21 {LP_MAX_BITRATE, userLogParamOutputToSimple |
22 userLogParamTrackMultiple |
23 userLogParamTrackInTrial |
24 userLogParamForceInTrial |
25 userLogParamShortInCycle},
26 {BP_CONTROL_MODE, userLogParamOutputToSimple},
27 {LP_UNIFORM, userLogParamOutputToSimple},
28 {LP_YSCALE, userLogParamOutputToSimple},
29 {BP_NUMBER_DIMENSIONS, userLogParamOutputToSimple},
30 {BP_EYETRACKER_MODE, userLogParamOutputToSimple},
31 {LP_LANGUAGE_MODEL_ID, userLogParamOutputToSimple},
32 {LP_LM_MAX_ORDER, userLogParamOutputToSimple},
33 {LP_LM_EXCLUSION, userLogParamOutputToSimple},
34 {LP_LM_UPDATE_EXCLUSION, userLogParamOutputToSimple},
35 {LP_LM_ALPHA, userLogParamOutputToSimple},
36 {LP_LM_BETA, userLogParamOutputToSimple},
37 {LP_LM_MIXTURE, userLogParamOutputToSimple},
38 {LP_LM_WORD_ALPHA, userLogParamOutputToSimple},
39 {-1, -1} // Flag value that should always be at the end
42 CUserLog::CUserLog(Dasher::CEventHandler *pEventHandler,
43 CSettingsStore *pSettingsStore,
44 int iLogTypeMask,
45 Dasher::CAlphabet* pAlphabet) : CDasherComponent(pEventHandler, pSettingsStore)
47 //CFunctionLogger f1("CUserLog::CUserLog", g_pLogger);
49 InitMemberVars();
51 m_pAlphabet = pAlphabet;
52 m_iLevelMask = iLogTypeMask;
54 InitUsingMask(iLogTypeMask);
56 if ((m_bSimple) && (m_pSimpleLogger != NULL))
57 m_pSimpleLogger->Log("start, %s", logDEBUG, GetVersionInfo().c_str());
59 SetOuputFilename();
60 m_pApplicationSpan = new CTimeSpan("Application", true);
62 if (m_pApplicationSpan == NULL)
63 g_pLogger->Log("CUserLog::CUserLog, failed to create m_pApplicationSpan!", logNORMAL);
65 // From the load test harness, we create object directly without a settings store
66 // and event handler. In this case, we don't want to try and push the intial
67 // parameters.
68 if ((pEventHandler != NULL) && (pSettingsStore != NULL))
69 AddInitialParam();
72 CUserLog::~CUserLog()
74 //CFunctionLogger f1("CUserLog::~CUserLog", g_pLogger);
76 if ((m_bSimple) && (m_pSimpleLogger != NULL))
77 m_pSimpleLogger->Log("stop", logDEBUG);
79 if (m_pApplicationSpan != NULL)
81 delete m_pApplicationSpan;
82 m_pApplicationSpan = NULL;
85 for (unsigned int i = 0; i < m_vpTrials.size(); i++)
87 CUserLogTrial* pTrial = (CUserLogTrial*) m_vpTrials[i];
89 if (pTrial != NULL)
91 delete pTrial;
92 pTrial = NULL;
96 for (unsigned int i = 0; i < m_vParams.size(); i++)
98 CUserLogParam* pParam = (CUserLogParam*) m_vParams[i];
100 if (pParam != NULL)
102 delete pParam;
103 pParam = NULL;
107 if (m_pSimpleLogger != NULL)
109 delete m_pSimpleLogger;
110 m_pSimpleLogger = NULL;
113 if (m_pCycleTimer != NULL)
115 delete m_pCycleTimer;
116 m_pCycleTimer = NULL;
120 // Do initialization of member variables based on the user log level mask
121 void CUserLog::InitUsingMask(int iLogLevelMask)
123 //CFunctionLogger f1("CUserLog::InitUsingMask", g_pLogger);
125 m_bInitIsDone = false;
127 if (iLogLevelMask & userLogSimple)
129 // First we check to see if the file exists, if it does not
130 // then we want to force all parameter values to be sent to
131 // the log file even before InitIsDone() is called.
132 FILE* fp = fopen(USER_LOG_SIMPLE_FILENAME.c_str(), "r");
133 if (fp == NULL)
135 m_bInitIsDone = true;
137 else
139 fclose(fp);
140 fp = NULL;
143 m_bSimple = true;
145 if (m_pSimpleLogger == NULL)
146 m_pSimpleLogger = new CFileLogger(USER_LOG_SIMPLE_FILENAME, logDEBUG, logTimeStamp | logDateStamp);
149 if (iLogLevelMask & userLogDetailed)
150 m_bDetailed = true;
154 // Called when we want to output the log file (usually on exit of dasher)
155 void CUserLog::OutputFile()
157 //CFunctionLogger f1("CUserLog::OutputFile", g_pLogger);
159 if (m_bDetailed)
161 // Let the last pTrial object know we are done with it, this lets it do
162 // any final calculations.
163 if (m_vpTrials.size() > 0)
165 CUserLogTrial* pTrial = m_vpTrials[m_vpTrials.size() - 1];
167 if (pTrial != NULL)
168 pTrial->Done();
171 // Output our data to an XML file before we destruct
172 WriteXML();
176 void CUserLog::StartWriting()
178 //CFunctionLogger f1("CUserLog::StartWriting", g_pLogger);
180 if (m_bSimple)
182 // The canvas size changes multiple times as a user resizes it. We just want to write
183 // one short log entry for the final position the next time they start writing.
184 if ((m_bNeedToWriteCanvas) && (m_pSimpleLogger != NULL))
186 m_pSimpleLogger->Log("canvas:\t%d\t%d\t%d\t%d",
187 logDEBUG,
188 m_sCanvasCoordinates.top,
189 m_sCanvasCoordinates.left,
190 m_sCanvasCoordinates.bottom,
191 m_sCanvasCoordinates.right);
192 m_bNeedToWriteCanvas = false;
195 // We log what happened between StartWriting() and StopWriting()
196 // so clear out any previous history.
197 ResetCycle();
200 if (m_bDetailed)
202 CUserLogTrial* pTrial = GetCurrentTrial();
204 // This could be the first use in this pTrial, create a new one if needed
205 if (pTrial == NULL)
206 pTrial = AddTrial();
208 if (pTrial != NULL)
209 pTrial->StartWriting();
210 else
211 g_pLogger->Log("CUserLog::StartWriting, failed to create new pTrial!", logNORMAL);
214 m_bIsWriting = true;
217 // This version should be called at the end of navigation with the dNats
218 // value under the current mouse position. This would be more accurate
219 // then the last value from a mouse event since some time may have
220 // elapsed.
221 void CUserLog::StopWriting(float dNats)
223 //CFunctionLogger f1("CUserLog::StopWriting", g_pLogger);
225 if (m_bIsWriting)
227 m_dCycleNats = (double) dNats;
228 StopWriting();
232 void CUserLog::StopWriting()
234 //CFunctionLogger f1("CUserLog::StopWriting", g_pLogger);
236 if (m_bIsWriting)
238 m_bIsWriting = false;
240 // In simple logging mode, we'll output the stats for this navigation cycle
241 if ((m_bSimple) && (m_pSimpleLogger != NULL))
242 m_pSimpleLogger->Log("%s", logDEBUG, GetStartStopCycleStats().c_str());
244 if (m_bDetailed)
246 CUserLogTrial* pTrial = GetCurrentTrial();
248 if (pTrial == NULL)
250 g_pLogger->Log("CUserLog::StopWriting, pTrial was NULL!", logNORMAL);
251 return;
254 pTrial->StopWriting();
259 void CUserLog::AddSymbols(Dasher::VECTOR_SYMBOL_PROB* vpNewSymbols, eUserLogEventType iEvent)
261 //CFunctionLogger f1("CUserLog::AddSymbols", g_pLogger);
263 if (!m_bIsWriting)
265 // StartWriting() wasn't called, so we'll do it implicitly now
266 g_pLogger->Log("CUserLog::AddSymbols, StartWriting() not called?", logDEBUG);
267 StartWriting();
270 if (vpNewSymbols == NULL)
272 g_pLogger->Log("CUserLog::AddSymbols, vpNewSymbols was NULL!", logNORMAL);
273 return;
276 if (m_bSimple)
278 // Also store a copy in a vector that gets cleared
279 // time StartWriting() is called.
281 for (unsigned int i = 0; i < vpNewSymbols->size(); i++)
283 Dasher::SymbolProb sNewSymbolProb = (Dasher::SymbolProb) (*vpNewSymbols)[i];
284 m_vCycleHistory.push_back(sNewSymbolProb);
288 if (m_bDetailed)
290 CUserLogTrial* pTrial = GetCurrentTrial();
292 // We should have a pTrial object since StartWriting() should have been called before us
293 if (pTrial == NULL)
295 g_pLogger->Log("CUserLog::AddSymbols, pTrial was NULL!", logNORMAL);
296 return;
299 if (m_pAlphabet == NULL)
301 g_pLogger->Log("CUserLog::AddSymbols, can't do detailed logging of symbols without an alphabet!", logNORMAL);
302 return;
305 pTrial->AddSymbols(vpNewSymbols, iEvent, m_pAlphabet);
309 void CUserLog::DeleteSymbols(int iNumToDelete, eUserLogEventType iEvent)
311 //CFunctionLogger f1("CUserLog::DeleteSymbols", g_pLogger);
313 if (iNumToDelete <= 0)
314 return;
316 if (!m_bIsWriting)
318 // StartWriting() wasn't called, so we'll do it implicitly now
319 g_pLogger->Log("CUserLog::DeleteSymbols, StartWriting() not called?", logDEBUG);
320 StartWriting();
323 if (m_bSimple)
325 m_iCycleNumDeletes += iNumToDelete;
327 // Be careful not to pop more things than we have (this will hork the
328 // memory up on linux but not windows).
329 int iActualNumToDelete = min((int) m_vCycleHistory.size(), iNumToDelete);
330 for (int i = 0; i < iActualNumToDelete; i++)
331 m_vCycleHistory.pop_back();
334 if (m_bDetailed)
336 CUserLogTrial* pTrial = GetCurrentTrial();
338 // We should have a pTrial object since StartWriting() should have been called before us
339 if (pTrial == NULL)
341 g_pLogger->Log("CUserLog::DeleteSymbols, pTrial was NULL!", logNORMAL);
342 return;
345 pTrial->DeleteSymbols(iNumToDelete, iEvent);
349 void CUserLog::NewTrial()
351 //CFunctionLogger f1("CUserLog::NewTrial", g_pLogger);
353 if (m_bIsWriting)
355 // We should have called StopWriting(), but we'll do it here implicitly
356 g_pLogger->Log("CUserLog::NewTrial, StopWriting() not called?", logDEBUG);
357 StopWriting();
360 // For safety we can dump the XML to file after each pTrial is done. This
361 // might be a good idea for long user pTrial sessions just in case Dasher
362 // were to do something completely crazy like crash.
363 if (USER_LOG_DUMP_AFTER_TRIAL)
364 WriteXML();
366 if (m_bDetailed)
368 CUserLogTrial* pTrial = GetCurrentTrial();
370 if (pTrial == NULL)
372 // Not an error, they may just hit new doc before anything else at start
373 return;
376 if (pTrial->HasWritingOccured())
378 // Create a new pTrial if the existing one has already been used
379 pTrial = AddTrial();
384 // Overloaded version that converts a double to a string
385 void CUserLog::AddParam(const string& strName, double dValue, int iOptionMask)
387 sprintf(m_szTempBuffer, "%0.4f", dValue);
388 AddParam(strName, m_szTempBuffer, iOptionMask);
391 // Overloaded version that converts a int to a string
392 void CUserLog::AddParam(const string& strName, int iValue, int iOptionMask)
394 sprintf(m_szTempBuffer, "%d", iValue);
395 AddParam(strName, m_szTempBuffer, iOptionMask);
398 // Adds a general parameter to our XML. This lets various Dasher components
399 // record what they are set at. Optionally can be set to track multiple
400 // values for the same parameter or to always output a line to the simple
401 // log file when the parameter is set.
402 void CUserLog::AddParam(const string& strName, const string& strValue, int iOptionMask)
404 //CFunctionLogger f1("CUserLog::AddParam", g_pLogger);
406 bool bOutputToSimple = false;
407 bool bTrackMultiple = false;
408 bool bTrackInTrial = false;
409 bool bForceInTrial = false;
410 bool bShortInCycle = false;
412 if (iOptionMask & userLogParamOutputToSimple)
413 bOutputToSimple = true;
414 if (iOptionMask & userLogParamTrackMultiple)
415 bTrackMultiple = true;
416 if (iOptionMask & userLogParamTrackInTrial)
417 bTrackInTrial = true;
418 if (iOptionMask & userLogParamForceInTrial)
419 bForceInTrial = true;
420 if (iOptionMask & userLogParamShortInCycle)
421 bShortInCycle = true;
423 // See if we want to immediately output this name/value pair to
424 // the running simple log file. If we are tracking the parameter
425 // in the short cycle stats line, then don't output here.
426 if ((bOutputToSimple) &&
427 (m_bSimple) &&
428 (m_pSimpleLogger != NULL) &&
429 (m_bInitIsDone) &&
430 (!bShortInCycle))
432 m_pSimpleLogger->Log("%s = %s", logDEBUG, strName.c_str(), strValue.c_str());
435 // See if this matches an existing parameter value that we may want to
436 // overwrite. But only if we aren't suppose to keep track of multiple changes.
437 if (!bTrackMultiple)
439 for (unsigned int i = 0; i < m_vParams.size(); i++)
441 CUserLogParam* pParam = (CUserLogParam*) m_vParams[i];
443 if (pParam != NULL)
445 if (pParam->strName.compare(strName) == 0)
447 pParam->strValue = strValue;
448 return;
454 // We need to add a new param
455 CUserLogParam* pNewParam = new CUserLogParam;
457 if (pNewParam == NULL)
459 g_pLogger->Log("CUserLog::AddParam, failed to create CUserLogParam object!", logNORMAL);
460 return;
463 pNewParam->strName = strName;
464 pNewParam->strValue = strValue;
465 pNewParam->strTimeStamp = "";
466 pNewParam->options = iOptionMask;
468 // Parameters that can have multiple values logged will also log when they were changed
469 if (bTrackMultiple)
470 pNewParam->strTimeStamp = CTimeSpan::GetTimeStamp();
472 m_vParams.push_back(pNewParam);
474 if ((bTrackInTrial) && (m_bDetailed))
476 // See if we need to pass the parameter onto the current pTrial object
477 CUserLogTrial* pTrial = GetCurrentTrial();
479 if (pTrial != NULL)
480 pTrial->AddParam(strName, strValue, iOptionMask);
485 // Adds a new point in our tracking of mouse locations
486 void CUserLog::AddMouseLocation(int iX, int iY, float dNats)
488 //CFunctionLogger f1("CUserLog::AddMouseLocation", g_pLogger);
490 // Check to see if it is time to actually push a mouse location update
491 if (UpdateMouseLocation())
493 if (m_bDetailed)
495 CUserLogTrial* pTrial = GetCurrentTrial();
497 if (pTrial == NULL)
499 // Only track during an actual pTrial
500 return;
503 // Only record mouse locations during navigation
504 if (pTrial->IsWriting())
505 pTrial->AddMouseLocation(iX, iY, dNats);
508 // Keep track of the dNats for the current mouse position
509 if (m_bIsWriting)
510 m_dCycleNats = dNats;
514 // Adds the size of the current window
515 void CUserLog::AddWindowSize(int iTop, int iLeft, int iBottom, int iRight)
517 //CFunctionLogger f1("CUserLog::AddWindowSize", g_pLogger);
519 m_sWindowCoordinates.top = iTop;
520 m_sWindowCoordinates.left = iLeft;
521 m_sWindowCoordinates.bottom = iBottom;
522 m_sWindowCoordinates.right = iRight;
524 if (m_bDetailed)
526 CUserLogTrial* pTrial = GetCurrentTrial();
528 if (pTrial == NULL)
530 // Only track during an actual pTrial
531 return;
534 pTrial->AddWindowSize(iTop, iLeft, iBottom, iRight);
538 // Adds the size of the current canvas, this should be called when our
539 // window is initially created and whenever somebody mucks with the
540 // size.
541 void CUserLog::AddCanvasSize(int iTop, int iLeft, int iBottom, int iRight)
543 //CFunctionLogger f1("CUserLog::AddCanvasSize", g_pLogger);
545 // Only log to simple log object if the coordinates are different from
546 // what we had prior to now.
547 if ((m_bSimple) &&
548 (m_pSimpleLogger != NULL) &&
549 ((m_sCanvasCoordinates.top != iTop) ||
550 (m_sCanvasCoordinates.left != iLeft) ||
551 (m_sCanvasCoordinates.bottom != iBottom) ||
552 (m_sCanvasCoordinates.right != iRight)))
553 m_bNeedToWriteCanvas = true;
555 m_sCanvasCoordinates.top = iTop;
556 m_sCanvasCoordinates.left = iLeft;
557 m_sCanvasCoordinates.bottom = iBottom;
558 m_sCanvasCoordinates.right = iRight;
560 if (m_bDetailed)
562 CUserLogTrial* pTrial = GetCurrentTrial();
564 if (pTrial == NULL)
566 // Only track during an actual pTrial
567 return;
570 pTrial->AddCanvasSize(iTop, iLeft, iBottom, iRight);
574 // We may want to use a noramlized version of mouse coordinates, this way it is
575 // invariant to changes in the window size before, during, or after navigation.
576 // The caller must send us both the x, y coordinates and the current window size.
577 void CUserLog::AddMouseLocationNormalized(int iX, int iY, bool bStoreIntegerRep, float dNats)
579 //CFunctionLogger f1("CUserLog::AddMouseLocationNormalized", g_pLogger);
581 // Check to see if it is time to actually push a mouse location update
582 if (UpdateMouseLocation())
584 if ((m_sCanvasCoordinates.bottom == 0) &&
585 (m_sCanvasCoordinates.left == 0) &&
586 (m_sCanvasCoordinates.right == 0) &&
587 (m_sCanvasCoordinates.top == 0))
589 g_pLogger->Log("CUserLog::AddMouseLocationNormalized, called before AddCanvasSize()?", logNORMAL);
590 return;
593 ComputeSimpleMousePos(iX, iY);
595 if (m_bDetailed)
597 CUserLogTrial* pTrial = GetCurrentTrial();
599 if (pTrial == NULL)
601 // Only track during an actual pTrial
602 return;
605 // Only record mouse locations during navigation
606 if (pTrial->IsWriting())
607 pTrial->AddMouseLocationNormalized(iX, iY, bStoreIntegerRep, dNats);
610 // Keep track of the dNats for the current mouse position
611 if (m_bIsWriting)
612 m_dCycleNats = dNats;
616 // For simple logging, we don't want to log the same parameters settings
617 // in the file every single time Dasher starts up. So we require that
618 // this method be called once the initial loading of parameters is
619 // complete. This way only changes during a session are logged (we can
620 // also force logging of the parameter setting when the log file is
621 // created by setting m_bInitIsDone to true in the constructor).
622 void CUserLog::InitIsDone()
624 //CFunctionLogger f1("CUserLog::InitIsDone", g_pLogger);
626 m_bInitIsDone = true;
629 // Update our pointer to the alphabet we are using, we need th
630 // alphabet in order to convert symbols into display strings
631 // that we put in the log file.
632 void CUserLog::SetAlphabetPtr(Dasher::CAlphabet* pAlphabet)
634 //CFunctionLogger f1("CUserLog::SetAlphabetPtr", g_pLogger);
636 m_pAlphabet = pAlphabet;
639 // Sets our output filename based on the current date and time.
640 // Or if a parameter is passed in, use that as the output name.
641 void CUserLog::SetOuputFilename(const string& strFilename)
643 //CFunctionLogger f1("CUserLog::SetOuputFilename", g_pLogger);
645 if (strFilename.length() > 0)
647 m_strFilename = strFilename;
649 else
651 m_strFilename = USER_LOG_DETAILED_PREFIX;
653 struct timeval sTimeBuffer;
654 struct timezone sTimezoneBuffer;
655 char* szTimeLine = NULL;
657 gettimeofday(&sTimeBuffer, &sTimezoneBuffer);
659 szTimeLine = ctime(&(sTimeBuffer.tv_sec));
661 if ((szTimeLine != NULL) && (strlen(szTimeLine) > 18))
663 for (int i = 4; i < 19; i++)
665 if (szTimeLine[i] == ' ')
666 m_strFilename += "_";
667 else if (szTimeLine[i] != ':')
668 m_strFilename += szTimeLine[i];
672 m_strFilename += ".xml";
675 // Make sure we store a fully qualified form, to prevent movent
676 // if the working directory changes
677 m_strFilename = CFileLogger::GetFullFilenamePath(m_strFilename);
681 // Find out what level mask this object was created with
682 int CUserLog::GetLogLevelMask()
684 //CFunctionLogger f1("CUserLog::GetLogLevelMask", g_pLogger);
686 return m_iLevelMask;
689 // This gets called whenever parameters get changed that we are tracking
690 void CUserLog::HandleEvent(Dasher::CEvent* pEvent)
692 if(pEvent->m_iEventType == 1) {
693 Dasher::CParameterNotificationEvent* pEvt(static_cast < Dasher::CParameterNotificationEvent * >(pEvent));
695 int i = 0;
697 // Go through each of the parameters in our lookup table from UserLogParam.h.
698 // If the key matches the notification event, then we want to push the
699 // parameter change to the logging object.
700 while (s_UserLogParamMaskTable[i].key != -1)
702 int iParameter = pEvt->m_iParameter;
704 if (s_UserLogParamMaskTable[i].key == iParameter)
706 int iOptionMask = s_UserLogParamMaskTable[i].mask;
708 UpdateParam(iParameter, iOptionMask);
709 return;
712 i++;
714 } // end while (s_UserLogParamMaskTable[i].key != -1)
716 } // end if (pEvent->m_iEventType == 1)
720 ////////////////////////////////////////// private methods ////////////////////////////////////////////////
722 // Just inits all our member variables, called by the constructors
723 void CUserLog::InitMemberVars()
725 //CFunctionLogger f1("CUserLog::InitMemberVars", g_pLogger);
727 m_strFilename = "";
728 m_pApplicationSpan = NULL;
729 m_dLastMouseUpdate = 0.0;
730 m_bSimple = false;
731 m_bDetailed = false;
732 m_pSimpleLogger = NULL;
733 m_pAlphabet = NULL;
734 m_bIsWriting = false;
735 m_bInitIsDone = false;
736 m_bNeedToWriteCanvas = false;
738 m_pCycleTimer = NULL;
739 m_iCycleNumDeletes = 0;
740 m_iCycleMouseCount = 0;
741 m_dCycleMouseNormXSum = 0.0;
742 m_dCycleMouseNormYSum = 0.0;
743 m_dCycleNats = 0.0;
745 m_sCanvasCoordinates.bottom = 0;
746 m_sCanvasCoordinates.top = 0;
747 m_sCanvasCoordinates.right = 0;
748 m_sCanvasCoordinates.left = 0;
750 m_sWindowCoordinates.bottom = 0;
751 m_sWindowCoordinates.top = 0;
752 m_sWindowCoordinates.right = 0;
753 m_sWindowCoordinates.left = 0;
755 // We want to use a fully qualified path so that we always
756 // look in the same spot, regardless of if the working
757 // directory has moved during runtime.
758 m_strCurrentTrialFilename = CFileLogger::GetFullFilenamePath(USER_LOG_CURRENT_TRIAL_FILENAME);
762 // Write this objects XML out
763 bool CUserLog::WriteXML()
765 //CFunctionLogger f1("CUserLog::WriteXML", g_pLogger);
769 fstream fout(m_strFilename.c_str(), ios::trunc | ios::out);
770 fout << GetXML();
771 fout.close();
773 return true;
775 } catch (...)
777 g_pLogger->Log("CUserLog::WriteXML, failed to write file %s", logNORMAL, m_strFilename.c_str());
778 return false;
782 // Serializes our data to XML
783 string CUserLog::GetXML()
785 //CFunctionLogger f1("CUserLog::GetXML", g_pLogger);
787 string strResult = "";
788 strResult.reserve(USER_LOG_DEFAULT_SIZE_TRIAL_XML * (m_vpTrials.size() + 1));
790 strResult += "<?xml version=\"1.0\"?>\n";
792 strResult += "<UserLog>\n";
793 if (m_pApplicationSpan != NULL)
794 strResult += m_pApplicationSpan->GetXML("\t");
796 strResult += "\t<Params>\n";
797 strResult += GetParamsXML();
798 strResult += "\t</Params>\n";
800 strResult += "\t<Trials>\n";
801 for (unsigned int i = 0; i < m_vpTrials.size(); i++)
803 CUserLogTrial* pTrial = (CUserLogTrial*) m_vpTrials[i];
805 // Only log trials that actually had some writing in it
806 if ((pTrial != NULL) && (pTrial->HasWritingOccured()))
808 strResult += pTrial->GetXML("\t\t");
811 strResult += "\t</Trials>\n";
813 strResult += "</UserLog>\n";
815 return strResult;
818 // Returns pointer to the current user pTrial, NULL if we don't have one yet
819 CUserLogTrial* CUserLog::GetCurrentTrial()
821 //CFunctionLogger f1("CUserLog::GetCurrentTrial", g_pLogger);
823 if (m_vpTrials.size() <= 0)
824 return NULL;
825 return m_vpTrials[m_vpTrials.size() - 1];
828 // Creates a new pTrial, adds to our vector and returns the pointer
829 CUserLogTrial* CUserLog::AddTrial()
831 //CFunctionLogger f1("CUserLog::AddTrial", g_pLogger);
833 // Let the last pTrial object know we are done with it
834 if (m_vpTrials.size() > 0)
836 CUserLogTrial* pTrial = m_vpTrials[m_vpTrials.size() - 1];
838 if (pTrial != NULL)
839 pTrial->Done();
842 CUserLogTrial* pTrial = new CUserLogTrial(m_strCurrentTrialFilename);
843 if (pTrial != NULL)
845 m_vpTrials.push_back(pTrial);
846 PrepareNewTrial();
848 else
849 g_pLogger->Log("CUserLog::AddTrial, failed to create CUserLogTrialSpeech!", logNORMAL);
851 return pTrial;
854 // See if the specified number of milliseconds has elapsed since the last mouse location update
855 bool CUserLog::UpdateMouseLocation()
857 //CFunctionLogger f1("CUserLog::UpdateMouseLocation", g_pLogger);
859 struct timeval sTimeBuffer;
860 struct timezone sTimezoneBuffer;
862 gettimeofday(&sTimeBuffer, &sTimezoneBuffer);
864 double dTime = (sTimeBuffer.tv_sec * 1000.0) + sTimeBuffer.tv_usec / 1000;
866 if ((dTime - m_dLastMouseUpdate) > LOG_MOUSE_EVERY_MS)
868 m_dLastMouseUpdate = dTime;
869 return true;
871 return false;
874 // Calculate how many bits entered in the last Start/Stop cycle
875 double CUserLog::GetCycleBits()
877 //CFunctionLogger f1("CUserLog::GetCycleBits", g_pLogger);
879 return m_dCycleNats / log(2.0);
882 // For lightweight logging, we want a string that represents the critical
883 // stats for what happened between start and stop
884 string CUserLog::GetStartStopCycleStats()
886 //CFunctionLogger f1("CUserLog::GetStartStopCycleStats", g_pLogger);
888 string strResult = "";
890 double dNormX = 0.0;
891 double dNormY = 0.0;
892 if (m_iCycleMouseCount > 0)
894 dNormX = m_dCycleMouseNormXSum / (double) m_iCycleMouseCount,
895 dNormY = m_dCycleMouseNormYSum / (double) m_iCycleMouseCount;
898 if (m_pCycleTimer == NULL)
900 g_pLogger->Log("CUserLog::GetStartStopCycleStats, cycle timer was NULL!", logNORMAL);
901 return "";
904 // Tab delimited fields are:
905 // elapsed time, symbols written, bits written, symbols deleted,
906 // avg normalized x mouse coordinate, avg normalized y mouse
907 // coordinate, (any parameters marked to be put in cycle stats)
909 // tsbdxym stands for: time symbols bits deletes x y maxbitrate
910 sprintf(m_szTempBuffer,
911 "tsbdxym:\t%0.3f\t%d\t%0.6f\t%d\t%0.3f\t%0.3f%s",
912 m_pCycleTimer->GetElapsed(),
913 m_vCycleHistory.size(),
914 GetCycleBits(),
915 m_iCycleNumDeletes,
916 dNormX,
917 dNormY,
918 GetCycleParamStats().c_str());
919 strResult = m_szTempBuffer;
921 return strResult;
924 // Helper that computes update of the simple logging's mouse
925 // position tracking member variables.
926 void CUserLog::ComputeSimpleMousePos(int iX, int iY)
928 //CFunctionLogger f1("CUserLog::ComputeSimpleMousePos", g_pLogger);
930 if ((m_bSimple) && (m_bIsWriting))
932 // We keep a running sum of normalized X, Y coordinates
933 // for use in the simple log file.
934 m_dCycleMouseNormXSum += CUserLocation::ComputeNormalizedX(iX,
935 m_sCanvasCoordinates.left,
936 m_sCanvasCoordinates.right);
938 m_dCycleMouseNormYSum += CUserLocation::ComputeNormalizedY(iY,
939 m_sCanvasCoordinates.top,
940 m_sCanvasCoordinates.bottom);
941 m_iCycleMouseCount++;
945 // Resets member variables that track a cycle for simple logging
946 void CUserLog::ResetCycle()
948 //CFunctionLogger f1("CUserLog::ResetCycle", g_pLogger);
950 m_vCycleHistory.clear();
951 m_iCycleNumDeletes = 0;
952 m_iCycleMouseCount = 0;
953 m_dCycleMouseNormXSum = 0.0;
954 m_dCycleMouseNormYSum = 0.0;
956 if (m_pCycleTimer != NULL)
958 delete m_pCycleTimer;
959 m_pCycleTimer = NULL;
962 m_pCycleTimer = new CSimpleTimer();
965 // Gets the XML that goes in the <Params> tag, but not the tags themselves.
966 string CUserLog::GetParamsXML()
968 //CFunctionLogger f1("CUserLog::GetParamsXML", g_pLogger);
970 string strResult = "";
972 // Make parameters with the same name appear near each other in the results
973 sort(m_vParams.begin(), m_vParams.end(), CUserLogParam::ComparePtr);
975 for (unsigned int i = 0; i < m_vParams.size(); i++)
977 CUserLogParam* pParam = (CUserLogParam*) m_vParams[i];
979 strResult += CUserLogTrial::GetParamXML(pParam, "\t\t");
982 return strResult;
985 // Prepares a new pTrial for use. Passes on the current canvas and window
986 // size so normalized mouse coordinates can be calculated. Also
987 // parameters can be marked to force them into the Trial object. Looks for
988 // these and push into the current Trial object.
989 void CUserLog::PrepareNewTrial()
991 //CFunctionLogger f1("CUserLog::PrepareNewTrial", g_pLogger);
993 CUserLogTrial* pTrial = GetCurrentTrial();
995 if (pTrial != NULL)
997 // We want to force the current value of any parameters that we marked
998 // with the userLogParamForceInTrial option when created. We can
999 // do this by going backwards through the parameter vector and only
1000 // pushing through the first value of a given parameter name.
1001 VECTOR_STRING vFound;
1002 if (m_vParams.size() > 0)
1004 for (VECTOR_USER_LOG_PARAM_PTR_ITER iter = m_vParams.end() - 1; iter >= m_vParams.begin(); iter--)
1006 if (((*iter) != NULL) && ((*iter)->options & userLogParamForceInTrial))
1008 // Make sure we haven't output this one already
1009 VECTOR_STRING_ITER strIter;
1010 strIter = find(vFound.begin(), vFound.end(), (*iter)->strName);
1011 if (strIter == vFound.end())
1013 pTrial->AddParam((*iter)->strName, (*iter)->strValue, (*iter)->options);
1014 vFound.push_back((*iter)->strName);
1020 // Make sure the pTrial has the current canvas and window coordinate sizes
1021 pTrial->AddCanvasSize(m_sCanvasCoordinates.top,
1022 m_sCanvasCoordinates.left,
1023 m_sCanvasCoordinates.bottom,
1024 m_sCanvasCoordinates.right);
1026 pTrial->AddWindowSize(m_sWindowCoordinates.top,
1027 m_sWindowCoordinates.left,
1028 m_sWindowCoordinates.bottom,
1029 m_sWindowCoordinates.right);
1032 else
1033 g_pLogger->Log("CUserLog::PrepareNewTrial, failed to create CUserLogTrial", logNORMAL);
1036 // Parameters can be marked to always end them at the cycle stats in short logging.
1037 // We'll look through our parameters and return a tab delimited list of their
1038 // values.
1039 string CUserLog::GetCycleParamStats()
1041 //CFunctionLogger f1("CUserLog::GetCycleParamStats", g_pLogger);
1043 string strResult = "";
1044 VECTOR_STRING vFound;
1046 if (m_vParams.size() <= 0)
1047 return strResult;
1049 // We may have more than one parameter that needs to be added and we want
1050 // the stats line to be invariant to the order in which AddParam() was
1051 // called. So we'll sort by param name and then by time stamp (for
1052 // parameters with multiple values).
1053 sort(m_vParams.begin(), m_vParams.end(), CUserLogParam::ComparePtr);
1055 // Find the last instance of any parameter marked as needed to be on
1056 // the cycle stats line.
1057 for (VECTOR_USER_LOG_PARAM_PTR_REV_ITER iter = m_vParams.rbegin(); iter != m_vParams.rend(); ++iter)
1059 if (((*iter) != NULL) && ((*iter)->options & userLogParamShortInCycle))
1061 // Make sure we haven't output this one already
1062 VECTOR_STRING_ITER strIter;
1063 strIter = find(vFound.begin(), vFound.end(), (*iter)->strName);
1064 if (strIter == vFound.end())
1066 strResult += "\t";
1067 strResult += (*iter)->strValue;
1068 vFound.push_back((*iter)->strName);
1073 return strResult;
1076 // Return a string with the operating system and product version
1077 string CUserLog::GetVersionInfo()
1079 //CFunctionLogger f1("CUserLog::GetVersionInfo", g_pLogger);
1081 string strResult = "";
1082 #ifdef _WIN32
1083 strResult += "win ";
1085 // TBD: getting version from resource is quite tricky and requires linking in
1086 // a whole library to do. Maybe we can just #DEFINE the product version?
1087 #else
1088 strResult += "not win ";
1089 #endif
1091 return strResult;
1094 // Forces all the parameters we are tracking to be intially set, used when the
1095 // object is first starting up.
1096 void CUserLog::AddInitialParam()
1098 int i = 0;
1099 while (s_UserLogParamMaskTable[i].key != -1)
1101 UpdateParam(s_UserLogParamMaskTable[i].key, s_UserLogParamMaskTable[i].mask);
1102 i++;
1106 // Helper method that takes a parameter ID a la Parameters.h and
1107 // looks up its type, name and value and pushes into our object
1108 // using the specified options mask.
1109 void CUserLog::UpdateParam(int iParameter, int iOptionMask)
1111 string strParamName = GetParameterName(iParameter);
1113 // What method we call depends on the type of the parameter
1114 switch (GetParameterType(iParameter))
1116 case (ParamBool):
1118 // Convert bool to a integer
1119 int iValue = 0;
1120 if (GetBoolParameter(iParameter))
1121 iValue = 1;
1122 AddParam(strParamName, iValue, iOptionMask);
1123 return;
1124 break;
1126 case (ParamLong):
1128 AddParam(strParamName, (int) GetLongParameter(iParameter), iOptionMask);
1129 return;
1130 break;
1132 case (ParamString):
1134 AddParam(strParamName, GetStringParameter(iParameter), iOptionMask);
1135 return;
1136 break;
1138 default:
1140 g_pLogger->Log("CUserLog::UpdateParam, matched parameter %d but unknown type %d", logNORMAL, iParameter, GetParameterType(iParameter));
1141 break;
1147 ///////////////////////////////////////////////////////////////////////////////////////
1148 // Below here are methods that are just used in the standalone tool that reads in
1149 // UserLog XML files and does cool things to them.
1151 // Load the object from an XML file
1152 CUserLog::CUserLog(string strXMLFilename) : CDasherComponent(NULL, NULL)
1154 //CFunctionLogger f1("CUserLog::CUserLog(XML)", g_pLogger);
1156 InitMemberVars();
1158 // We are representing detailed logging when we create from XML
1159 m_bDetailed = true;
1161 VECTOR_STRING vectorTrials;
1163 // First split up various parts of the XML
1164 string strXML = XMLUtil::LoadFile(strXMLFilename);
1165 string strApp = XMLUtil::GetElementString("Application", strXML, true);
1166 string strParams = XMLUtil::GetElementString("Params", strXML, true);
1167 string strTrials = XMLUtil::GetElementString("Trials", strXML, true);
1168 vectorTrials = XMLUtil::GetElementStrings("Trial", strTrials, true);
1170 m_pApplicationSpan = new CTimeSpan("Application", strApp);
1171 m_vParams = CUserLogTrial::ParseParamsXML(strParams);
1173 // Now construct each of the Trial objects based on its section of XML
1174 for (VECTOR_STRING_ITER iter = vectorTrials.begin(); iter < vectorTrials.end(); iter++)
1176 CUserLogTrial* pTrial = new CUserLogTrial(*iter, 0);
1178 if (pTrial != NULL)
1179 m_vpTrials.push_back(pTrial);
1184 // Returns a vector that contains vectors of strings which each
1185 // contain a tab delimited list of mouse coordinates for each
1186 // navigation cycle.
1187 VECTOR_VECTOR_STRING CUserLog::GetTabMouseXY(bool bReturnNormalized)
1189 //CFunctionLogger f1("CUserLog::GetTabMouseXY", g_pLogger);
1191 VECTOR_VECTOR_STRING vResult;
1193 for (VECTOR_USER_LOG_TRIAL_PTR_ITER iter = m_vpTrials.begin();
1194 iter < m_vpTrials.end();
1195 iter++)
1197 if (*iter != NULL)
1199 VECTOR_STRING vectorTrial = (*iter)->GetTabMouseXY(bReturnNormalized);
1200 vResult.push_back(vectorTrial);
1204 return vResult;
1207 // Returns a vector that contains a vector of density grids.
1208 VECTOR_VECTOR_DENSITY_GRIDS CUserLog::GetMouseDensity(int iGridSize)
1210 //CFunctionLogger f1("CUserLog::GetMouseDensity", g_pLogger);
1212 VECTOR_VECTOR_DENSITY_GRIDS vResult;
1213 for (VECTOR_USER_LOG_TRIAL_PTR_ITER iter = m_vpTrials.begin();
1214 iter < m_vpTrials.end();
1215 iter++)
1217 if (*iter != NULL)
1219 VECTOR_DENSITY_GRIDS vTrial = (*iter)->GetMouseDensity(iGridSize);
1220 vResult.push_back(vTrial);
1224 return vResult;