/** * Gets the list of farms * * @param string $query optional Search query * @return array Retuens array of farms */ private function getFarmsList($query = null) { $farms = []; $collection = $this->getContainer()->analytics->usage->findFarmsByKey($this->environment->id, $query); if ($collection->count()) { $iterator = ChartPeriodIterator::create('month', gmdate('Y-m-01'), null, 'UTC'); $criteria = ['envId' => $this->environment->id]; //It calculates usage for all provided cost centres $usage = $this->getContainer()->analytics->usage->getFarmData($this->environment->clientId, $criteria, $iterator->getStart(), $iterator->getEnd(), [TagEntity::TAG_ID_FARM, TagEntity::TAG_ID_FARM_ROLE, TagEntity::TAG_ID_PLATFORM]); //It calculates usage for previous period same days $prevusage = $this->getContainer()->analytics->usage->getFarmData($this->environment->clientId, $criteria, $iterator->getPreviousStart(), $iterator->getPreviousEnd(), [TagEntity::TAG_ID_FARM]); foreach ($collection as $dbFarm) { /* @var $dbFarm \DBFarm */ $totalCost = round(isset($usage['data'][$dbFarm->ID]) ? $usage['data'][$dbFarm->ID]['cost'] : 0, 2); $farms[$dbFarm->ID] = $this->getFarmData($dbFarm); if (isset($usage['data'][$dbFarm->ID]['data'])) { $farms[$dbFarm->ID]['topSpender'] = $this->getFarmRoleTopSpender($usage['data'][$dbFarm->ID]['data']); } else { $farms[$dbFarm->ID]['topSpender'] = null; } $prevCost = round(isset($prevusage['data'][$dbFarm->ID]) ? $prevusage['data'][$dbFarm->ID]['cost'] : 0, 2); $farms[$dbFarm->ID] = $this->getWrappedUsageData(['farmId' => $dbFarm->ID, 'iterator' => $iterator, 'usage' => $totalCost, 'prevusage' => $prevCost]) + $farms[$dbFarm->ID]; } } return array_values($farms); }
/** * Gets events list * * @param string $mode Chart mode * @param string $date The requested date time * @param string $start Start date of the current period * @param string $end optional End date of the period * @param string $projectId optional Project id * @throws InvalidArgumentException */ public function xGetTimelineEventsAction($mode, $date, $start, $end = null, $projectId = null) { if (!preg_match('/^[\\d]{4}-[\\d]{2}-[\\d]{2} [\\d]{2}:00$/', $date)) { throw new InvalidArgumentException(sprintf("Invalid date:%s. 'YYYY-MM-DD HH:00' is expected.", strip_tags($date))); } $analytics = $this->getContainer()->analytics; $iterator = ChartPeriodIterator::create($mode, $start, $end ?: null, 'UTC'); $pointPosition = $iterator->searchPoint($date); if ($pointPosition !== false) { $chartPoint = $iterator->current(); $startDate = $chartPoint->dt; if ($chartPoint->isLastPoint) { $endDate = $chartPoint->end; } else { $iterator->next(); $endDate = $iterator->current()->dt; $endDate->modify("-1 second"); } } if (!isset($startDate)) { throw new OutOfBoundsException(sprintf("Date %s is inconsistent with the interval object", $date)); } $entities = $analytics->events->get($startDate, $endDate, ['envId' => $this->getEnvironmentId(), 'accountId' => $this->user->getAccountId(), 'projectId' => $projectId]); $data = []; foreach ($entities as $entity) { $data[] = ['dtime' => $entity->dtime->format('Y-m-d H:i:s'), 'description' => $entity->description, 'type' => $entity->eventType]; } $this->response->data(['data' => $data]); }
/** * Gets a list of environments by key * * @param string $query Search query * @return array Returns array of environments */ private function getEnvironmentsList($query = null) { $envs = []; $environments = $this->user->getEnvironments($query); if (count($environments) > 0) { $iterator = ChartPeriodIterator::create('month', gmdate('Y-m-01'), null, 'UTC'); //It calculates usage for all provided enviroments $usage = $this->getContainer()->analytics->usage->getFarmData($this->user->getAccountId(), [], $iterator->getStart(), $iterator->getEnd(), [TagEntity::TAG_ID_ENVIRONMENT, TagEntity::TAG_ID_FARM]); //It calculates usage for previous period same days $prevusage = $this->getContainer()->analytics->usage->getFarmData($this->user->getAccountId(), [], $iterator->getPreviousStart(), $iterator->getPreviousEnd(), [TagEntity::TAG_ID_ENVIRONMENT, TagEntity::TAG_ID_FARM]); foreach ($environments as $env) { if (isset($usage['data'][$env['id']]['data'])) { $envs[$env['id']]['topSpender'] = $this->getFarmTopSpender($usage['data'][$env['id']]['data']); } else { $envs[$env['id']]['topSpender'] = null; } $envs[$env['id']]['name'] = $env['name']; $envs[$env['id']]['envId'] = $env['id']; $ccId = \Scalr_Environment::init()->loadById($env['id'])->getPlatformConfigValue(\Scalr_Environment::SETTING_CC_ID); if (!empty($ccId)) { $envs[$env['id']]['ccId'] = $ccId; $envs[$env['id']]['ccName'] = CostCentreEntity::findPk($ccId)->name; } $totalCost = round(isset($usage['data'][$env['id']]) ? $usage['data'][$env['id']]['cost'] : 0, 2); $prevCost = round(isset($prevusage['data'][$env['id']]) ? $prevusage['data'][$env['id']]['cost'] : 0, 2); $envs[$env['id']] = $this->getWrappedUsageData(['iterator' => $iterator, 'usage' => $totalCost, 'prevusage' => $prevCost]) + $envs[$env['id']]; } } return array_values($envs); }
/** * @test * @dataProvider providerConstructor */ public function testConstructor($mode, $start, $end, $fixture) { $iterator = ChartPeriodIterator::create($mode, $start, $end); $this->assertEquals($fixture['interval'], $iterator->getInterval()); $this->assertEquals($fixture['startDate'], $iterator->getStart()->format('Y-m-d'), 'Start date does not match.'); $this->assertEquals($fixture['endDate'], $iterator->getEnd()->format('Y-m-d'), 'End date does not match'); $this->assertEquals($fixture['prevStartDate'], $iterator->getPreviousStart()->format('Y-m-d'), 'Previous period Start date does not match.'); $this->assertEquals($fixture['prevEndDate'], $iterator->getPreviousEnd()->format('Y-m-d'), 'Previous period End date does not match.'); foreach ($iterator as $chartPoint) { /* @var $chartPoint ChartPointInfo */ $this->assertTrue(isset($fixture['keys'][$chartPoint->i]), sprintf("Fixture with number %d is not expected.", $chartPoint->i)); $this->assertEquals($fixture['keys'][$chartPoint->i], $chartPoint->key, "Keys does not match."); } }
/** * Gets cost centre properties and parameters * * @param CostCentreEntity $cc Cost centre entity * @param string $calculate optional Whether response should be adjusted with cost usage data * @return array Returns cost centre properties and parameters */ private function getCostCenterData(CostCentreEntity $cc, $calculate = false) { $ret = array('ccId' => $cc->ccId, 'name' => $cc->name, 'billingCode' => $cc->getProperty(CostCentrePropertyEntity::NAME_BILLING_CODE), 'description' => $cc->getProperty(CostCentrePropertyEntity::NAME_DESCRIPTION), 'leadEmail' => $cc->getProperty(CostCentrePropertyEntity::NAME_LEAD_EMAIL), 'locked' => $cc->getProperty(CostCentrePropertyEntity::NAME_LOCKED) ? 1 : 0, 'created' => $cc->created->format('Y-m-d'), 'createdByEmail' => $cc->createdByEmail, 'archived' => $cc->archived, 'envCount' => count($cc->getEnvironmentsList()), 'projectsCount' => count($cc->getProjects())); if ($calculate) { $iterator = ChartPeriodIterator::create('month', gmdate('Y-m-01'), null, 'UTC'); $usage = $this->getContainer()->analytics->usage->get(['ccId' => $cc->ccId], $iterator->getStart(), $iterator->getEnd()); //It calculates usage for previous period same days $prevusage = $this->getContainer()->analytics->usage->get(['ccId' => $cc->ccId], $iterator->getPreviousStart(), $iterator->getPreviousEnd()); $ret = $this->getWrappedUsageData(['ccId' => $cc->ccId, 'iterator' => $iterator, 'usage' => $usage['cost'], 'prevusage' => $prevusage['cost']]) + $ret; } return $ret; }
/** * Gets project properties and parameters * * @param ProjectEntity $projectEntity Project entity * @param string $calculate optional Whether response should be adjusted with cost usage data * @return array Returns cost centre properties and parameters */ private function getProjectData(ProjectEntity $projectEntity, $calculate = false) { $ret = array('ccId' => $projectEntity->ccId, 'ccName' => $projectEntity->getCostCenter() !== null ? $projectEntity->getCostCenter()->name : null, 'projectId' => $projectEntity->projectId, 'name' => $projectEntity->name, 'billingCode' => $projectEntity->getProperty(ProjectPropertyEntity::NAME_BILLING_CODE), 'description' => $projectEntity->getProperty(ProjectPropertyEntity::NAME_DESCRIPTION), 'leadEmail' => $projectEntity->getProperty(ProjectPropertyEntity::NAME_LEAD_EMAIL), 'created' => $projectEntity->created->format('Y-m-d'), 'createdByEmail' => $projectEntity->createdByEmail, 'archived' => $projectEntity->archived, 'shared' => $projectEntity->shared, 'farmsCount' => count($projectEntity->getFarmsList())); if (!empty($projectEntity->accountId) && $projectEntity->shared === ProjectEntity::SHARED_WITHIN_ACCOUNT) { $ret['accountId'] = $projectEntity->accountId; $ret['accountName'] = Scalr_Account::init()->loadById($projectEntity->accountId)->name; } elseif (!empty($projectEntity->envId) && $projectEntity->shared === ProjectEntity::SHARED_WITHIN_ENV) { $ret['accountId'] = $projectEntity->accountId; $ret['accountName'] = Scalr_Account::init()->loadById($projectEntity->accountId)->name; $ret['envId'] = $projectEntity->envId; $ret['envName'] = Scalr_Environment::init()->loadById($projectEntity->envId)->name; } if ($calculate) { $iterator = ChartPeriodIterator::create('month', gmdate('Y-m-01'), null, 'UTC'); $usage = $this->getContainer()->analytics->usage->get(['projectId' => $ret['projectId']], $iterator->getStart(), $iterator->getEnd()); //It calculates usage for previous period same days $prevusage = $this->getContainer()->analytics->usage->get(['projectId' => $ret['projectId']], $iterator->getPreviousStart(), $iterator->getPreviousEnd()); $ret = $this->getWrappedUsageData(['ccId' => $ret['ccId'], 'projectId' => $ret['projectId'], 'iterator' => $iterator, 'usage' => $usage['cost'], 'prevusage' => $prevusage['cost']]) + $ret; } return $ret; }
/** * Gets a list of projects by key * * @param string $query Search query * @return array Returns array of projects */ private function getProjectsList($query = null) { $projects = []; $collection = $this->getContainer()->analytics->projects->findByKey($query, ['accountId' => $this->user->getAccountId()], true); //Select identifiers of all projects assigned to farms from the account $assignedProjects = []; $rs = $this->db->Execute("\n SELECT DISTINCT fs.value\n FROM farms f\n JOIN farm_settings fs ON f.id = fs.farmid\n WHERE fs.name = ?\n AND f.clientid = ?\n ", [Entity\FarmSetting::PROJECT_ID, $this->user->getAccountId()]); while ($rec = $rs->fetchRow()) { $assignedProjects[$rec['value']] = true; } //Adjusts missing projects. //This is going to be very rare event. foreach ($collection as $projectEntity) { if (isset($assignedProjects[$projectEntity->projectId])) { unset($assignedProjects[$projectEntity->projectId]); } } foreach ($assignedProjects as $projectId => $v) { $project = ProjectEntity::findPk($projectId); /* @var $project ProjectEntity */ $projectBillingCode = $project->getProperty(ProjectPropertyEntity::NAME_BILLING_CODE); if (empty($query) || (stripos($project->name, $query) !== false || stripos($projectBillingCode, $query) !== false)) { $collection->append($project); } } if ($collection->count()) { $iterator = ChartPeriodIterator::create('month', gmdate('Y-m-01'), null, 'UTC'); //It calculates usage for all provided cost centres $usage = $this->getContainer()->analytics->usage->getFarmData($this->user->getAccountId(), [], $iterator->getStart(), $iterator->getEnd(), [TagEntity::TAG_ID_PROJECT]); //It calculates usage for previous period same days $prevusage = $this->getContainer()->analytics->usage->getFarmData($this->user->getAccountId(), [], $iterator->getPreviousStart(), $iterator->getPreviousEnd(), [TagEntity::TAG_ID_PROJECT]); foreach ($collection as $projectEntity) { /* @var $projectEntity \Scalr\Stats\CostAnalytics\Entity\ProjectEntity */ $totalCost = round(isset($usage['data'][$projectEntity->projectId]) ? $usage['data'][$projectEntity->projectId]['cost'] : 0, 2); //Archived projects are excluded only when there aren't any usage for this month and //query filter key has not been provided. if (($query === null || $query === '') && $projectEntity->archived && $totalCost < 0.01) { continue; } $projects[$projectEntity->projectId] = $this->getProjectData($projectEntity); $prevCost = round(isset($prevusage['data'][$projectEntity->projectId]) ? $prevusage['data'][$projectEntity->projectId]['cost'] : 0, 2); $projects[$projectEntity->projectId] = $this->getWrappedUsageData(['projectId' => $projectEntity->projectId, 'iterator' => $iterator, 'usage' => $totalCost, 'prevusage' => $prevCost]) + $projects[$projectEntity->projectId]; } } return array_values($projects); }
/** * Gets a list of projects by key * * @param string $query Search query * @return array Returns array of projects */ private function getProjectsList($query = null) { $projects = []; $collection = $this->getContainer()->analytics->projects->getAccountProjects($this->user->getAccountId(), $query); if ($collection->count()) { $iterator = ChartPeriodIterator::create('month', gmdate('Y-m-01'), null, 'UTC'); //It calculates usage for all provided cost centres $usage = $this->getContainer()->analytics->usage->getFarmData($this->user->getAccountId(), [], $iterator->getStart(), $iterator->getEnd(), [TagEntity::TAG_ID_PROJECT]); //It calculates usage for previous period same days $prevusage = $this->getContainer()->analytics->usage->getFarmData($this->user->getAccountId(), [], $iterator->getPreviousStart(), $iterator->getPreviousEnd(), [TagEntity::TAG_ID_PROJECT]); foreach ($collection as $projectEntity) { /* @var $projectEntity \Scalr\Stats\CostAnalytics\Entity\ProjectEntity */ $totalCost = round(isset($usage['data'][$projectEntity->projectId]) ? $usage['data'][$projectEntity->projectId]['cost'] : 0, 2); //Archived projects are excluded only when there aren't any usage for this month and //query filter key has not been provided. if (($query === null || $query === '') && $projectEntity->archived && $totalCost < 0.01) { continue; } $projects[$projectEntity->projectId] = $this->getProjectData($projectEntity); $prevCost = round(isset($prevusage['data'][$projectEntity->projectId]) ? $prevusage['data'][$projectEntity->projectId]['cost'] : 0, 2); $projects[$projectEntity->projectId] = $this->getWrappedUsageData(['projectId' => $projectEntity->projectId, 'iterator' => $iterator, 'usage' => $totalCost, 'prevusage' => $prevCost]) + $projects[$projectEntity->projectId]; } } return array_values($projects); }
/** * Get period data for one point on chart * * * * @param int $accountId Identifier of the Account * @param string $projectId optional Identifier of the Project * @param int $envId optional Identifier of the Environment * @param int|array $farmId optional Identifier of the Farm, or the list of the farms which should be excluded * @param int $farmRoleId optional Identifier of the Farm Role Id * @param string $mode The mode (chart) * @param string $date The UTC date within period ('Y-m-d H:00') * @param string $start The start date of the period in UTC ('Y-m-d') * @param string $end The end date of the period in UTC ('Y-m-d') * @return array */ public function getFarmPointData($accountId, $projectId = null, $envId = null, $farmId = null, $farmRoleId = null, $mode, $date, $start, $end) { $criteria = []; if ($envId) { $criteria['envId'] = $envId; } if ($farmId) { $criteria['farmId'] = $farmId; } if ($farmRoleId) { $criteria['farmRoleId'] = $farmRoleId; } if ($projectId) { $criteria['projectId'] = $projectId; } $chartPoint = null; if (!empty($date)) { $iterator = ChartPeriodIterator::create($mode, $start, $end ?: null, 'UTC'); //Interval which is used in the database query for grouping $queryInterval = preg_replace('/^1 /', '', $iterator->getInterval()); //Finds the key for current label foreach ($iterator as $chartPoint) { /* @var $chartPoint \Scalr\Stats\CostAnalytics\ChartPointInfo */ if ($chartPoint->dt->format('Y-m-d H:00') == $date) { break; } } } else { $queryInterval = $mode == 'day' ? 'hour' : 'day'; } if ($chartPoint === null) { $intervalStart = new DateTime($start . " 00:00:00", new DateTimeZone('UTC')); $intervalEnd = new DateTime($end . " 23:59:59", new DateTimeZone('UTC')); } else { $intervalStart = $chartPoint->dt; if ($chartPoint->isLastPoint) { $intervalEnd = new DateTime($end . " 23:59:59", new DateTimeZone('UTC')); } else { $iterator->next(); $intervalEnd = $iterator->current()->dt->modify('-1 second'); } } if ($queryInterval == 'hour') { $criteria['hourly'] = true; } //Requests data for the specified period $usg = (new AggregationCollection(['distributionType', 'usageType' => ['name', 'displayName'], 'usageItem' => ['envId', 'platform', 'cloudLocation', 'id']], ['cost' => 'sum', 'minUsage' => 'min', 'maxUsage' => 'max', 'usageHours' => 'sum', 'workingHours' => 'sum']))->load($this->getFarmData($accountId, $criteria, $intervalStart, $intervalEnd, ['distributionType', 'usageType', 'usageItem'], true))->calculatePercentage(); $distrTypes = []; if (!empty($usg['data'])) { foreach ($usg['data'] as $distrType => $distrUsage) { $usageTypesData = []; $distrTypesDataPoint = $this->getDetailedPointDataArray($distrType, $distrType, $distrUsage, null, null); foreach ($distrUsage['data'] as $usageType => $uv) { $usageTypeDataPoint = ['id' => $usageType, 'name' => $uv['name'], 'displayName' => $uv['displayName'], 'measure' => $this->getMeasure($uv['name'])]; if (!empty($uv['data'])) { $usageItemsData = []; foreach ($uv['data'] as $usageItem => $iv) { if ($uv['name'] == UsageTypeEntity::NAME_COMPUTE_BOX_USAGE) { $usageItemName = $this->getInstanceTypeName($usageItem, $iv['envId'], $iv['platform'], $iv['cloudLocation']); } else { $usageItemName = $usageItem; } $usageItemDataPoint = $this->getDetailedPointDataArray($iv['id'], $usageItemName, $iv, null, null); $usageItemDataPoint['costPct'] = !empty($usg['cost']) ? round($usageItemDataPoint['cost'] / $usg['cost'] * 100) : 0; $usageItemDataPoint['min'] = $iv['minUsage']; $usageItemDataPoint['max'] = $iv['maxUsage']; $usageItemDataPoint['avg'] = !empty($iv['workingHours']) ? round($iv['usageHours'] / $iv['workingHours']) : 0; $usageItemDataPoint['hours'] = $iv['usageHours']; $usageItemDataPoint['displayHours'] = $this->getDisplayHours($iv['usageHours']); $usageItemsData[] = $usageItemDataPoint; } $usageTypeDataPoint['usageItems'] = $usageItemsData; } $usageTypesData[] = $usageTypeDataPoint; } $distrTypesDataPoint['usageTypes'] = $usageTypesData; $distrTypes[] = $distrTypesDataPoint; } } return $distrTypes; }
/** * Returns iterator for current quarter * * @return Iterator\ChartQuarterlyIterator */ public function getCurrentQuarterIterator() { $quarters = new Quarters(SettingEntity::getQuarters()); $currentQuarter = $quarters->getQuarterForDate(new \DateTime('now', new \DateTimeZone('UTC'))); $currentYear = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y'); if ($currentQuarter === 1) { $quarter = 4; $year = $currentYear - 1; } else { $quarter = $currentQuarter - 1; $year = $currentYear; } $date = $quarters->getPeriodForQuarter($quarter, $year); $iterator = ChartPeriodIterator::create('quarter', $date->start, $date->end, 'UTC'); return $iterator; }
/** * Gets period data for top farms * * @param int $accountId The current client id * @param array $allowedEnvs Array of allowed environments' ids for current user * @param string $mode The mode (week, month, quarter, year) * @param string $startDate The start date of the period in UTC ('Y-m-d') * @param string $endDate The end date of the period in UTC ('Y-m-d') * @param int $farmCount Top farms count * @return array Returns cost analytics data for environment scope */ public function getTopFarmsPeriodData($accountId, array $allowedEnvs, $mode, $startDate, $endDate, $farmCount = 5) { $utcTz = new DateTimeZone('UTC'); $iterator = ChartPeriodIterator::create($mode, new DateTime($startDate, $utcTz), new DateTime($endDate, $utcTz), 'UTC'); $start = $iterator->getStart(); $end = $iterator->getEnd(); //Interval which is used in the database query for grouping $queryInterval = preg_replace('/^1 /', '', $iterator->getInterval()); $criteria = !empty($allowedEnvs) ? ['envId' => $allowedEnvs] : []; //Requests data for the specified period $rawUsage = $this->getFarmData($accountId, $criteria, $start, $end, [$queryInterval, TagEntity::TAG_ID_FARM], true); //Requests data for the previous period $rawPrevUsage = $this->getFarmData($accountId, $criteria, $iterator->getPreviousStart(), $iterator->getPreviousEnd(), [$queryInterval, TagEntity::TAG_ID_FARM], true); //Calculates top five farms for the specified period $topFarms = []; $arr = (new AggregationCollection(['farmId'], ['cost' => 'sum']))->load($rawUsage)->getArrayCopy(); if (!empty($arr['data']) && count($arr['data']) > $farmCount + 1) { uasort($arr['data'], function ($a, $b) { if ($a['cost'] == $b['cost']) { return 0; } return $a['cost'] < $b['cost'] ? 1 : -1; }); $i = 0; foreach ($arr['data'] as $farmId => $v) { $topFarms[$farmId] = $farmId; if (++$i >= $farmCount) { break; } } } //Subtotals by farms $usage = new AggregationCollection(['farmId'], ['cost' => 'sum']); //Previous period subtotals by farms $prevUsage = new AggregationCollection(['farmId'], ['cost' => 'sum']); if (empty($topFarms)) { //Loads current period foreach ($rawUsage as $item) { $usage->append($item); } //Loads previous period foreach ($rawPrevUsage as $item) { $prevUsage->append($item); } } else { //Loads current period and aggregates top 5 farms foreach ($rawUsage as $item) { if (array_key_exists($item['farmId'], $topFarms)) { $usage->append($item); } } //Loads previous period and aggregates top 5 farms foreach ($rawPrevUsage as $item) { if (array_key_exists($item['farmId'], $topFarms)) { $prevUsage->append($item); } } } //Calculates percentage $usage->calculatePercentage(); if ($iterator->getWholePreviousPeriodEnd() != $iterator->getPreviousEnd()) { $rawPrevUsageWhole = $this->getFarmData($accountId, ['envId' => $allowedEnvs], $iterator->getPreviousStart(), $iterator->getWholePreviousPeriodEnd(), [TagEntity::TAG_ID_FARM], true); //Previous whole period usage subtotals by farm $prevUsageWhole = (new AggregationCollection(['farmId'], ['cost' => 'sum']))->load($rawPrevUsageWhole); } else { $prevUsageWhole = $prevUsage; } //Build farms total $farmsTotal = []; $it = $usage->getIterator(); foreach ($it as $farmId => $p) { $pp = isset($prevUsage['data'][$farmId]) ? $prevUsage['data'][$farmId] : null; $pw = isset($prevUsageWhole['data'][$farmId]) ? $prevUsageWhole['data'][$farmId] : null; $cl = $this->getTotalDataArray($farmId, $this->fetchFarmName($farmId), $p, $pp, $pw, [], null, true); $farmsTotal[] = $cl; } return $farmsTotal; }