tagging release
[dasher.git] / Src / DasherCore / UserLog.cpp
blob6f75a04b70b203a01bd5cb24fdf29dfde1f87891
2 #include "../Common/Common.h"
4 #include "UserLog.h"
5 #include <fstream>
7 #ifdef _WIN32
8 #include <sys/timeb.h>
9 #else
10 #include <sys/time.h>
11 #endif
13 // Track memory leaks on Windows to the line that new'd the memory
14 #ifdef _WIN32
15 #ifdef _DEBUG
16 #define DEBUG_NEW new( _NORMAL_BLOCK, THIS_FILE, __LINE__ )
17 #define new DEBUG_NEW
18 #undef THIS_FILE
19 static char THIS_FILE[] = __FILE__;
20 #endif
21 #endif
23 static UserLogParamMask s_UserLogParamMaskTable [] = {
24 {SP_ALPHABET_ID, userLogParamOutputToSimple},
25 {SP_COLOUR_ID, userLogParamOutputToSimple},
26 {LP_MAX_BITRATE, userLogParamOutputToSimple |
27 userLogParamTrackMultiple |
28 userLogParamTrackInTrial |
29 userLogParamForceInTrial |
30 userLogParamShortInCycle},
31 {BP_CONTROL_MODE, userLogParamOutputToSimple},
32 {LP_UNIFORM, userLogParamOutputToSimple},
33 {LP_YSCALE, userLogParamOutputToSimple},
34 {LP_LANGUAGE_MODEL_ID, userLogParamOutputToSimple},
35 {LP_LM_MAX_ORDER, userLogParamOutputToSimple},
36 {LP_LM_EXCLUSION, userLogParamOutputToSimple},
37 {LP_LM_UPDATE_EXCLUSION, userLogParamOutputToSimple},
38 {LP_LM_ALPHA, userLogParamOutputToSimple},
39 {LP_LM_BETA, userLogParamOutputToSimple},
40 {LP_LM_MIXTURE, userLogParamOutputToSimple},
41 {LP_LM_WORD_ALPHA, userLogParamOutputToSimple},
42 {-1, -1} // Flag value that should always be at the end
45 CUserLog::CUserLog(Dasher::CEventHandler *pEventHandler,
46 CSettingsStore *pSettingsStore,
47 int iLogTypeMask,
48 Dasher::CAlphabet* pAlphabet) : CUserLogBase(pEventHandler, pSettingsStore)
50 //CFunctionLogger f1("CUserLog::CUserLog", g_pLogger);
52 InitMemberVars();
54 m_pAlphabet = pAlphabet;
55 m_iLevelMask = iLogTypeMask;
57 InitUsingMask(iLogTypeMask);
59 if ((m_bSimple) && (m_pSimpleLogger != NULL))
60 m_pSimpleLogger->Log("start, %s", logDEBUG, GetVersionInfo().c_str());
62 SetOuputFilename();
63 m_pApplicationSpan = new CTimeSpan("Application", true);
65 if (m_pApplicationSpan == NULL)
66 g_pLogger->Log("CUserLog::CUserLog, failed to create m_pApplicationSpan!", logNORMAL);
68 // From the load test harness, we create object directly without a settings store
69 // and event handler. In this case, we don't want to try and push the intial
70 // parameters.
71 if ((pEventHandler != NULL) && (pSettingsStore != NULL))
72 AddInitialParam();
75 CUserLog::~CUserLog()
77 //CFunctionLogger f1("CUserLog::~CUserLog", g_pLogger);
79 if ((m_bSimple) && (m_pSimpleLogger != NULL))
80 m_pSimpleLogger->Log("stop", logDEBUG);
82 if (m_pApplicationSpan != NULL)
84 delete m_pApplicationSpan;
85 m_pApplicationSpan = NULL;
88 for (unsigned int i = 0; i < m_vpTrials.size(); i++)
90 CUserLogTrial* pTrial = (CUserLogTrial*) m_vpTrials[i];
92 if (pTrial != NULL)
94 delete pTrial;
95 pTrial = NULL;
99 for (unsigned int i = 0; i < m_vParams.size(); i++)
101 CUserLogParam* pParam = (CUserLogParam*) m_vParams[i];
103 if (pParam != NULL)
105 delete pParam;
106 pParam = NULL;
110 if (m_pSimpleLogger != NULL)
112 delete m_pSimpleLogger;
113 m_pSimpleLogger = NULL;
116 if (m_pCycleTimer != NULL)
118 delete m_pCycleTimer;
119 m_pCycleTimer = NULL;
123 // Do initialization of member variables based on the user log level mask
124 void CUserLog::InitUsingMask(int iLogLevelMask)
126 //CFunctionLogger f1("CUserLog::InitUsingMask", g_pLogger);
128 m_bInitIsDone = false;
130 if (iLogLevelMask & userLogSimple)
132 // First we check to see if the file exists, if it does not
133 // then we want to force all parameter values to be sent to
134 // the log file even before InitIsDone() is called.
135 FILE* fp = fopen(USER_LOG_SIMPLE_FILENAME.c_str(), "r");
136 if (fp == NULL)
138 m_bInitIsDone = true;
140 else
142 fclose(fp);
143 fp = NULL;
146 m_bSimple = true;
148 if (m_pSimpleLogger == NULL)
149 m_pSimpleLogger = new CFileLogger(USER_LOG_SIMPLE_FILENAME, logDEBUG, logTimeStamp | logDateStamp);
152 if (iLogLevelMask & userLogDetailed)
153 m_bDetailed = true;
157 // Called when we want to output the log file (usually on exit of dasher)
158 void CUserLog::OutputFile()
160 //CFunctionLogger f1("CUserLog::OutputFile", g_pLogger);
162 if (m_bDetailed)
164 // Let the last pTrial object know we are done with it, this lets it do
165 // any final calculations.
166 if (m_vpTrials.size() > 0)
168 CUserLogTrial* pTrial = m_vpTrials[m_vpTrials.size() - 1];
170 if (pTrial != NULL)
171 pTrial->Done();
174 // Output our data to an XML file before we destruct
175 WriteXML();
179 void CUserLog::StartWriting()
181 //CFunctionLogger f1("CUserLog::StartWriting", g_pLogger);
183 if (m_bSimple)
185 // The canvas size changes multiple times as a user resizes it. We just want to write
186 // one short log entry for the final position the next time they start writing.
187 if ((m_bNeedToWriteCanvas) && (m_pSimpleLogger != NULL))
189 m_pSimpleLogger->Log("canvas:\t%d\t%d\t%d\t%d",
190 logDEBUG,
191 m_sCanvasCoordinates.top,
192 m_sCanvasCoordinates.left,
193 m_sCanvasCoordinates.bottom,
194 m_sCanvasCoordinates.right);
195 m_bNeedToWriteCanvas = false;
198 // We log what happened between StartWriting() and StopWriting()
199 // so clear out any previous history.
200 ResetCycle();
203 if (m_bDetailed)
205 CUserLogTrial* pTrial = GetCurrentTrial();
207 // This could be the first use in this pTrial, create a new one if needed
208 if (pTrial == NULL)
209 pTrial = AddTrial();
211 if (pTrial != NULL)
212 pTrial->StartWriting();
213 else
214 g_pLogger->Log("CUserLog::StartWriting, failed to create new pTrial!", logNORMAL);
217 m_bIsWriting = true;
220 // This version should be called at the end of navigation with the dNats
221 // value under the current mouse position. This would be more accurate
222 // then the last value from a mouse event since some time may have
223 // elapsed.
224 void CUserLog::StopWriting(float dNats)
226 //CFunctionLogger f1("CUserLog::StopWriting", g_pLogger);
228 if (m_bIsWriting)
230 m_dCycleNats = (double) dNats;
231 StopWriting();
235 void CUserLog::StopWriting()
237 //CFunctionLogger f1("CUserLog::StopWriting", g_pLogger);
239 if (m_bIsWriting)
241 m_bIsWriting = false;
243 // In simple logging mode, we'll output the stats for this navigation cycle
244 if ((m_bSimple) && (m_pSimpleLogger != NULL))
245 m_pSimpleLogger->Log("%s", logDEBUG, GetStartStopCycleStats().c_str());
247 if (m_bDetailed)
249 CUserLogTrial* pTrial = GetCurrentTrial();
251 if (pTrial == NULL)
253 g_pLogger->Log("CUserLog::StopWriting, pTrial was NULL!", logNORMAL);
254 return;
257 pTrial->StopWriting(GetCycleBits());
262 void CUserLog::AddSymbols(Dasher::VECTOR_SYMBOL_PROB* vpNewSymbols, eUserLogEventType iEvent)
264 //CFunctionLogger f1("CUserLog::AddSymbols", g_pLogger);
266 if (!m_bIsWriting)
268 // StartWriting() wasn't called, so we'll do it implicitly now
269 g_pLogger->Log("CUserLog::AddSymbols, StartWriting() not called?", logDEBUG);
270 StartWriting();
273 if (vpNewSymbols == NULL)
275 g_pLogger->Log("CUserLog::AddSymbols, vpNewSymbols was NULL!", logNORMAL);
276 return;
279 if (m_bSimple)
281 // Also store a copy in a vector that gets cleared
282 // time StartWriting() is called.
284 for (unsigned int i = 0; i < vpNewSymbols->size(); i++)
286 Dasher::SymbolProb sNewSymbolProb = (Dasher::SymbolProb) (*vpNewSymbols)[i];
287 m_vCycleHistory.push_back(sNewSymbolProb);
291 if (m_bDetailed)
293 CUserLogTrial* pTrial = GetCurrentTrial();
295 // We should have a pTrial object since StartWriting() should have been called before us
296 if (pTrial == NULL)
298 g_pLogger->Log("CUserLog::AddSymbols, pTrial was NULL!", logNORMAL);
299 return;
302 if (m_pAlphabet == NULL)
304 g_pLogger->Log("CUserLog::AddSymbols, can't do detailed logging of symbols without an alphabet!", logNORMAL);
305 return;
308 pTrial->AddSymbols(vpNewSymbols, iEvent, m_pAlphabet);
312 void CUserLog::DeleteSymbols(int iNumToDelete, eUserLogEventType iEvent)
314 //CFunctionLogger f1("CUserLog::DeleteSymbols", g_pLogger);
316 if (iNumToDelete <= 0)
317 return;
319 if (!m_bIsWriting)
321 // StartWriting() wasn't called, so we'll do it implicitly now
322 g_pLogger->Log("CUserLog::DeleteSymbols, StartWriting() not called?", logDEBUG);
323 StartWriting();
326 if (m_bSimple)
328 m_iCycleNumDeletes += iNumToDelete;
330 // Be careful not to pop more things than we have (this will hork the
331 // memory up on linux but not windows).
332 int iActualNumToDelete = min((int) m_vCycleHistory.size(), iNumToDelete);
333 for (int i = 0; i < iActualNumToDelete; i++)
334 m_vCycleHistory.pop_back();
337 if (m_bDetailed)
339 CUserLogTrial* pTrial = GetCurrentTrial();
341 // We should have a pTrial object since StartWriting() should have been called before us
342 if (pTrial == NULL)
344 g_pLogger->Log("CUserLog::DeleteSymbols, pTrial was NULL!", logNORMAL);
345 return;
348 pTrial->DeleteSymbols(iNumToDelete, iEvent);
352 void CUserLog::NewTrial()
354 //CFunctionLogger f1("CUserLog::NewTrial", g_pLogger);
356 if (m_bIsWriting)
358 // We should have called StopWriting(), but we'll do it here implicitly
359 g_pLogger->Log("CUserLog::NewTrial, StopWriting() not called?", logDEBUG);
360 StopWriting();
363 // For safety we can dump the XML to file after each pTrial is done. This
364 // might be a good idea for long user pTrial sessions just in case Dasher
365 // were to do something completely crazy like crash.
366 if (USER_LOG_DUMP_AFTER_TRIAL)
367 WriteXML();
369 if (m_bDetailed)
371 CUserLogTrial* pTrial = GetCurrentTrial();
373 if (pTrial == NULL)
375 // Not an error, they may just hit new doc before anything else at start
376 return;
379 if (pTrial->HasWritingOccured())
381 // Create a new pTrial if the existing one has already been used
382 pTrial = AddTrial();
387 // Overloaded version that converts a double to a string
388 void CUserLog::AddParam(const string& strName, double dValue, int iOptionMask)
390 sprintf(m_szTempBuffer, "%0.4f", dValue);
391 AddParam(strName, m_szTempBuffer, iOptionMask);
394 // Overloaded version that converts a int to a string
395 void CUserLog::AddParam(const string& strName, int iValue, int iOptionMask)
397 sprintf(m_szTempBuffer, "%d", iValue);
398 AddParam(strName, m_szTempBuffer, iOptionMask);
401 // Adds a general parameter to our XML. This lets various Dasher components
402 // record what they are set at. Optionally can be set to track multiple
403 // values for the same parameter or to always output a line to the simple
404 // log file when the parameter is set.
405 void CUserLog::AddParam(const string& strName, const string& strValue, int iOptionMask)
407 //CFunctionLogger f1("CUserLog::AddParam", g_pLogger);
409 bool bOutputToSimple = false;
410 bool bTrackMultiple = false;
411 bool bTrackInTrial = false;
412 bool bForceInTrial = false;
413 bool bShortInCycle = false;
415 if (iOptionMask & userLogParamOutputToSimple)
416 bOutputToSimple = true;
417 if (iOptionMask & userLogParamTrackMultiple)
418 bTrackMultiple = true;
419 if (iOptionMask & userLogParamTrackInTrial)
420 bTrackInTrial = true;
421 if (iOptionMask & userLogParamForceInTrial)
422 bForceInTrial = true;
423 if (iOptionMask & userLogParamShortInCycle)
424 bShortInCycle = true;
426 // See if we want to immediately output this name/value pair to
427 // the running simple log file. If we are tracking the parameter
428 // in the short cycle stats line, then don't output here.
429 if ((bOutputToSimple) &&
430 (m_bSimple) &&
431 (m_pSimpleLogger != NULL) &&
432 (m_bInitIsDone) &&
433 (!bShortInCycle))
435 m_pSimpleLogger->Log("%s = %s", logDEBUG, strName.c_str(), strValue.c_str());
438 // See if this matches an existing parameter value that we may want to
439 // overwrite. But only if we aren't suppose to keep track of multiple changes.
440 if (!bTrackMultiple)
442 for (unsigned int i = 0; i < m_vParams.size(); i++)
444 CUserLogParam* pParam = (CUserLogParam*) m_vParams[i];
446 if (pParam != NULL)
448 if (pParam->strName.compare(strName) == 0)
450 pParam->strValue = strValue;
451 return;
457 // We need to add a new param
458 CUserLogParam* pNewParam = new CUserLogParam;
460 if (pNewParam == NULL)
462 g_pLogger->Log("CUserLog::AddParam, failed to create CUserLogParam object!", logNORMAL);
463 return;
466 pNewParam->strName = strName;
467 pNewParam->strValue = strValue;
468 pNewParam->strTimeStamp = "";
469 pNewParam->options = iOptionMask;
471 // Parameters that can have multiple values logged will also log when they were changed
472 if (bTrackMultiple)
473 pNewParam->strTimeStamp = CTimeSpan::GetTimeStamp();
475 m_vParams.push_back(pNewParam);
477 if ((bTrackInTrial) && (m_bDetailed))
479 // See if we need to pass the parameter onto the current pTrial object
480 CUserLogTrial* pTrial = GetCurrentTrial();
482 if (pTrial != NULL)
483 pTrial->AddParam(strName, strValue, iOptionMask);
488 // Adds a new point in our tracking of mouse locations
489 void CUserLog::AddMouseLocation(int iX, int iY, float dNats)
491 //CFunctionLogger f1("CUserLog::AddMouseLocation", g_pLogger);
493 // Check to see if it is time to actually push a mouse location update
494 if (UpdateMouseLocation())
496 if (m_bDetailed)
498 CUserLogTrial* pTrial = GetCurrentTrial();
500 if (pTrial == NULL)
502 // Only track during an actual pTrial
503 return;
506 // Only record mouse locations during navigation
507 if (pTrial->IsWriting())
508 pTrial->AddMouseLocation(iX, iY, dNats);
511 // Keep track of the dNats for the current mouse position
512 if (m_bIsWriting)
513 m_dCycleNats = dNats;
517 // Adds the size of the current window
518 void CUserLog::AddWindowSize(int iTop, int iLeft, int iBottom, int iRight)
520 //CFunctionLogger f1("CUserLog::AddWindowSize", g_pLogger);
522 m_sWindowCoordinates.top = iTop;
523 m_sWindowCoordinates.left = iLeft;
524 m_sWindowCoordinates.bottom = iBottom;
525 m_sWindowCoordinates.right = iRight;
527 if (m_bDetailed)
529 CUserLogTrial* pTrial = GetCurrentTrial();
531 if (pTrial == NULL)
533 // Only track during an actual pTrial
534 return;
537 pTrial->AddWindowSize(iTop, iLeft, iBottom, iRight);
541 // Adds the size of the current canvas, this should be called when our
542 // window is initially created and whenever somebody mucks with the
543 // size.
544 void CUserLog::AddCanvasSize(int iTop, int iLeft, int iBottom, int iRight)
546 //CFunctionLogger f1("CUserLog::AddCanvasSize", g_pLogger);
548 // Only log to simple log object if the coordinates are different from
549 // what we had prior to now.
550 if ((m_bSimple) &&
551 (m_pSimpleLogger != NULL) &&
552 ((m_sCanvasCoordinates.top != iTop) ||
553 (m_sCanvasCoordinates.left != iLeft) ||
554 (m_sCanvasCoordinates.bottom != iBottom) ||
555 (m_sCanvasCoordinates.right != iRight)))
556 m_bNeedToWriteCanvas = true;
558 m_sCanvasCoordinates.top = iTop;
559 m_sCanvasCoordinates.left = iLeft;
560 m_sCanvasCoordinates.bottom = iBottom;
561 m_sCanvasCoordinates.right = iRight;
563 if (m_bDetailed)
565 CUserLogTrial* pTrial = GetCurrentTrial();
567 if (pTrial == NULL)
569 // Only track during an actual pTrial
570 return;
573 pTrial->AddCanvasSize(iTop, iLeft, iBottom, iRight);
577 // We may want to use a noramlized version of mouse coordinates, this way it is
578 // invariant to changes in the window size before, during, or after navigation.
579 // The caller must send us both the x, y coordinates and the current window size.
580 void CUserLog::AddMouseLocationNormalized(int iX, int iY, bool bStoreIntegerRep, float dNats)
582 //CFunctionLogger f1("CUserLog::AddMouseLocationNormalized", g_pLogger);
584 // Check to see if it is time to actually push a mouse location update
585 if (UpdateMouseLocation())
587 if ((m_sCanvasCoordinates.bottom == 0) &&
588 (m_sCanvasCoordinates.left == 0) &&
589 (m_sCanvasCoordinates.right == 0) &&
590 (m_sCanvasCoordinates.top == 0))
592 g_pLogger->Log("CUserLog::AddMouseLocationNormalized, called before AddCanvasSize()?", logNORMAL);
593 return;
596 ComputeSimpleMousePos(iX, iY);
598 if (m_bDetailed)
600 CUserLogTrial* pTrial = GetCurrentTrial();
602 if (pTrial == NULL)
604 // Only track during an actual pTrial
605 return;
608 // Only record mouse locations during navigation
609 if (pTrial->IsWriting())
610 pTrial->AddMouseLocationNormalized(iX, iY, bStoreIntegerRep, dNats);
613 // Keep track of the dNats for the current mouse position
614 if (m_bIsWriting)
615 m_dCycleNats = dNats;
619 // For simple logging, we don't want to log the same parameters settings
620 // in the file every single time Dasher starts up. So we require that
621 // this method be called once the initial loading of parameters is
622 // complete. This way only changes during a session are logged (we can
623 // also force logging of the parameter setting when the log file is
624 // created by setting m_bInitIsDone to true in the constructor).
625 void CUserLog::InitIsDone()
627 //CFunctionLogger f1("CUserLog::InitIsDone", g_pLogger);
629 m_bInitIsDone = true;
632 // Update our pointer to the alphabet we are using, we need th
633 // alphabet in order to convert symbols into display strings
634 // that we put in the log file.
635 void CUserLog::SetAlphabetPtr(Dasher::CAlphabet* pAlphabet)
637 //CFunctionLogger f1("CUserLog::SetAlphabetPtr", g_pLogger);
639 m_pAlphabet = pAlphabet;
642 // Sets our output filename based on the current date and time.
643 // Or if a parameter is passed in, use that as the output name.
644 void CUserLog::SetOuputFilename(const string& strFilename)
646 //CFunctionLogger f1("CUserLog::SetOuputFilename", g_pLogger);
648 if (strFilename.length() > 0)
650 m_strFilename = strFilename;
652 else
654 m_strFilename = USER_LOG_DETAILED_PREFIX;
656 #ifdef _WIN32
657 struct timeb sTimeBuffer;
658 #else
659 struct timeval sTimeBuffer;
660 struct timezone sTimezoneBuffer;
661 #endif
662 char* szTimeLine = NULL;
664 #ifdef _WIN32
665 ftime(&sTimeBuffer);
666 szTimeLine = ctime(&(sTimeBuffer.time));
667 #else
668 gettimeofday(&sTimeBuffer, &sTimezoneBuffer);
669 szTimeLine = ctime(&(sTimeBuffer.tv_sec));
670 #endif
672 if ((szTimeLine != NULL) && (strlen(szTimeLine) > 18))
674 for (int i = 4; i < 19; i++)
676 if (szTimeLine[i] == ' ')
677 m_strFilename += "_";
678 else if (szTimeLine[i] != ':')
679 m_strFilename += szTimeLine[i];
683 m_strFilename += ".xml";
686 // Make sure we store a fully qualified form, to prevent movent
687 // if the working directory changes
688 m_strFilename = CFileLogger::GetFullFilenamePath(m_strFilename);
692 // Find out what level mask this object was created with
693 int CUserLog::GetLogLevelMask()
695 //CFunctionLogger f1("CUserLog::GetLogLevelMask", g_pLogger);
697 return m_iLevelMask;
700 void CUserLog::KeyDown(int iId, int iType, int iEffect) {
701 CUserLogTrial* pTrial = GetCurrentTrial();
703 if(pTrial)
704 pTrial->AddKeyDown(iId, iType, iEffect);
707 // This gets called whenever parameters get changed that we are tracking
708 void CUserLog::HandleEvent(Dasher::CEvent* pEvent)
710 if(pEvent->m_iEventType == 1) {
711 Dasher::CParameterNotificationEvent* pEvt(static_cast < Dasher::CParameterNotificationEvent * >(pEvent));
713 int i = 0;
715 // Go through each of the parameters in our lookup table from UserLogParam.h.
716 // If the key matches the notification event, then we want to push the
717 // parameter change to the logging object.
718 while (s_UserLogParamMaskTable[i].key != -1)
720 int iParameter = pEvt->m_iParameter;
722 if (s_UserLogParamMaskTable[i].key == iParameter)
724 int iOptionMask = s_UserLogParamMaskTable[i].mask;
726 UpdateParam(iParameter, iOptionMask);
727 return;
730 i++;
732 } // end while (s_UserLogParamMaskTable[i].key != -1)
734 } // end if (pEvent->m_iEventType == 1)
738 ////////////////////////////////////////// private methods ////////////////////////////////////////////////
740 // Just inits all our member variables, called by the constructors
741 void CUserLog::InitMemberVars()
743 //CFunctionLogger f1("CUserLog::InitMemberVars", g_pLogger);
745 m_strFilename = "";
746 m_pApplicationSpan = NULL;
747 m_dLastMouseUpdate = 0.0;
748 m_bSimple = false;
749 m_bDetailed = false;
750 m_pSimpleLogger = NULL;
751 m_pAlphabet = NULL;
752 m_bIsWriting = false;
753 m_bInitIsDone = false;
754 m_bNeedToWriteCanvas = false;
756 m_pCycleTimer = NULL;
757 m_iCycleNumDeletes = 0;
758 m_iCycleMouseCount = 0;
759 m_dCycleMouseNormXSum = 0.0;
760 m_dCycleMouseNormYSum = 0.0;
761 m_dCycleNats = 0.0;
763 m_sCanvasCoordinates.bottom = 0;
764 m_sCanvasCoordinates.top = 0;
765 m_sCanvasCoordinates.right = 0;
766 m_sCanvasCoordinates.left = 0;
768 m_sWindowCoordinates.bottom = 0;
769 m_sWindowCoordinates.top = 0;
770 m_sWindowCoordinates.right = 0;
771 m_sWindowCoordinates.left = 0;
773 // We want to use a fully qualified path so that we always
774 // look in the same spot, regardless of if the working
775 // directory has moved during runtime.
776 m_strCurrentTrialFilename = CFileLogger::GetFullFilenamePath(USER_LOG_CURRENT_TRIAL_FILENAME);
780 // Write this objects XML out
781 bool CUserLog::WriteXML()
783 //CFunctionLogger f1("CUserLog::WriteXML", g_pLogger);
787 fstream fout(m_strFilename.c_str(), ios::trunc | ios::out);
788 fout << GetXML();
789 fout.close();
791 return true;
793 } catch (...)
795 g_pLogger->Log("CUserLog::WriteXML, failed to write file %s", logNORMAL, m_strFilename.c_str());
796 return false;
800 // Serializes our data to XML
801 string CUserLog::GetXML()
803 //CFunctionLogger f1("CUserLog::GetXML", g_pLogger);
805 string strResult = "";
806 strResult.reserve(USER_LOG_DEFAULT_SIZE_TRIAL_XML * (m_vpTrials.size() + 1));
808 strResult += "<?xml version=\"1.0\"?>\n";
810 strResult += "<UserLog>\n";
811 if (m_pApplicationSpan != NULL)
812 strResult += m_pApplicationSpan->GetXML("\t");
814 strResult += "\t<Params>\n";
815 strResult += GetParamsXML();
816 strResult += "\t</Params>\n";
818 strResult += "\t<Trials>\n";
819 for (unsigned int i = 0; i < m_vpTrials.size(); i++)
821 CUserLogTrial* pTrial = (CUserLogTrial*) m_vpTrials[i];
823 // Only log trials that actually had some writing in it
824 if ((pTrial != NULL) && (pTrial->HasWritingOccured()))
826 strResult += pTrial->GetXML("\t\t");
829 strResult += "\t</Trials>\n";
831 strResult += "</UserLog>\n";
833 return strResult;
836 // Returns pointer to the current user pTrial, NULL if we don't have one yet
837 CUserLogTrial* CUserLog::GetCurrentTrial()
839 //CFunctionLogger f1("CUserLog::GetCurrentTrial", g_pLogger);
841 if (m_vpTrials.size() <= 0)
842 return NULL;
843 return m_vpTrials[m_vpTrials.size() - 1];
846 // Creates a new pTrial, adds to our vector and returns the pointer
847 CUserLogTrial* CUserLog::AddTrial()
849 //CFunctionLogger f1("CUserLog::AddTrial", g_pLogger);
851 // Let the last pTrial object know we are done with it
852 if (m_vpTrials.size() > 0)
854 CUserLogTrial* pTrial = m_vpTrials[m_vpTrials.size() - 1];
856 if (pTrial != NULL)
857 pTrial->Done();
860 CUserLogTrial* pTrial = new CUserLogTrial(m_strCurrentTrialFilename);
861 if (pTrial != NULL)
863 m_vpTrials.push_back(pTrial);
864 PrepareNewTrial();
866 else
867 g_pLogger->Log("CUserLog::AddTrial, failed to create CUserLogTrialSpeech!", logNORMAL);
869 return pTrial;
872 // See if the specified number of milliseconds has elapsed since the last mouse location update
873 bool CUserLog::UpdateMouseLocation()
875 //CFunctionLogger f1("CUserLog::UpdateMouseLocation", g_pLogger);
877 #ifdef _WIN32
878 struct timeb sTimeBuffer;
879 #else
880 struct timeval sTimeBuffer;
881 struct timezone sTimezoneBuffer;
882 #endif
884 #ifdef _WIN32
885 ftime(&sTimeBuffer);
886 double dTime = (sTimeBuffer.time * 1000.0) + sTimeBuffer.millitm;
887 #else
888 gettimeofday(&sTimeBuffer, &sTimezoneBuffer);
889 double dTime = (sTimeBuffer.tv_sec * 1000.0) + sTimeBuffer.tv_usec / 1000;
890 #endif
893 if ((dTime - m_dLastMouseUpdate) > LOG_MOUSE_EVERY_MS)
895 m_dLastMouseUpdate = dTime;
896 return true;
898 return false;
901 // Calculate how many bits entered in the last Start/Stop cycle
902 double CUserLog::GetCycleBits()
904 //CFunctionLogger f1("CUserLog::GetCycleBits", g_pLogger);
906 return m_dCycleNats / log(2.0);
909 // For lightweight logging, we want a string that represents the critical
910 // stats for what happened between start and stop
911 string CUserLog::GetStartStopCycleStats()
913 //CFunctionLogger f1("CUserLog::GetStartStopCycleStats", g_pLogger);
915 string strResult = "";
917 double dNormX = 0.0;
918 double dNormY = 0.0;
919 if (m_iCycleMouseCount > 0)
921 dNormX = m_dCycleMouseNormXSum / (double) m_iCycleMouseCount,
922 dNormY = m_dCycleMouseNormYSum / (double) m_iCycleMouseCount;
925 if (m_pCycleTimer == NULL)
927 g_pLogger->Log("CUserLog::GetStartStopCycleStats, cycle timer was NULL!", logNORMAL);
928 return "";
931 // Tab delimited fields are:
932 // elapsed time, symbols written, bits written, symbols deleted,
933 // avg normalized x mouse coordinate, avg normalized y mouse
934 // coordinate, (any parameters marked to be put in cycle stats)
936 // tsbdxym stands for: time symbols bits deletes x y maxbitrate
937 sprintf(m_szTempBuffer,
938 "tsbdxym:\t%0.3f\t%d\t%0.6f\t%d\t%0.3f\t%0.3f%s",
939 m_pCycleTimer->GetElapsed(),
940 m_vCycleHistory.size(),
941 GetCycleBits(),
942 m_iCycleNumDeletes,
943 dNormX,
944 dNormY,
945 GetCycleParamStats().c_str());
946 strResult = m_szTempBuffer;
948 return strResult;
951 // Helper that computes update of the simple logging's mouse
952 // position tracking member variables.
953 void CUserLog::ComputeSimpleMousePos(int iX, int iY)
955 //CFunctionLogger f1("CUserLog::ComputeSimpleMousePos", g_pLogger);
957 if ((m_bSimple) && (m_bIsWriting))
959 // We keep a running sum of normalized X, Y coordinates
960 // for use in the simple log file.
961 m_dCycleMouseNormXSum += CUserLocation::ComputeNormalizedX(iX,
962 m_sCanvasCoordinates.left,
963 m_sCanvasCoordinates.right);
965 m_dCycleMouseNormYSum += CUserLocation::ComputeNormalizedY(iY,
966 m_sCanvasCoordinates.top,
967 m_sCanvasCoordinates.bottom);
968 m_iCycleMouseCount++;
972 // Resets member variables that track a cycle for simple logging
973 void CUserLog::ResetCycle()
975 //CFunctionLogger f1("CUserLog::ResetCycle", g_pLogger);
977 m_vCycleHistory.clear();
978 m_iCycleNumDeletes = 0;
979 m_iCycleMouseCount = 0;
980 m_dCycleMouseNormXSum = 0.0;
981 m_dCycleMouseNormYSum = 0.0;
983 if (m_pCycleTimer != NULL)
985 delete m_pCycleTimer;
986 m_pCycleTimer = NULL;
989 m_pCycleTimer = new CSimpleTimer();
992 // Gets the XML that goes in the <Params> tag, but not the tags themselves.
993 string CUserLog::GetParamsXML()
995 //CFunctionLogger f1("CUserLog::GetParamsXML", g_pLogger);
997 string strResult = "";
999 // Make parameters with the same name appear near each other in the results
1000 sort(m_vParams.begin(), m_vParams.end(), CUserLogParam::ComparePtr);
1002 for (unsigned int i = 0; i < m_vParams.size(); i++)
1004 CUserLogParam* pParam = (CUserLogParam*) m_vParams[i];
1006 strResult += CUserLogTrial::GetParamXML(pParam, "\t\t");
1009 return strResult;
1012 // Prepares a new pTrial for use. Passes on the current canvas and window
1013 // size so normalized mouse coordinates can be calculated. Also
1014 // parameters can be marked to force them into the Trial object. Looks for
1015 // these and push into the current Trial object.
1016 void CUserLog::PrepareNewTrial()
1018 //CFunctionLogger f1("CUserLog::PrepareNewTrial", g_pLogger);
1020 CUserLogTrial* pTrial = GetCurrentTrial();
1022 if (pTrial != NULL)
1024 // We want to force the current value of any parameters that we marked
1025 // with the userLogParamForceInTrial option when created. We can
1026 // do this by going backwards through the parameter vector and only
1027 // pushing through the first value of a given parameter name.
1028 VECTOR_STRING vFound;
1029 if (m_vParams.size() > 0)
1031 for (VECTOR_USER_LOG_PARAM_PTR_REV_ITER iter = m_vParams.rbegin(); iter != m_vParams.rend(); ++iter)
1033 if (((*iter) != NULL) && ((*iter)->options & userLogParamForceInTrial))
1035 // Make sure we haven't output this one already
1036 VECTOR_STRING_ITER strIter;
1037 strIter = find(vFound.begin(), vFound.end(), (*iter)->strName);
1038 if (strIter == vFound.end())
1040 pTrial->AddParam((*iter)->strName, (*iter)->strValue, (*iter)->options);
1041 vFound.push_back((*iter)->strName);
1047 // Make sure the pTrial has the current canvas and window coordinate sizes
1048 pTrial->AddCanvasSize(m_sCanvasCoordinates.top,
1049 m_sCanvasCoordinates.left,
1050 m_sCanvasCoordinates.bottom,
1051 m_sCanvasCoordinates.right);
1053 pTrial->AddWindowSize(m_sWindowCoordinates.top,
1054 m_sWindowCoordinates.left,
1055 m_sWindowCoordinates.bottom,
1056 m_sWindowCoordinates.right);
1059 else
1060 g_pLogger->Log("CUserLog::PrepareNewTrial, failed to create CUserLogTrial", logNORMAL);
1063 // Parameters can be marked to always end them at the cycle stats in short logging.
1064 // We'll look through our parameters and return a tab delimited list of their
1065 // values.
1066 string CUserLog::GetCycleParamStats()
1068 //CFunctionLogger f1("CUserLog::GetCycleParamStats", g_pLogger);
1070 string strResult = "";
1071 VECTOR_STRING vFound;
1073 if (m_vParams.size() <= 0)
1074 return strResult;
1076 // We may have more than one parameter that needs to be added and we want
1077 // the stats line to be invariant to the order in which AddParam() was
1078 // called. So we'll sort by param name and then by time stamp (for
1079 // parameters with multiple values).
1080 sort(m_vParams.begin(), m_vParams.end(), CUserLogParam::ComparePtr);
1082 // Find the last instance of any parameter marked as needed to be on
1083 // the cycle stats line.
1084 for (VECTOR_USER_LOG_PARAM_PTR_REV_ITER iter = m_vParams.rbegin(); iter != m_vParams.rend(); ++iter)
1086 if (((*iter) != NULL) && ((*iter)->options & userLogParamShortInCycle))
1088 // Make sure we haven't output this one already
1089 VECTOR_STRING_ITER strIter;
1090 strIter = find(vFound.begin(), vFound.end(), (*iter)->strName);
1091 if (strIter == vFound.end())
1093 strResult += "\t";
1094 strResult += (*iter)->strValue;
1095 vFound.push_back((*iter)->strName);
1100 return strResult;
1103 // Return a string with the operating system and product version
1104 string CUserLog::GetVersionInfo()
1106 //CFunctionLogger f1("CUserLog::GetVersionInfo", g_pLogger);
1108 string strResult = "";
1109 #ifdef _WIN32
1110 strResult += "win ";
1112 // TBD: getting version from resource is quite tricky and requires linking in
1113 // a whole library to do. Maybe we can just #DEFINE the product version?
1114 #else
1115 strResult += "not win ";
1116 #endif
1118 return strResult;
1121 // Forces all the parameters we are tracking to be intially set, used when the
1122 // object is first starting up.
1123 void CUserLog::AddInitialParam()
1125 int i = 0;
1126 while (s_UserLogParamMaskTable[i].key != -1)
1128 UpdateParam(s_UserLogParamMaskTable[i].key, s_UserLogParamMaskTable[i].mask);
1129 i++;
1133 // Helper method that takes a parameter ID a la Parameters.h and
1134 // looks up its type, name and value and pushes into our object
1135 // using the specified options mask.
1136 void CUserLog::UpdateParam(int iParameter, int iOptionMask)
1138 string strParamName = GetParameterName(iParameter);
1140 // What method we call depends on the type of the parameter
1141 switch (GetParameterType(iParameter))
1143 case (ParamBool):
1145 // Convert bool to a integer
1146 int iValue = 0;
1147 if (GetBoolParameter(iParameter))
1148 iValue = 1;
1149 AddParam(strParamName, iValue, iOptionMask);
1150 return;
1151 break;
1153 case (ParamLong):
1155 AddParam(strParamName, (int) GetLongParameter(iParameter), iOptionMask);
1156 return;
1157 break;
1159 case (ParamString):
1161 AddParam(strParamName, GetStringParameter(iParameter), iOptionMask);
1162 return;
1163 break;
1165 default:
1167 g_pLogger->Log("CUserLog::UpdateParam, matched parameter %d but unknown type %d", logNORMAL, iParameter, GetParameterType(iParameter));
1168 break;
1174 ///////////////////////////////////////////////////////////////////////////////////////
1175 // Below here are methods that are just used in the standalone tool that reads in
1176 // UserLog XML files and does cool things to them.
1178 // Load the object from an XML file
1179 CUserLog::CUserLog(string strXMLFilename) : CUserLogBase(NULL, NULL)
1181 //CFunctionLogger f1("CUserLog::CUserLog(XML)", g_pLogger);
1183 InitMemberVars();
1185 // We are representing detailed logging when we create from XML
1186 m_bDetailed = true;
1188 VECTOR_STRING vectorTrials;
1190 // First split up various parts of the XML
1191 string strXML = XMLUtil::LoadFile(strXMLFilename);
1192 string strApp = XMLUtil::GetElementString("Application", strXML, true);
1193 string strParams = XMLUtil::GetElementString("Params", strXML, true);
1194 string strTrials = XMLUtil::GetElementString("Trials", strXML, true);
1195 vectorTrials = XMLUtil::GetElementStrings("Trial", strTrials, true);
1197 m_pApplicationSpan = new CTimeSpan("Application", strApp);
1198 m_vParams = CUserLogTrial::ParseParamsXML(strParams);
1200 // Now construct each of the Trial objects based on its section of XML
1201 for (VECTOR_STRING_ITER iter = vectorTrials.begin(); iter < vectorTrials.end(); iter++)
1203 CUserLogTrial* pTrial = new CUserLogTrial(*iter, 0);
1205 if (pTrial != NULL)
1206 m_vpTrials.push_back(pTrial);
1211 // Returns a vector that contains vectors of strings which each
1212 // contain a tab delimited list of mouse coordinates for each
1213 // navigation cycle.
1214 VECTOR_VECTOR_STRING CUserLog::GetTabMouseXY(bool bReturnNormalized)
1216 //CFunctionLogger f1("CUserLog::GetTabMouseXY", g_pLogger);
1218 VECTOR_VECTOR_STRING vResult;
1220 for (VECTOR_USER_LOG_TRIAL_PTR_ITER iter = m_vpTrials.begin();
1221 iter < m_vpTrials.end();
1222 iter++)
1224 if (*iter != NULL)
1226 VECTOR_STRING vectorTrial = (*iter)->GetTabMouseXY(bReturnNormalized);
1227 vResult.push_back(vectorTrial);
1231 return vResult;
1234 // Returns a vector that contains a vector of density grids.
1235 VECTOR_VECTOR_DENSITY_GRIDS CUserLog::GetMouseDensity(int iGridSize)
1237 //CFunctionLogger f1("CUserLog::GetMouseDensity", g_pLogger);
1239 VECTOR_VECTOR_DENSITY_GRIDS vResult;
1240 for (VECTOR_USER_LOG_TRIAL_PTR_ITER iter = m_vpTrials.begin();
1241 iter < m_vpTrials.end();
1242 iter++)
1244 if (*iter != NULL)
1246 VECTOR_DENSITY_GRIDS vTrial = (*iter)->GetMouseDensity(iGridSize);
1247 vResult.push_back(vTrial);
1251 return vResult;