/** * 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]); }
/** * @test * @dataProvider providerConstructor */ public function testConstructor($mode, $start, $end, $fixture) { $iterator = new ChartPeriodIterator($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, '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 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; }
/** * 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(); }
/** * 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]; }