public function defaultAction() { $days = SettingEntity::getQuarters(true); $quarters = new Quarters($days); $period = $quarters->getPeriodForDate(); $this->response->page('ui/account2/analytics/budgets/view.js', ['quarters' => $days, 'quartersConfirmed' => SettingEntity::getValue(SettingEntity::ID_QUARTERS_DAYS_CONFIRMED), 'fiscalYear' => $period->year], ['ui/analytics/analytics.js'], ['ui/analytics/analytics.css', '/ui/admin/analytics/admin.css', '/ui/admin/analytics/budgets/budgets.css']); }
/** * {@inheritdoc} * @see \Scalr\System\Zmq\Cron\TaskInterface::enqueue() */ public function enqueue() { $queue = new ArrayObject([]); if (!\Scalr::getContainer()->analytics->enabled) { $this->log("INFO", "Terminating the process as Cost analytics is disabled in the config."); exit; } if (SettingEntity::getValue(SettingEntity::ID_FORBID_AUTOMATIC_UPDATE_AWS_PRICES)) { $this->log("INFO", "Terminating the process because of overriding AWS prices has been forbidden by financial admin."); exit; } $now = new DateTime('now', new DateTimeZone('UTC')); $urls = array('https://a0.awsstatic.com/pricing/1/ec2/linux-od.min.js', 'https://a0.awsstatic.com/pricing/1/ec2/mswin-od.min.js', 'https://a0.awsstatic.com/pricing/1/ec2/previous-generation/linux-od.min.js', 'https://a0.awsstatic.com/pricing/1/ec2/previous-generation/mswin-od.min.js'); $availableLocations = Aws::getCloudLocations(); foreach ($urls as $link) { $json = trim(preg_replace('/^.+?callback\\((.+?)\\);\\s*$/sU', '\\1', $this->getPricingContent($link))); $data = json_decode(preg_replace('/(\\w+):/', '"\\1":', $json)); if (!empty($data->config->regions)) { foreach ($data->config->regions as $rd) { $rd->url = basename($link); $queue->append($rd); } } } return $queue; }
public function xSaveQuarterCalendarAction() { $this->request->defineParams(array('quarters' => array('type' => 'array'))); $quarters = $this->getParam('quarters'); //Validate the quarter dates if (empty($quarters) || !is_array($quarters) || count($quarters) != 4) { throw new UnexpectedValueException(sprintf("Four periods should be defined.")); } $normalized = []; $year = gmdate('Y'); $ny = 0; $i = 0; foreach ($quarters as $value) { if (preg_match('/^([\\d]{1,2})\\-([\\d]{1,2})$/', $value, $matches)) { $m = $matches[1]; $d = $matches[2]; $lastDayOfMonth = date('t', strtotime(sprintf("%04d-%02d-01", $year, $m))); if ($m < 1 || $m > 12) { throw new OutOfBoundsException(sprintf("Invalid month number %02d.", $m)); } else { if ($d < 1 || $d > $lastDayOfMonth) { throw new OutOfBoundsException(sprintf("Invalid day (%02d) of month (%02d). Last day of this month is %d", $d, $m, $lastDayOfMonth)); } else { if ($m == 2 && ($d = 29)) { throw new OutOfBoundsException(sprintf("You cannot specify Feb 29 as start date of the quarter.")); } } } $v = sprintf("%02d-%02d", $m, $d); if (in_array($v, $normalized)) { throw new OutOfBoundsException(sprintf("You cannot specify the same day twice (%s)", $v)); } if ($i > 0) { if ($normalized[$i - 1] > $v) { $ny++; } if ($ny > 1) { throw new OutOfBoundsException("Periods should be consistent."); } } $normalized[$i++] = $v; } else { throw new UnexpectedValueException(sprintf("Invalid date [MM-DD] %s", strip_tags($value))); } } //Saving $entity = new SettingEntity(); $entity->id = SettingEntity::ID_BUDGET_DAYS; $entity->value = json_encode($normalized); $entity->save(); if (!SettingEntity::getValue(SettingEntity::ID_QUARTERS_DAYS_CONFIRMED)) { $entity = new SettingEntity(); $entity->id = SettingEntity::ID_QUARTERS_DAYS_CONFIRMED; $entity->value = 1; $entity->save(); } $this->response->data(array('quarter' => $normalized)); $this->response->success('Fiscal calendar has been successfully saved'); }
public function defaultAction() { $ccs = array(); $projects = array(); foreach (CostCentreEntity::find([['archived' => 0]]) as $cc) { $ccs[$cc->ccId] = $cc->name; } foreach (ProjectEntity::find([['archived' => 0]]) as $project) { $projects[$project->projectId] = $project->name; } $this->response->page('ui/analytics/notifications/view.js', array('notifications.ccs' => array('enabled' => SettingEntity::getValue(SettingEntity::ID_NOTIFICATIONS_CCS_ENABLED), 'items' => NotificationEntity::findBySubjectType(NotificationEntity::SUBJECT_TYPE_CC)), 'notifications.projects' => array('enabled' => SettingEntity::getValue(SettingEntity::ID_NOTIFICATIONS_PROJECTS_ENABLED), 'items' => NotificationEntity::findBySubjectType(NotificationEntity::SUBJECT_TYPE_PROJECT)), 'reports' => array('enabled' => SettingEntity::getValue(SettingEntity::ID_REPORTS_ENABLED), 'items' => ReportEntity::all()), 'ccs' => $ccs, 'projects' => $projects), array(), array()); }
public function defaultAction() { //Platforms should be in the same order everywhere $platforms = $this->getContainer()->analytics->prices->getSupportedClouds(); $this->response->page('ui/admin/analytics/pricing/view.js', ['platforms' => $platforms, 'forbidAutomaticUpdate' => [SERVER_PLATFORMS::EC2 => !!SettingEntity::getValue(SettingEntity::ID_FORBID_AUTOMATIC_UPDATE_AWS_PRICES)]], [], ['ui/admin/analytics/pricing/view.css']); }
public function defaultAction() { $this->response->page('ui/account2/analytics/budgets/view.js', array('quarters' => SettingEntity::getQuarters(true), 'quartersConfirmed' => SettingEntity::getValue(SettingEntity::ID_QUARTERS_DAYS_CONFIRMED)), array('ui/analytics/analytics.js'), array('ui/analytics/analytics.css', '/ui/admin/analytics/admin.css', '/ui/admin/analytics/budgets/budgets.css')); }
protected function run5($stage) { $this->console->out("Updating reports status..."); $this->db->Execute("UPDATE `reports` set `status` = ?", array((int) SettingEntity::getValue(SettingEntity::ID_REPORTS_ENABLED))); }
/** * {@inheritdoc} * @see \Scalr\System\Pcntl\ProcessInterface::OnStartForking() */ public function OnStartForking() { if (!\Scalr::getContainer()->analytics->enabled) { die("Terminating the process as Cost analytics is disabled in the config.\n"); } $this->console->out("%s (UTC) Start Analytics Notifications process", gmdate('Y-m-d')); if (SettingEntity::getValue(SettingEntity::ID_NOTIFICATIONS_CCS_ENABLED) || SettingEntity::getValue(SettingEntity::ID_NOTIFICATIONS_PROJECTS_ENABLED)) { $this->console->out('Calculating data for projects notifications'); $quarters = new Quarters(SettingEntity::getQuarters()); $date = $quarters->getPeriodForDate('yesterday'); $formatedTitle = 'Q' . $quarters->getQuarterForDate('now') . ' budget (' . (new \DateTime('now', new \DateTimeZone('UTC')))->format('M j, Y') . ')'; $projects = ProjectEntity::find(); foreach ($projects as $project) { $periodProjectData = \Scalr::getContainer()->analytics->usage->getProjectPeriodData($project->projectId, 'quarter', $date->start->format('Y-m-d'), $date->end->format('Y-m-d')); $projectAnalytics[$project->projectId] = ['budget' => $periodProjectData['totals']['budget'], 'name' => $project->name, 'trends' => $periodProjectData['totals']['trends'], 'forecastCost' => $periodProjectData['totals']['forecastCost'], 'interval' => $periodProjectData['interval'], 'jsonVersion' => '1.0.0', 'farms' => []]; if (!empty($periodProjectData['totals']['farms'])) { foreach ($periodProjectData['totals']['farms'] as $farm) { $projectAnalytics[$project->projectId]['farms'][] = ['id' => $farm['id'], 'name' => $farm['name'], 'median' => $farm['median'] / 7, 'cost' => $farm['cost'], 'costPct' => $farm['costPct']]; } if (count($projectAnalytics[$project->projectId]['farms'] > 1)) { usort($projectAnalytics[$project->projectId]['farms'], array($this, 'sortItems')); if (count($projectAnalytics[$project->projectId]['farms'] > 6)) { array_splice($projectAnalytics[$project->projectId]['farms'], 6, count($projectAnalytics[$project->projectId]['farms'])); } } } $projectAnalytics[$project->projectId]['date'] = $formatedTitle; if ($project->archived) { continue; } if (SettingEntity::getValue(SettingEntity::ID_NOTIFICATIONS_PROJECTS_ENABLED)) { $projectNotifications = NotificationEntity::findBySubjectType(NotificationEntity::SUBJECT_TYPE_PROJECT); foreach ($projectNotifications as $notification) { $this->saveNotificationData('project', $notification, $project->projectId, $projectAnalytics); } } } } if (SettingEntity::getValue(SettingEntity::ID_NOTIFICATIONS_CCS_ENABLED)) { $this->console->out('Calculating data for cost center notifications'); $ccs = CostCentreEntity::find(); foreach ($ccs as $cc) { if ($cc->archived) { continue; } $periodCostCenterData = \Scalr::getContainer()->analytics->usage->getCostCenterPeriodData($cc->ccId, 'quarter', $date->start->format('Y-m-d'), $date->end->format('Y-m-d')); $ccAnalytics[$cc->ccId] = ['budget' => $periodCostCenterData['totals']['budget'], 'name' => $cc->name, 'trends' => $periodCostCenterData['totals']['trends'], 'forecastCost' => $periodCostCenterData['totals']['forecastCost'], 'interval' => $periodCostCenterData['interval'], 'jsonVersion' => '1.0.0', 'projects' => []]; if (!empty($periodCostCenterData['totals']['projects'])) { foreach ($periodCostCenterData['totals']['projects'] as $key => $project) { if (!empty($project['id'])) { $projectBudget = $projectAnalytics[$project['id']]['budget']; $projectBudget['name'] = $project['name']; $projectBudget['id'] = $project['id']; $projectBudget['median'] = $project['median'] / 7; $ccAnalytics[$cc->ccId]['projects'][] = $projectBudget; } else { $otherProjectsKey = $key; } } if (isset($otherProjectsKey)) { $ccAnalytics[$cc->ccId]['projects'][] = ['id' => '', 'budgetSpent' => $periodCostCenterData['totals']['projects'][$otherProjectsKey]['cost'], 'median' => $periodCostCenterData['totals']['projects'][$otherProjectsKey]['median'] / 7, 'name' => $periodCostCenterData['totals']['projects'][$otherProjectsKey]['name'], 'estimateOverspend' => null]; unset($otherProjectsKey); } if (count($ccAnalytics[$cc->ccId]['projects'] > 1)) { usort($ccAnalytics[$cc->ccId]['projects'], array($this, 'sortItems')); if (count($ccAnalytics[$cc->ccId]['projects'] > 6)) { array_splice($ccAnalytics[$cc->ccId]['projects'], 6, count($ccAnalytics[$cc->ccId]['projects'])); } } } $ccAnalytics[$cc->ccId]['date'] = $formatedTitle; $ccsNotifications = NotificationEntity::findBySubjectType(NotificationEntity::SUBJECT_TYPE_CC); foreach ($ccsNotifications as $notification) { $this->saveNotificationData('cc', $notification, $cc->ccId, $ccAnalytics); } } } if (SettingEntity::getValue(SettingEntity::ID_REPORTS_ENABLED)) { $this->console->out('Calculating data for reports'); $reports = ReportEntity::find(); foreach ($reports as $report) { switch ($report->period) { case ReportEntity::PERIOD_DAILY: $period = 'custom'; $start = (new \DateTime('yesterday', new \DateTimeZone('UTC')))->format('Y-m-d'); $end = $start; $startForecast = (new \DateTime('first day of this month', new \DateTimeZone('UTC')))->format('Y-m-d'); $endForecast = (new \DateTime('last day of this month', new \DateTimeZone('UTC')))->format('Y-m-d'); if ($startForecast == (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d')) { $startForecast = (new \DateTime('first day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $endForecast = (new \DateTime('last day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); } $periodForecast = 'month'; $formatedTitle = (new \DateTime($start, new \DateTimeZone('UTC')))->format('M j'); break; case ReportEntity::PERIOD_MONTHLY: $period = 'month'; $start = (new \DateTime('first day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $end = (new \DateTime('last day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $quarters = new Quarters(SettingEntity::getQuarters()); $currentQuarter = $quarters->getQuarterForDate(new \DateTime($start, new \DateTimeZone('UTC'))); $currentYear = (new \DateTime($start, new \DateTimeZone('UTC')))->format('Y'); $date = $quarters->getPeriodForQuarter($currentQuarter, $currentYear); $formatedTitle = (new \DateTime($start, new \DateTimeZone('UTC')))->format('M Y'); $startForecast = $date->start->format('Y-m-d'); $endForecast = $date->end->format('Y-m-d'); $periodForecast = 'quarter'; break; case ReportEntity::PERIOD_QUARTELY: $period = 'quarter'; $quarters = new Quarters(SettingEntity::getQuarters()); $currentQuarter = $quarters->getQuarterForDate(new \DateTime('yesterday', new \DateTimeZone('UTC'))); $currentYear = (new \DateTime('yesterday', new \DateTimeZone('UTC')))->format('Y'); if ($currentQuarter === 1) { $quarter = 4; $year = $currentYear - 1; } else { $quarter = $currentQuarter - 1; $year = $currentYear; } $date = $quarters->getPeriodForQuarter($quarter, $year); $start = $date->start->format('Y-m-d'); $end = $date->end->format('Y-m-d'); $formatedTitle = 'Q' . $quarter . ' ' . $year; $startForecast = $currentYear . '-01-01'; $endForecast = $currentYear . '-12-31'; $periodForecast = 'year'; break; case ReportEntity::PERIOD_WEEKLY: $period = 'week'; $end = (new \DateTime('yesterday', new \DateTimeZone('UTC')))->modify('last saturday')->format('Y-m-d'); $start = (new \DateTime($end, new \DateTimeZone('UTC')))->modify('last sunday')->format('Y-m-d'); $formatedTitle = (new \DateTime($start, new \DateTimeZone('UTC')))->format('M j') . ' - ' . (new \DateTime($end, new \DateTimeZone('UTC')))->format('M j'); $startForecast = (new \DateTime('first day of this month', new \DateTimeZone('UTC')))->format('Y-m-d'); $endForecast = (new \DateTime('last day of this month', new \DateTimeZone('UTC')))->format('Y-m-d'); if ($startForecast == (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d')) { $startForecast = (new \DateTime('first day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $endForecast = (new \DateTime('last day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); } $periodForecast = 'month'; break; } if ($report->subjectType === ReportEntity::SUBJECT_TYPE_CC) { $getPeriodicSubjectData = 'getCostCenterPeriodData'; $subjectEntityName = 'Scalr\\Stats\\CostAnalytics\\Entity\\CostCentre'; $subjectId = 'ccId'; } else { if ($report->subjectType === ReportEntity::SUBJECT_TYPE_PROJECT) { $getPeriodicSubjectData = 'getProjectPeriodData'; $subjectEntityName = 'Scalr\\Stats\\CostAnalytics\\Entity\\Project'; $subjectId = 'projectId'; } else { $periodData = \Scalr::getContainer()->analytics->usage->getDashboardPeriodData($period, $start, $end); $periodDataForecast = \Scalr::getContainer()->analytics->usage->getDashboardPeriodData($periodForecast, $startForecast, $endForecast); $periodData['period'] = $period; $periodData['forecastPeriod'] = $periodForecast; $periodData['totals']['forecastCost'] = $periodDataForecast['totals']['forecastCost']; $periodData['totals']['trends'] = $periodDataForecast['totals']['trends']; $periodData['name'] = 'Cloud Cost Report'; $periodData['jsonVersion'] = '1.0.0'; $periodData['totals']['clouds'] = $this->changeCloudNames($periodData['totals']['clouds']); $periodData['date'] = $formatedTitle; $periodData['totals']['budget']['budget'] = null; if ($period == 'quarter') { $periodData['totals']['budget'] = ['quarter' => $quarter, 'year' => $year, 'quarterStartDate' => $start, 'quarterEndDate' => $end]; } else { if ($period == 'month') { $periodData['totals']['budget'] = ['quarter' => $currentQuarter]; } } unset($periodData['projects'], $periodData['budget']['projects']); if (count($periodData['costcenters'] > 1)) { uasort($periodData['costcenters'], array($this, 'sortItems')); if (count($periodData['costcenters'] > 6)) { array_splice($periodData['costcenters'], 6, count($periodData['costcenters'])); } } if (count($periodData['totals']['clouds'] > 1)) { usort($periodData['totals']['clouds'], array($this, 'sortItems')); } $entity = ReportPayloadEntity::init([$report->subjectType, $report->subjectId, $period], $periodData, $start); if (!ReportPayloadEntity::findPk($entity->uuid)) { $payload = json_decode($entity->payload, true); \Scalr::getContainer()->mailer->setSubject('Summary report.')->setContentType('text/html')->sendTemplate(SCALR_TEMPLATES_PATH . '/emails/report_summary.html.php', $payload, $report->emails); $this->console->out('Summary report email has been sent'); $payload['date'] = $entity->created->format('Y-m-d'); $entity->payload = json_encode($payload); $entity->save(); } } } unset($currentQuarter, $currentYear); if (!empty($report->subjectType) && !empty($report->subjectId)) { $subject = call_user_func($subjectEntityName . 'Entity::findPk', $report->subjectId); if ($subject->archived) { continue; } $this->saveReportData($getPeriodicSubjectData, $subjectEntityName, ['period' => $period, 'start' => $start, 'end' => $end], ['period' => $periodForecast, 'start' => $startForecast, 'end' => $endForecast], $report->subjectId, $report->subjectType, $report->emails, $formatedTitle); } else { if (!empty($report->subjectType)) { $subjects = call_user_func($subjectEntityName . 'Entity::find'); foreach ($subjects as $subject) { if ($subject->archived) { continue; } $this->saveReportData($getPeriodicSubjectData, $subjectEntityName, ['period' => $period, 'start' => $start, 'end' => $end], ['period' => $periodForecast, 'start' => $startForecast, 'end' => $endForecast], $subject->{$subjectId}, $report->subjectType, $report->emails, $formatedTitle); } } } } } $this->console->out('Done'); exit; }
/** * {@inheritdoc} * @see \Scalr\System\Pcntl\ProcessInterface::OnStartForking() */ public function OnStartForking() { if (!\Scalr::getContainer()->analytics->enabled) { die("Terminating the process as Cost analytics is disabled in the config.\n"); } if (SettingEntity::getValue(SettingEntity::ID_FORBID_AUTOMATIC_UPDATE_AWS_PRICES)) { die("Terminating the process because of overriding AWS prices has been forbidden by financial admin.\n"); } $now = new DateTime('now', new DateTimeZone('UTC')); $urls = array('https://a0.awsstatic.com/pricing/1/ec2/linux-od.min.js', 'https://a0.awsstatic.com/pricing/1/ec2/mswin-od.min.js'); $mapping = array('us-east' => 'us-east-1', 'us-west' => 'us-west-1', 'us-west-2' => 'us-west-2', 'eu-ireland' => 'eu-west-1', 'sa-east-1' => 'sa-east-1', 'apac-sin' => 'ap-southeast-1', 'apac-tokyo' => 'ap-northeast-1', 'apac-syd' => 'ap-southeast-2'); $availableLocations = Aws::getCloudLocations(); foreach ($urls as $link) { $json = trim(preg_replace('/^.+?callback\\((.+?)\\);\\s*$/sU', '\\1', file_get_contents($link))); $data = json_decode(preg_replace('/(\\w+):/', '"\\1":', $json)); if (!empty($data->config->regions)) { $cadb = Scalr::getContainer()->cadb; foreach ($data->config->regions as $rd) { foreach ($rd->instanceTypes as $it) { if (!isset($mapping[$rd->region])) { throw new Exception(sprintf("Region %s does not exist in the mapping.", $rd->region)); } $region = $mapping[$rd->region]; $latest = array(); //Gets latest prices for all instance types from current region. $res = $cadb->Execute("\n SELECT p.instance_type, ph.applied, p.os, p.name, HEX(p.price_id) `price_id`, p.cost\n FROM price_history ph\n JOIN prices p ON p.price_id = ph.price_id\n LEFT JOIN price_history ph2 ON ph2.platform = ph.platform\n AND ph2.cloud_location = ph.cloud_location\n AND ph2.account_id = ph.account_id\n AND ph2.url = ph.url\n AND ph2.applied > ph.applied AND ph2.applied <= ?\n LEFT JOIN prices p2 ON p2.price_id = ph2.price_id\n AND p2.instance_type = p.instance_type\n AND p2.os = p.os\n WHERE ph.account_id = 0 AND p2.price_id IS NULL\n AND ph.platform = 'ec2'\n AND ph.cloud_location = ?\n AND ph.url = ''\n AND ph.applied <= ?\n ", array($now->format('Y-m-d'), $region, $now->format('Y-m-d'))); while ($rec = $res->FetchRow()) { $latest[$rec['instance_type']][$rec['os']] = array('applied' => $rec['applied'], 'price_id' => $rec['price_id'], 'cost' => $rec['cost']); } $upd = array(); $needUpdate = false; foreach ($it->sizes as $sz) { foreach ($sz->valueColumns as $v) { $os = $v->name == 'linux' ? PriceEntity::OS_LINUX : PriceEntity::OS_WINDOWS; if (!isset($latest[$sz->size][$os])) { $needUpdate = true; } else { if (abs(($latest[$sz->size][$os]['cost'] - $v->prices->USD) / $v->prices->USD) > 1.0E-6) { $needUpdate = true; $latest[$sz->size][$os]['cost'] = $v->prices->USD; } else { continue; } } $latest[$sz->size][$os] = array('cost' => $v->prices->USD); } } if ($needUpdate) { $priceid = $cadb->GetOne("\n SELECT HEX(`price_id`) AS `price_id`\n FROM price_history\n WHERE platform = 'ec2'\n AND url = ''\n AND cloud_location = ?\n AND applied = ?\n AND account_id = 0\n LIMIT 1\n ", array($region, $now->format('Y-m-d'))); if (!$priceid) { $priceid = str_replace('-', '', Scalr::GenerateUID()); $cadb->Execute("\n INSERT price_history\n SET price_id = UNHEX(?),\n platform = 'ec2',\n url = '',\n cloud_location = ?,\n account_id = 0,\n applied = ?,\n deny_override = 0\n ", array($priceid, $region, $now->format('Y-m-d'))); } foreach ($latest as $instanceType => $ld) { foreach ($ld as $os => $v) { $cadb->Execute("\n REPLACE prices\n SET price_id = UNHEX(?),\n instance_type = ?,\n name = ?,\n os = ?,\n cost = ?\n ", array($priceid, $instanceType, $instanceType, $os, $v['cost'])); } } } } } } } exit; }
public function defaultAction() { //Platforms should be in the same order everywhere $platforms = array_values(array_intersect(array_keys(SERVER_PLATFORMS::GetList()), array_merge([SERVER_PLATFORMS::EC2], PlatformFactory::getOpenstackBasedPlatforms(), PlatformFactory::getCloudstackBasedPlatforms()))); $this->response->page('ui/analytics/pricing/view.js', ['platforms' => $platforms, 'forbidAutomaticUpdate' => [SERVER_PLATFORMS::EC2 => !!SettingEntity::getValue(SettingEntity::ID_FORBID_AUTOMATIC_UPDATE_AWS_PRICES)]], [], ['ui/analytics/pricing/view.css']); }