Skip to content
Extraits de code Groupes Projets
Valider f4ff556a rédigé par benakamoorthi's avatar benakamoorthi
Parcourir les fichiers

Refs #1077. Improved speed of the IndexedBySite archive type by optimizing the...

Refs #1077. Improved speed of the IndexedBySite archive type by optimizing the case when launching the archiving process is disabled. Instead of selecting archive IDs one at a time, IndexedBySite will now select them all at once.

Notes:
* Also added an integration test that will test whether Piwik works when archiving is disabled.
* Modified getKnownSegmentsToArchive() to cache its result as static function data instead of having ArchiveProcessing cache it as instance data.
* Changed several instance methods in ArchiveProcessing to static methods. These methods are responsible for determining if archiving is disabled.
* Removed meaningless idSite related code from TablePartitioning.


git-svn-id: http://dev.piwik.org/svn/trunk@5475 59fd770c-687e-43c8-a1e3-f5a4ff64c105
parent 6622c834
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
Affichage de
avec 446 ajouts et 80 suppressions
......@@ -321,7 +321,7 @@ abstract class Piwik_Archive
return $dataTable;
}
protected function getSegment()
public function getSegment()
{
return $this->segment;
}
......
......@@ -16,6 +16,14 @@
*/
class Piwik_Archive_Array_IndexedBySite extends Piwik_Archive_Array
{
/**
* Used to cache the name of the table that holds the data this archive.
*
* This will only be used if the archives held by this instance are instances of
* Piwik_Archive_Single.
*/
private $tableName = null;
/**
* @param Piwik_Site $oSite
* @param string $strPeriod eg. 'day' 'week' etc.
......@@ -79,12 +87,6 @@ class Piwik_Archive_Array_IndexedBySite extends Piwik_Archive_Array
private function getValues($fields)
{
foreach($this->archives as $archive)
{
$archive->setRequestedReport( is_string($fields) ? $fields : current($fields) );
$archive->prepareArchive();
}
$arrayValues = array();
foreach($this->loadValuesFromDB($fields) as $value)
{
......@@ -95,12 +97,28 @@ class Piwik_Archive_Array_IndexedBySite extends Piwik_Archive_Array
private function loadValuesFromDB($fields)
{
$requestedReport = is_string($fields) ? $fields : current($fields);
$inNames = Piwik_Common::getSqlStringFieldsArray($fields);
$archiveIds = $this->getArchiveIds();
// get the archive ids
if (!$this->getFirstArchive()->isArchivingDisabled())
{
$archiveIds = $this->getArchiveIdsAfterLaunching($requestedReport);
}
else
{
$archiveIds = $this->getArchiveIdsWithoutLaunching($requestedReport);
}
$archiveIds = implode(', ', array_filter($archiveIds));
// if no archive ids are found, avoid executing any SQL queries
if(empty($archiveIds))
{
return array();
}
// select archive data
$sql = "SELECT value, name, idarchive, idsite
FROM {$this->getNumericTableName()}
WHERE idarchive IN ( $archiveIds )
......@@ -110,12 +128,26 @@ class Piwik_Archive_Array_IndexedBySite extends Piwik_Archive_Array
private function getFirstArchive()
{
reset($this->archives);
return current($this->archives);
return reset($this->archives);
}
private function getArchiveIds()
/**
* Gets the archive id of every Single archive this archive holds. This method
* will launch the archiving process if appropriate.
*
* @param string $requestedReport The requested archive report.
* @return array
*/
private function getArchiveIdsAfterLaunching( $requestedReport )
{
// prepare archives (this will launch archiving when appropriate)
foreach($this->archives as $archive)
{
$archive->setRequestedReport( $requestedReport );
$archive->prepareArchive();
}
// collect archive ids for archives that have visits
$archiveIds = array();
foreach($this->archives as $archive)
{
......@@ -125,17 +157,83 @@ class Piwik_Archive_Array_IndexedBySite extends Piwik_Archive_Array
}
$archiveIds[] = $archive->getIdArchive();
if( $this->getNumericTableName() != $archive->archiveProcessing->getTableArchiveNumericName())
{
throw new Exception("Piwik_Archive_Array_IndexedBySite::getDataTableFromNumeric() algorithm won't work if data is stored in different tables");
}
}
return implode(', ', array_filter($archiveIds));
return $archiveIds;
}
/**
* Gets the archive id of every Single archive this archive holds. This method
* will not launch the archiving process.
*
* @param string $requestedReport The requested archive report.
* @return array
*/
private function getArchiveIdsWithoutLaunching( $requestedReport )
{
$firstArchive = $this->getFirstArchive();
$segment = $firstArchive->getSegment();
$period = $firstArchive->getPeriod();
// the flags used to tell how the archiving process for a specific archive was completed,
// if it was completed
$done = Piwik_ArchiveProcessing::getDoneStringFlagFor($segment, $period, $requestedReport);
$donePlugins = Piwik_ArchiveProcessing::getDoneStringFlagFor($segment, $period, $requestedReport, true);
// create the SQL to query every archive ID
$nameConditionSuffix = '';
if ($done != $donePlugins)
{
$nameConditionSuffix = "OR name = '$donePlugins'";
}
$nameCondition = "(name = '$done' $nameConditionSuffix) AND
(value = '".Piwik_ArchiveProcessing::DONE_OK."' OR
value = '".Piwik_ArchiveProcessing::DONE_OK_TEMPORARY."')";
$sql = "SELECT idsite,
MAX(idarchive) AS idarchive
FROM ".$this->getNumericTableName()."
WHERE date1 = ?
AND date2 = ?
AND period = ?
AND $nameCondition
AND idsite IN (".implode(',', array_keys($this->archives)).")
GROUP BY idsite";
$bind = array($period->getDateStart()->toString('Y-m-d'),
$period->getDateEnd()->toString('Y-m-d'),
$period->getId());
// execute the query and process the results.
$archiveIds = array();
foreach (Piwik_FetchAll($sql, $bind) as $row)
{
$archiveIds[] = $row['idarchive'];
}
return $archiveIds;
}
/**
* Gets the name of the database table that holds the numeric archive data for
* this archive.
*
* @return string
*/
private function getNumericTableName()
{
return $this->getFirstArchive()->archiveProcessing->getTableArchiveNumericName();
if (is_null($this->tableName))
{
$table = Piwik_ArchiveProcessing::makeNumericArchiveTable($this->getFirstArchive()->getPeriod());
$this->tableName = $table->getTableName();
}
return $this->tableName;
}
}
......@@ -569,4 +569,15 @@ class Piwik_Archive_Single extends Piwik_Archive
$this->freeBlob($name);
return $dataTableToLoad;
}
/**
* Returns true if Piwik can launch the archiving process for this archive,
* false if otherwise.
*
* @return bool
*/
public function isArchivingDisabled()
{
return Piwik_ArchiveProcessing::isArchivingDisabledFor($this->segment, $this->period);
}
}
......@@ -200,7 +200,10 @@ abstract class Piwik_ArchiveProcessing
protected $startTimestampUTC;
protected $endTimestampUTC;
protected $segmentsToProcess = null;
/**
* TODO
*/
public static $forceDisableArchiving = false;
/**
* Constructor
......@@ -315,12 +318,8 @@ abstract class Piwik_ArchiveProcessing
$dateStartLocalTimezone = $this->period->getDateStart();
$dateEndLocalTimezone = $this->period->getDateEnd();
$this->tableArchiveNumeric = new Piwik_TablePartitioning_Monthly('archive_numeric');
$this->tableArchiveNumeric->setIdSite($this->idsite);
$this->tableArchiveNumeric->setTimestamp($dateStartLocalTimezone->getTimestamp());
$this->tableArchiveBlob = new Piwik_TablePartitioning_Monthly('archive_blob');
$this->tableArchiveBlob->setIdSite($this->idsite);
$this->tableArchiveBlob->setTimestamp($dateStartLocalTimezone->getTimestamp());
$this->tableArchiveNumeric = self::makeNumericArchiveTable($this->period);
$this->tableArchiveBlob = self::makeBlobArchiveTable($this->period);
$dateStartUTC = $dateStartLocalTimezone->setTimezone($this->site->getTimezone());
$dateEndUTC = $dateEndLocalTimezone->setTimezone($this->site->getTimezone());
......@@ -333,6 +332,34 @@ abstract class Piwik_ArchiveProcessing
$db = Zend_Registry::get('db');
$this->compressBlob = $db->hasBlobDataType();
}
/**
* Utility function which creates a TablePartitioning instance for the numeric
* archive data of a given period.
*
* @param $period The time period of the archive data.
* @return Piwik_TablePartitioning_Monthly
*/
public static function makeNumericArchiveTable($period)
{
$result = new Piwik_TablePartitioning_Monthly('archive_numeric');
$result->setTimestamp($period->getDateStart()->getTimestamp());
return $result;
}
/**
* Utility function which creates a TablePartitioning instance for the blob
* archive data of a given period.
*
* @param $period The time period of the archive data.
* @return Piwik_TablePartitioning_Monthly
*/
public static function makeBlobArchiveTable($period)
{
$result = new Piwik_TablePartitioning_Monthly('archive_blob');
$result->setTimestamp($period->getDateStart()->getTimestamp());
return $result;
}
public function getStartDatetimeUTC()
{
......@@ -452,22 +479,41 @@ abstract class Piwik_ArchiveProcessing
abstract public function isThereSomeVisits();
protected function getDoneStringFlag($flagArchiveAsAllPlugins = false)
/**
* Returns the name of the archive field used to tell the status of an archive, (ie,
* whether the archive was created succesfully or not).
*
* @param bool $flagArchiveAsAllPlugins
* @return string
*/
public function getDoneStringFlag($flagArchiveAsAllPlugins = false)
{
$segment = $this->getSegment()->getHash();
if(!$this->shouldProcessReportsAllPlugins($this->getSegment(), $this->period))
return self::getDoneStringFlagFor(
$this->getSegment(), $this->period, $this->getRequestedReport(), $flagArchiveAsAllPlugins);
}
/**
* Returns the name of the archive field used to tell the status of an archive, (ie,
* whether the archive was created succesfully or not).
*
* @param bool $flagArchiveAsAllPlugins
* @return string
*/
public static function getDoneStringFlagFor($segment, $period, $requestedReport, $flagArchiveAsAllPlugins = false)
{
$segmentHash = $segment->getHash();
if(!self::shouldProcessReportsAllPluginsFor($segment, $period))
{
$pluginProcessed = self::getPluginBeingProcessed($this->getRequestedReport());
// Piwik::log("Plugin processed: $pluginProcessed");
$pluginProcessed = self::getPluginBeingProcessed($requestedReport);
if(!Piwik_PluginsManager::getInstance()->isPluginLoaded($pluginProcessed)
|| $flagArchiveAsAllPlugins
)
{
$pluginProcessed = 'all';
}
$segment .= '.'.$pluginProcessed;
$segmentHash .= '.'.$pluginProcessed;
}
return 'done' . $segment;
return 'done' . $segmentHash;
}
/**
......@@ -880,14 +926,19 @@ abstract class Piwik_ArchiveProcessing
*/
public function isArchivingDisabled()
{
$processOneReportOnly = !$this->shouldProcessReportsAllPlugins($this->getSegment(), $this->period);
return self::isArchivingDisabledFor($this->getSegment(), $this->period);
}
public static function isArchivingDisabledFor($segment, $period)
{
$processOneReportOnly = !self::shouldProcessReportsAllPluginsFor($segment, $period);
if($processOneReportOnly)
{
// When there is a segment, archiving is not necessary allowed
// If browser archiving is allowed, then archiving is enabled
// if browser archiving is not allowed, then archiving is disabled
if(!$this->getSegment()->isEmpty()
&& !$this->isRequestAuthorizedToArchive()
if(!$segment->isEmpty()
&& !self::isRequestAuthorizedToArchive()
&& Zend_Registry::get('config')->General->browser_archiving_disabled_enforce
)
{
......@@ -896,16 +947,17 @@ abstract class Piwik_ArchiveProcessing
}
return false;
}
$isDisabled = !$this->isRequestAuthorizedToArchive();
$isDisabled = !self::isRequestAuthorizedToArchive();
return $isDisabled;
}
protected function isRequestAuthorizedToArchive()
protected static function isRequestAuthorizedToArchive()
{
return self::isBrowserTriggerArchivingEnabled()
|| Piwik_Common::isPhpCliMode()
|| (Piwik::isUserIsSuperUser()
&& Piwik_Common::isArchivePhpTriggered())
return !self::$forceDisableArchiving &&
(self::isBrowserTriggerArchivingEnabled()
|| Piwik_Common::isPhpCliMode()
|| (Piwik::isUserIsSuperUser()
&& Piwik_Common::isArchivePhpTriggered()))
;
}
......@@ -915,29 +967,31 @@ abstract class Piwik_ArchiveProcessing
* - there is a segment that is part of the preprocessed [Segments] list
*/
protected function shouldProcessReportsAllPlugins($segment, $period)
{
return self::shouldProcessReportsAllPluginsFor($segment, $period);
}
protected static function shouldProcessReportsAllPluginsFor($segment, $period)
{
if($segment->isEmpty() && $period->getLabel() != 'range')
{
return true;
}
if(is_null($this->segmentsToProcess))
{
$this->segmentsToProcess = Piwik::getKnownSegmentsToArchive();
}
if(!empty($this->segmentsToProcess))
$segmentsToProcess = Piwik::getKnownSegmentsToArchive();
if(!empty($segmentsToProcess))
{
// If the requested segment is one of the segments to pre-process
// we ensure that any call to the API will trigger archiving of all reports for this segment
$segment = $this->getSegment()->getString();
if(in_array($segment, $this->segmentsToProcess))
$segment = $segment->getString();
if(in_array($segment, $segmentsToProcess))
{
return true;
}
}
return false;
}
/**
* When a segment is set, we shall only process the requested report (no more).
* The requested data set will return a lot faster if we only process these reports rather than all plugins.
......
......@@ -1552,14 +1552,21 @@ class Piwik
Zend_Registry::get('config')->General->autocomplete_min_sites);
return (int)$count;
}
/**
* Segments to pre-process
*/
static public function getKnownSegmentsToArchive()
{
$segments = Zend_Registry::get('config')->Segments->toArray();
return isset($segments['Segments']) ? $segments['Segments'] : '';
static $cachedResult = null;
if (is_null($cachedResult))
{
$segments = Zend_Registry::get('config')->Segments->toArray();
$cachedResult = isset($segments['Segments']) ? $segments['Segments'] : '';
}
return $cachedResult;
}
/*
......
......@@ -23,8 +23,7 @@ abstract class Piwik_TablePartitioning
protected $tableName = null;
protected $generatedTableName = null;
protected $timestamp = null;
protected $idSite = null;
static public $tablesAlreadyInstalled = null;
public function __construct( $tableName )
......@@ -40,11 +39,6 @@ abstract class Piwik_TablePartitioning
$this->generatedTableName = null;
$this->getTableName();
}
public function setIdSite($idSite)
{
$this->idSite = $idSite;
}
public function getTableName()
{
......
......@@ -580,8 +580,38 @@ class Test_Piwik_Integration_Main extends Test_Integration
private function doTest_TwoVisitors_twoWebsites_differentDays(
$function, $apiToCall, $allowConversions = false, $testGetProcessedReport = true)
{
// tests run in UTC, the Tracker in UTC
$dateTime = '2010-01-03 11:22:33';
$idSites = $this->setup_TwoVisitors_twoWebsites_differentDays($dateTime, $allowConversions);
$this->setApiToCall($apiToCall);
$periods = array('day', 'week', 'month', 'year');
// Request data for the last 6 periods and idSite=all
$this->callGetApiCompareOutput($function, 'xml', $allSites = 'all', $dateTime, $periods, $setDateLastN = true);
// Request data for the last 6 periods and idSite=1
$this->callGetApiCompareOutput($function.'_idSiteOne_', 'xml', $idSites[0], $dateTime, array('day','week','month','year'), $setDateLastN = true);
// We also test a single period to check that this use case (Reports per idSite in the response) works
$this->setApiToCall(array('VisitsSummary.get', 'Goals.get'));
$this->callGetApiCompareOutput($function . '_NotLastNPeriods', 'xml', $allSites = 'all', $dateTime, array('day', 'month'), $setDateLastN = false);
// testing metadata API for multiple periods
$this->setApiNotToCall(array());
if ($testGetProcessedReport)
{
$this->setApiToCall( array('API.getProcessedReport' ) );
}
$apiToCall = array_diff($apiToCall, array('Actions.getPageTitle', 'Actions.getPageUrl'));
foreach($apiToCall as $api)
{
list($apiModule, $apiAction) = explode(".", $api);
$this->callGetApiCompareOutput($function . '_'.$api.'_firstSite_lastN', 'xml', $idSites[0], $dateTime, $periods = array('day'), $setDateLastN = true, $language = false, $segment = false, $visitorId = false, $abandonedCarts = false, $idGoal = false, $apiModule, $apiAction);
}
}
private function setup_TwoVisitors_twoWebsites_differentDays($dateTime, $allowConversions = false)
{
// tests run in UTC, the Tracker in UTC
$idSite = $this->createWebsite($dateTime);
$idSite2 = $this->createWebsite($dateTime);
if ($allowConversions)
......@@ -589,7 +619,6 @@ class Test_Piwik_Integration_Main extends Test_Integration
Piwik_Goals_API::getInstance()->addGoal($idSite, 'all', 'url', 'http', 'contains');
Piwik_Goals_API::getInstance()->addGoal($idSite2, 'all', 'url', 'http', 'contains');
}
$this->setApiToCall($apiToCall);
// -
// First visitor on Idsite 1: two page views
$datetimeSpanOverTwoDays = '2010-01-03 23:55:00';
......@@ -659,30 +688,35 @@ class Test_Piwik_Integration_Main extends Test_Integration
// $t2->setUrlReferrer('http://www.baidu.com/s?wd=%D0%C2+%CE%C5&n=2');
// $t2->setUrl('http://example2.com/home');
// $this->checkResponse($t2->doTrackPageView('I\'m a returning visitor...'));
$periods = array('day', 'week', 'month', 'year');
// Request data for the last 6 periods and idSite=all
$this->callGetApiCompareOutput($function, 'xml', $allSites = 'all', $dateTime, $periods, $setDateLastN = true);
// Request data for the last 6 periods and idSite=1
$this->callGetApiCompareOutput($function.'_idSiteOne_', 'xml', $idSite, $dateTime, array('day','week','month','year'), $setDateLastN = true);
// We also test a single period to check that this use case (Reports per idSite in the response) works
$this->setApiToCall(array('VisitsSummary.get', 'Goals.get'));
$this->callGetApiCompareOutput($function . '_NotLastNPeriods', 'xml', $allSites = 'all', $dateTime, array('day', 'month'), $setDateLastN = false);
return array($idSite, $idSite2);
}
public function test_TwoVisitors_twoWebsites_differentDays_ArchivingDisabled()
{
$apiToCall = array('VisitsSummary.get');
$dateTime = '2010-01-03 11:22:33';
$periods = array('day', 'week', 'month', 'year');
$idSites = $this->setup_TwoVisitors_twoWebsites_differentDays($dateTime, false);
$this->setApiToCall($apiToCall);
// testing metadata API for multiple periods
$this->setApiNotToCall(array());
if ($testGetProcessedReport)
{
$this->setApiToCall( array('API.getProcessedReport' ) );
}
$apiToCall = array_diff($apiToCall, array('Actions.getPageTitle', 'Actions.getPageUrl'));
foreach($apiToCall as $api)
{
list($apiModule, $apiAction) = explode(".", $api);
$this->callGetApiCompareOutput($function . '_'.$api.'_firstSite_lastN', 'xml', $idSite, $dateTime, $periods = array('day'), $setDateLastN = true, $language = false, $segment = false, $visitorId = false, $abandonedCarts = false, $idGoal = false, $apiModule, $apiAction);
}
// disable archiving & check that there is no archive data
Piwik_ArchiveProcessing::$forceDisableArchiving = true;
$this->callGetApiCompareOutput(__FUNCTION__ . '_disabledBefore', 'xml', $allSites = 'all', $dateTime, $periods);
// re-enable archiving & check the output
Piwik_ArchiveProcessing::$forceDisableArchiving = false;
$this->callGetApiCompareOutput(__FUNCTION__ . '_enabled', 'xml', $allSites = 'all', $dateTime, $periods);
// disable archiving again & check the output
Piwik_ArchiveProcessing::$forceDisableArchiving = true;
$this->callGetApiCompareOutput(__FUNCTION__ . '_disabledAfter', 'xml', $allSites = 'all', $dateTime, $periods);
// re-enable archiving
Piwik_ArchiveProcessing::$forceDisableArchiving = false;
}
private function doTest_twoVisitsWithCustomVariables($dateTime, $width=1111, $height=222)
......
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1">
<bounce_count>1</bounce_count>
<max_actions>1</max_actions>
<nb_actions>1</nb_actions>
<nb_uniq_visitors>1</nb_uniq_visitors>
<nb_visits>1</nb_visits>
<bounce_rate>100%</bounce_rate>
<nb_actions_per_visit>1</nb_actions_per_visit>
<avg_time_on_site>0</avg_time_on_site>
</result>
<result idSite="2" />
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1">
<bounce_count>1</bounce_count>
<max_actions>5</max_actions>
<nb_actions>8</nb_actions>
<nb_uniq_visitors>2</nb_uniq_visitors>
<nb_visits>3</nb_visits>
<sum_visit_length>1262</sum_visit_length>
<bounce_rate>33%</bounce_rate>
<nb_actions_per_visit>2.7</nb_actions_per_visit>
<avg_time_on_site>421</avg_time_on_site>
</result>
<result idSite="2">
<max_actions>2</max_actions>
<nb_actions>2</nb_actions>
<nb_uniq_visitors>1</nb_uniq_visitors>
<nb_visits>1</nb_visits>
<sum_visit_length>1</sum_visit_length>
<bounce_rate>0%</bounce_rate>
<nb_actions_per_visit>2</nb_actions_per_visit>
<avg_time_on_site>1</avg_time_on_site>
</result>
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1">
<nb_uniq_visitors>1</nb_uniq_visitors>
<nb_visits>1</nb_visits>
<nb_actions>1</nb_actions>
<bounce_count>1</bounce_count>
<max_actions>1</max_actions>
<bounce_rate>100%</bounce_rate>
<nb_actions_per_visit>1</nb_actions_per_visit>
<avg_time_on_site>0</avg_time_on_site>
</result>
<result idSite="2" />
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1">
<bounce_count>1</bounce_count>
<max_actions>5</max_actions>
<nb_actions>8</nb_actions>
<nb_visits>3</nb_visits>
<sum_visit_length>1262</sum_visit_length>
<bounce_rate>33%</bounce_rate>
<nb_actions_per_visit>2.7</nb_actions_per_visit>
<avg_time_on_site>421</avg_time_on_site>
</result>
<result idSite="2">
<max_actions>2</max_actions>
<nb_actions>2</nb_actions>
<nb_visits>1</nb_visits>
<sum_visit_length>1</sum_visit_length>
<bounce_rate>0%</bounce_rate>
<nb_actions_per_visit>2</nb_actions_per_visit>
<avg_time_on_site>1</avg_time_on_site>
</result>
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1" />
<result idSite="2" />
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1" />
<result idSite="2" />
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1" />
<result idSite="2" />
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1" />
<result idSite="2" />
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1">
<bounce_count>1</bounce_count>
<max_actions>1</max_actions>
<nb_actions>1</nb_actions>
<nb_uniq_visitors>1</nb_uniq_visitors>
<nb_visits>1</nb_visits>
<bounce_rate>100%</bounce_rate>
<nb_actions_per_visit>1</nb_actions_per_visit>
<avg_time_on_site>0</avg_time_on_site>
</result>
<result idSite="2" />
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1">
<bounce_count>1</bounce_count>
<max_actions>5</max_actions>
<nb_actions>8</nb_actions>
<nb_uniq_visitors>2</nb_uniq_visitors>
<nb_visits>3</nb_visits>
<sum_visit_length>1262</sum_visit_length>
<bounce_rate>33%</bounce_rate>
<nb_actions_per_visit>2.7</nb_actions_per_visit>
<avg_time_on_site>421</avg_time_on_site>
</result>
<result idSite="2">
<max_actions>2</max_actions>
<nb_actions>2</nb_actions>
<nb_uniq_visitors>1</nb_uniq_visitors>
<nb_visits>1</nb_visits>
<sum_visit_length>1</sum_visit_length>
<bounce_rate>0%</bounce_rate>
<nb_actions_per_visit>2</nb_actions_per_visit>
<avg_time_on_site>1</avg_time_on_site>
</result>
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1">
<bounce_count>1</bounce_count>
<max_actions>1</max_actions>
<nb_actions>1</nb_actions>
<nb_uniq_visitors>1</nb_uniq_visitors>
<nb_visits>1</nb_visits>
<bounce_rate>100%</bounce_rate>
<nb_actions_per_visit>1</nb_actions_per_visit>
<avg_time_on_site>0</avg_time_on_site>
</result>
<result idSite="2" />
</results>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?>
<results>
<result idSite="1">
<bounce_count>1</bounce_count>
<max_actions>5</max_actions>
<nb_actions>8</nb_actions>
<nb_visits>3</nb_visits>
<sum_visit_length>1262</sum_visit_length>
<bounce_rate>33%</bounce_rate>
<nb_actions_per_visit>2.7</nb_actions_per_visit>
<avg_time_on_site>421</avg_time_on_site>
</result>
<result idSite="2">
<max_actions>2</max_actions>
<nb_actions>2</nb_actions>
<nb_visits>1</nb_visits>
<sum_visit_length>1</sum_visit_length>
<bounce_rate>0%</bounce_rate>
<nb_actions_per_visit>2</nb_actions_per_visit>
<avg_time_on_site>1</avg_time_on_site>
</result>
</results>
\ No newline at end of file
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter