/** * 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 $ccId optional Cost center id * @param string $projectId optional Project id * @throws InvalidArgumentException */ public function xGetTimelineEventsAction($mode, $date, $start, $end = null, $ccId = 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 = new ChartPeriodIterator($mode, $start, $end ?: null, 'UTC'); foreach ($iterator as $chartPoint) { //FIXME rewrite search a point if ($chartPoint->dt->format('Y-m-d H:00') === $date) { $startDate = $chartPoint->dt; if ($chartPoint->isLastPoint) { $endDate = $iterator->getEnd(); } else { $iterator->next(); $endDate = $iterator->current()->dt; $endDate->modify("-1 second"); } break; } } if (!isset($startDate)) { throw new OutOfBoundsException(sprintf("Date %s is inconsistent with the interval object", $date)); } $entities = $analytics->events->get($startDate, $endDate, $ccId, $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 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 $envId optional Environment id * @param string $projectId optional Project id * @throws InvalidArgumentException */ public function xGetTimelineEventsAction($mode, $date, $start, $end = null, $envId = 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))); } if (!empty($envId)) { $env = Scalr_Environment::init()->loadById($envId); if ($env->clientId !== $this->user->getAccountId()) { throw new Scalr_Exception_InsufficientPermissions(); } } $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' => $envId, '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 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 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); }
/** * Constructor * * @param ChartPeriodIterator $iterator The iterator */ public function __construct(ChartPeriodIterator $iterator) { $this->mode = $iterator->getMode(); $this->dt = $iterator->getIterationTimestamp(); $this->interval = $iterator->getInterval(); $this->i = $iterator->getIterationNumber(); $this->start = $iterator->getStart(); $this->end = $iterator->getEnd(); $this->isLastPoint = $iterator->isLastPoint(); }
/** * @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 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 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, 'farmsCount' => count($projectEntity->getFarmsList())); if ($calculate) { $iterator = new ChartPeriodIterator('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()); //Calclulates usage for previous whole period if ($iterator->getPreviousEnd() != $iterator->getWholePreviousPeriodEnd()) { $prevWholePeriodUsage = $this->getContainer()->analytics->usage->get(['projectId' => $ret['projectId']], $iterator->getPreviousStart(), $iterator->getWholePreviousPeriodEnd()); } else { $prevWholePeriodUsage = $prevusage; } $ret = $this->getWrappedUsageData(['ccId' => $ret['ccId'], 'projectId' => $ret['projectId'], 'iterator' => $iterator, 'usage' => $usage['cost'], 'prevusage' => $prevusage['cost'], 'prevusagewhole' => $prevWholePeriodUsage['cost']]) + $ret; } return $ret; }
/** * Constructor * * @param ChartPeriodIterator $iterator The iterator * @throws \InvalidArgumentException */ public function __construct(ChartPeriodIterator $iterator) { $this->mode = $iterator->getMode(); $this->dt = $iterator->getIterationTimestamp(); $this->interval = $iterator->getInterval(); $this->i = $iterator->getIterationNumber(); $prevStart = $iterator->getPreviousStart(); $previousPeriodDt = clone $this->dt; $previousPeriodDt->sub($iterator->getPreviousPeriodInterval()); $this->start = $iterator->getStart(); $this->end = $iterator->getEnd(); $this->isLastPoint = $iterator->isLastPoint(); if ($this->mode == 'year' || $this->mode == 'custom' && $this->interval == '1 month') { $this->show = $this->label = $this->dt->format('M'); $this->key = $this->dt->format('Y-m'); $this->previousPeriodKey = $previousPeriodDt->format('Y-m'); } elseif ($this->mode == 'quarter' || $this->mode == 'custom' && $this->interval == '1 week') { $ddt = clone $this->dt; $ddt->modify('next saturday'); if ($ddt > $this->end) { $ddt = $this->end; } $this->label = $this->dt->format('M j') . ' - ' . $ddt->format('M j'); $this->key = \Scalr_Util_DateTime::yearweek($this->dt->format('Y-m-d')); $this->previousPeriodKey = \Scalr_Util_DateTime::yearweek($previousPeriodDt->format('Y-m-d')); $this->show = $this->i % 3 == 0 ? $this->dt->format('M j') : ($this->isLastPoint && $this->i % 3 > 1 ? $ddt->format('M j') : ''); } elseif ($this->mode == 'week') { $this->label = $this->dt->format('l, M j'); $this->show = $this->dt->format('M j'); $this->key = $this->dt->format('Y-m-d'); $this->previousPeriodKey = $previousPeriodDt->format('Y-m-d'); } elseif ($this->mode == 'month' || $this->mode == 'custom' && $this->interval == '1 day') { $this->label = $this->dt->format('M j'); $this->key = $this->dt->format('Y-m-d'); $this->previousPeriodKey = $previousPeriodDt->format('Y-m-d'); $this->show = $this->i % 4 == 0 || $this->isLastPoint && $this->i % 4 > 2 ? $this->dt->format('M j') : ''; } elseif ($this->mode == 'custom') { switch ($this->interval) { case '1 hour': $h = $this->dt->format('H'); $this->label = $this->dt->format('l, M j, g A'); $this->show = $h == 0 ? $this->dt->format('M j') : ($h % 3 == 0 ? $this->dt->format('g a') : ''); $this->key = $this->dt->format("Y-m-d H:00:00"); $this->previousPeriodKey = $previousPeriodDt->format('Y-m-d H:00:00'); break; case '1 quarter': //Quarter breakdown is not supported yet $quarters = new Quarters(SettingEntity::getQuarters()); $currentPeriod = $quarters->getPeriodForDate($this->start); $prevPeriod = $quarters->getPeriodForDate($prevStart); $this->show = $this->label = $currentPeriod->year . ' Q' . $currentPeriod->quarter; $this->key = $currentPeriod->year . '-' . $currentPeriod->quarter; $this->previousPeriodKey = $prevPeriod->year . '-' . $prevPeriod->quarter; break; case '1 year': $this->show = $this->label = $this->dt->format('Y'); $this->key = $this->label; $this->previousPeriodKey = $previousPeriodDt->format('Y'); break; default: throw new \InvalidArgumentException(sprintf('Unsupported interval for custom mode %s.', $this->interval)); break; } } else { throw new \InvalidArgumentException(sprintf('Invalid mode %s', strip_tags($this->mode))); } }
/** * 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); }
/** * Gets total data array * * @param string $id The identifier of the subject * @param string $name The name of the subject * @param array $currentPeriod * @param array $previousPeriod * @param array $previousWholePeriod * @param array $detailed Details by each point on chart * @param ChartPeriodIterator $iterator optional Iterator is needed when it returns long form of array * @param bool $bshort optional Whether it should return short form of array * @return array Returns data array */ public function getTotalDataArray($id, $name, $currentPeriod, $previousPeriod, $previousWholePeriod, $detailed, ChartPeriodIterator $iterator = null, $bshort = false) { $cl = ['id' => $id, 'name' => $name, 'cost' => isset($currentPeriod['cost']) ? round($currentPeriod['cost'], 2) : 0, 'costPct' => isset($currentPeriod['cost_percentage']) ? $currentPeriod['cost_percentage'] : 0, 'prevCost' => isset($previousPeriod['cost']) ? round($previousPeriod['cost'], 2) : 0, 'prevCostPct' => isset($previousPeriod['cost_percentage']) ? $previousPeriod['cost_percentage'] : 0]; $cl['growth'] = $cl['cost'] - $cl['prevCost']; // growth $cl['growthPct'] = $cl['prevCost'] == 0 ? null : round(abs($cl['growth'] / $cl['prevCost'] * 100), 0); //growth percentage //short form of the data array if ($bshort) { return $cl; } // forecasted spend for period $cl['forecastCost'] = self::calculateForecast($cl['cost'], $iterator->getStart(), $iterator->getEnd(), $previousWholePeriod['cost'], ($cl['growth'] > 0 ? 1 : -1) * $cl['growthPct']); $mediandata = []; if (!empty($detailed[$id]['data'])) { $dt = $iterator->getStart(); //FIXME rewrite searching a point foreach ($detailed[$id]['data'] as $i => $v) { if ($dt > $iterator->today) { break; } $mediandata[] = !isset($v['cost']) ? 0 : $v['cost']; if ($iterator->getInterval() == '1 week') { $dt->modify('next sunday'); } else { $dt->add($iterator->getIterationInterval()); } } } $cl['median'] = empty($detailed[$id]['data']) ? 0 : round((double) Scalr_Util_Arrays::median($mediandata), 2); $cl['averageCost'] = count($mediandata) ? round(array_sum($mediandata) / count($mediandata), 2) : 0; // percentage difference between current and previous period $cl['curPrevPctGrowth'] = $cl['costPct'] - $cl['prevCostPct']; return $cl; }
/** * 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 detailed top 5 usage by farms for specified project on date * * @param string|null $projectId The identifier of the project * @param string $platform The cloud platform * @param string $mode The mode * @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') * @param string $ccId optional The identifier of the cost center (It is used only when project is null) * @return array Returns detailed top 5 usage by farms for specified project on date * @throws AnalyticsException * @throws OutOfBoundsException */ public function getProjectFarmsTopUsageOnDate($projectId, $platform, $mode, $date, $start, $end, $ccId = null) { $projectId = empty($projectId) ? null : $projectId; $iterator = new ChartPeriodIterator($mode, $start, $end ?: null, 'UTC'); //Interval which is used in the database query for grouping $queryInterval = preg_replace('/^1 /', '', $iterator->getInterval()); if ($projectId !== null) { $project = ProjectEntity::findPk($projectId); if ($project === null) { if (empty($ccId)) { throw new AnalyticsException(sprintf("Project %s does not exist. Please provide ccId.", $projectId)); } } else { $ccId = $project->ccId; } } //Requests data for the specified period $rawUsage = $this->get(['projectId' => $projectId], $iterator->getStart(), $iterator->getEnd(), [$queryInterval, TagEntity::TAG_ID_PLATFORM, TagEntity::TAG_ID_FARM], true); //Requests data for the previous period $rawPrevUsage = $this->get(['projectId' => $projectId], $iterator->getPreviousStart(), $iterator->getPreviousEnd(), [$queryInterval, TagEntity::TAG_ID_PLATFORM, TagEntity::TAG_ID_FARM], true); //We do not need to calculate the percentage here $usg = (new AggregationCollection(['period', 'platform', 'farmId' => ['envId']], ['cost' => 'sum']))->load($rawUsage); $prevUsg = (new AggregationCollection(['period', 'platform', 'farmId'], ['cost' => 'sum']))->load($rawPrevUsage)->calculatePercentage(); //Previous chart point $prevcp = null; //Finds the key for current label foreach ($iterator as $chartPoint) { //FIXME rewrite search a point if ($chartPoint->dt->format('Y-m-d H:00') !== $date) { $prevcp = $chartPoint; continue; } $cp = $chartPoint; break; } if (!isset($cp)) { throw new OutOfRangeException(sprintf('Requested date (%s) is out of the range. Last point date is %s', $date, isset($prevcp->dt) ? $prevcp->dt->format('Y-m-d H:00') : 'undefined')); } $result = []; //Maximum number of the farms without grouping $max = 5; $sEverything = self::EVERYTHING_ELSE; if (!empty($usg['data'][$cp->key]['data'][$platform]['data'])) { $usgFarms = new AggregationCollection(['farmId' => ['envId']], ['cost' => 'sum']); $ptr = $usg['data'][$cp->key]['data'][$platform]['data']; uasort($ptr, function ($a, $b) { if ($a['cost'] == $b['cost']) { return 0; } return $a['cost'] > $b['cost'] ? -1 : 1; }); //Aggregates farms if its number more then max + 1 if (count($ptr) > $max + 1) { $this->otherFarmsQuantity = count($ptr) - $max; $new = []; $i = 0; foreach ($ptr as $farmId => $v) { $v['cost_percentage'] = round($usg['data'][$cp->key]['data'][$platform]['cost'] == 0 ? 0 : $v['cost'] * 100 / $usg['data'][$cp->key]['data'][$platform]['cost'], 0); if ($i < $max) { $new[$farmId] = $v; } elseif (!isset($new[self::EVERYTHING_ELSE])) { $v['id'] = self::EVERYTHING_ELSE; $new[self::EVERYTHING_ELSE] = $v; } else { $new[self::EVERYTHING_ELSE]['cost'] += $v['cost']; } $i++; } $new[self::EVERYTHING_ELSE]['cost_percentage'] = round($usg['data'][$cp->key]['data'][$platform]['cost'] == 0 ? 0 : $new[self::EVERYTHING_ELSE]['cost'] * 100 / $usg['data'][$cp->key]['data'][$platform]['cost'], 0); $usgFarms->setArray(['data' => $new]); } else { $usgFarms->setArray($usg['data'][$cp->key]['data'][$platform])->calculatePercentage(); } //Forms result data array foreach ($usgFarms->getIterator() as $farmId => $pv) { $record = $this->getDetailedPointDataArray($farmId, $this->fetchFarmName($farmId), $pv, isset($prevUsg['data'][$cp->previousPeriodKey]['data'][$platform]['data'][$farmId]) ? $prevUsg['data'][$cp->previousPeriodKey]['data'][$platform]['data'][$farmId] : null, isset($usg['data'][$cp->key]['data'][$platform]['data'][$farmId]) ? $usg['data'][$cp->key]['data'][$platform]['data'][$farmId] : null); if ($farmId && $farmId != self::EVERYTHING_ELSE && !empty($pv['envId'])) { $record['environment'] = ['id' => (int) $pv['envId'], 'name' => AccountTagEntity::fetchName($pv['envId'], TagEntity::TAG_ID_ENVIRONMENT)]; } $result[] = $record; } } return ['data' => $result]; }
/** * 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 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 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; }