Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions front/usageimpact.form.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,13 @@
}
}

$usage_impact = Engine::getEngineFromItemtype($itemtype);
$usage_impact = Engine::getEngineFromItemtype($item);
if ($usage_impact === null) {
Session::addMessageAfterRedirect(__('Unable to find calculation engine for this asset.', 'carbon'), false, ERROR);
Html::back();
}

if (!$usage_impact->evaluateItem($_POST['items_id'])) {
if (!$usage_impact->evaluateItem()) {
Session::addMessageAfterRedirect(__('Update of usage impact failed.', 'carbon'), false, ERROR);
}
}
Expand Down
4 changes: 2 additions & 2 deletions install/mysql/plugin_carbon_empty.sql
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_embodiedimpacts` (
`pm_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants',
`pocp` float DEFAULT '0' COMMENT '(unit g NMVOC eq) Photochemical ozone formation',
`pocp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants',
`wu` float DEFAULT '0' COMMENT '(unit L) Use of water resources',
`wu` float DEFAULT '0' COMMENT '(unit M^3) Use of water resources',
`wu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants',
`mips` float DEFAULT '0' COMMENT '(unit g) Material input per unit of service',
`mips_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants',
Expand Down Expand Up @@ -289,7 +289,7 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_usageimpacts` (
`pm_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants',
`pocp` float DEFAULT '0' COMMENT '(unit g NMVOC eq) Photochemical ozone formation',
`pocp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants',
`wu` float DEFAULT '0' COMMENT '(unit L) Use of water resources',
`wu` float DEFAULT '0' COMMENT '(unit M^3) Use of water resources',
`wu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants',
`mips` float DEFAULT '0' COMMENT '(unit g) Material input per unit of service',
`mips_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants',
Expand Down
90 changes: 90 additions & 0 deletions src/AbstractImpact.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@
namespace GlpiPlugin\Carbon;

use CommonDBChild;
use CommonDBTM;
use DBmysql;
use DBmysqlIterator;
use GlpiPlugin\Carbon\Impact\Type;
use Toolbox as GlpiToolbox;

abstract class AbstractImpact extends CommonDBChild
{
Expand All @@ -42,6 +46,11 @@ abstract class AbstractImpact extends CommonDBChild

public static $rightname = 'carbon:report';

public function canEdit($ID): bool
{
return false;
}

public function rawSearchOptions()
{
$tab = parent::rawSearchOptions();
Expand Down Expand Up @@ -132,6 +141,87 @@ public function rawSearchOptions()
return $tab;
}

/**
* Get iterator of items without known embodied impact for a specified itemtype
*
* @template T of CommonDBTM
* @param class-string<T> $itemtype
* @param array $crit Criteria array of WHERE, ORDER, GROUP BY, LEFT JOIN, INNER JOIN, RIGHT JOIN, HAVING, LIMIT
* @return DBmysqlIterator
*/
public static function getItemsToEvaluate(string $itemtype, array $crit = []): DBmysqlIterator
{
/** @var DBmysql $DB */
global $DB;

// Check $itemtype inherits from CommonDBTM
if (!GlpiToolbox::isCommonDBTM($itemtype)) {
throw new \LogicException('itemtype is not a CommonDBTM object');
}

// clean $crit array: remove mostly SELECT, FROM
$crit = array_intersect_key($crit, array_flip([
'WHERE',
'ORDER',
'GROUP BY',
'LEFT JOIN',
'INNER JOIN',
'RIGHT JOIN',
'HAVING',
'LIMIT',
]));

$table = self::getTable();
$glpi_item_type_table = getTableForItemType($itemtype . 'Type');
$glpi_item_type_fk = getForeignKeyFieldForTable($glpi_item_type_table);
$item_type_table = getTableForItemType('GlpiPlugin\\Carbon\\' . $itemtype . 'Type');
$item_table = $itemtype::getTable();

$iterator = $DB->request(array_merge_recursive([
'SELECT' => [
$itemtype::getTableField('id'),
],
'FROM' => $item_table,
'LEFT JOIN' => [
$table => [
'FKEY' => [
$table => 'items_id',
$item_table => 'id',

],
'AND' => [
'itemtype' => $itemtype,
],
],
$item_type_table => [
[
'FKEY' => [
$item_type_table => $glpi_item_type_fk,
$item_table => $glpi_item_type_fk,
],
],
],
],
'WHERE' => [
[
// No calculated data or data to recalculate
'OR' => [
self::getTableField('items_id') => null,
self::getTableField('recalculate') => 1,
],
], [
// Item not marked to exclude from calculation
'OR' => [
$item_type_table . '.is_ignore' => 0,
$item_type_table . '.id' => null,
],
],
],
], $crit));

return $iterator;
}

/**
* Get impact value in a human r eadable format, selecting the best unit
*
Expand Down
50 changes: 32 additions & 18 deletions src/CronTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,13 @@
use Config as GlpiConfig;
use CronTask as GlpiCronTask;
use Geocoder\Exception\QuotaExceeded;
use Geocoder\Geocoder;
use GlpiPlugin\Carbon\DataSource\CarbonIntensity\ClientFactory;
use GlpiPlugin\Carbon\DataSource\CarbonIntensity\ClientInterface;
use GlpiPlugin\Carbon\DataSource\CronTaskProvider;
use GlpiPlugin\Carbon\Impact\Embodied\AbstractEmbodiedImpact;
use GlpiPlugin\Carbon\Impact\Embodied\Engine as EmbodiedEngine;
use GlpiPlugin\Carbon\Impact\History\AssetInterface;
use GlpiPlugin\Carbon\Impact\Usage\Engine as UsageEngine;
use GlpiPlugin\Carbon\Impact\Usage\UsageImpactInterface as UsageImpactInterface;
use Location as GlpiLocation;
use Toolbox as GlpiToolbox;

class CronTask extends CommonGLPI
{
Expand Down Expand Up @@ -140,25 +137,47 @@ public static function cronUsageImpact(GlpiCronTask $task): int
// Half of job for GWP, the other half for other impacts
$limit_per_type = max(1, floor($limit_per_type / 2));

/**
* Huge quantity of SQL queries will be executed
* We NEED to check memory usage to avoid running out of memory
* @see DbMysql::doQuery()
*/
$memory_limit = Toolbox::getMemoryLimit();

// Calculate GWP
$count = 0;
foreach ($usage_impacts as $usage_impact_type) {
/** @var UsageImpactInterface $usage_impact */
/** @var AssetInterface $usage_impact */
$usage_impact = new $usage_impact_type();
$usage_impact->setLimit($limit_per_type);
$count = $usage_impact->evaluateItems($usage_impact->getItemsToEvaluate());
$task->addVolume($count);
}

// Calculate other impacts
$limit = ['LIMIT' => $limit_per_type];
foreach (PLUGIN_CARBON_TYPES as $itemtype) {
$usage_impact = UsageEngine::getEngineFromItemtype($itemtype);
if ($usage_impact === null) {
continue;
foreach (UsageImpact::getItemsToEvaluate($itemtype, $limit) as $row) {
$item = new $itemtype();
if (!$item->getFromDB($row['id'])) {
continue;
}
$usage_impact = UsageEngine::getEngineFromItemtype($item);
if ($usage_impact === null) {
// An error occured while configuring the engine
continue;
}
if ($usage_impact->evaluateItem()) {
$count++;
}

// Check free memory
if ($memory_limit && $memory_limit < memory_get_usage()) {
// 8 MB memory left, emergency exit
// Terminate the task
break 2;
}
}
$usage_impact->setLimit($limit_per_type);
$count = $usage_impact->evaluateItems($usage_impact->getItemsToEvaluate());
$task->addVolume($count);
}

return ($count > 0 ? 1 : 0);
Expand All @@ -183,18 +202,13 @@ public static function cronEmbodiedImpact(GlpiCronTask $task): int
* We NEED to check memory usage to avoid running out of memory
* @see DbMysql::doQuery()
*/
$memory_limit = GlpiToolbox::getMemoryLimit() - 8 * 1024 * 1024;
if ($memory_limit < 0) {
// May happen in test seems that ini_get("memory_limits") returns
// enpty string in PHPUnit environment
$memory_limit = null;
}
$memory_limit = Toolbox::getMemoryLimit();

/** @var int $count count of successfully evaluated assets */
$count = 0;
$limit = ['LIMIT' => $limit_per_type];
foreach (PLUGIN_CARBON_TYPES as $itemtype) {
foreach (AbstractEmbodiedImpact::getItemsToEvaluate($itemtype, $limit) as $row) {
foreach (EmbodiedImpact::getItemsToEvaluate($itemtype, $limit) as $row) {
$item = new $itemtype();
if (!$item->getFromDB($row['id'])) {
continue;
Expand Down
9 changes: 2 additions & 7 deletions src/DataSource/CarbonIntensity/AbstractClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
use GlpiPlugin\Carbon\CarbonIntensity;
use GlpiPlugin\Carbon\Source;
use GlpiPlugin\Carbon\Source_Zone;
use GlpiPlugin\Carbon\Toolbox;
use GlpiPlugin\Carbon\Zone;
use Symfony\Component\Console\Helper\ProgressBar;
use Toolbox as GlpiToolbox;

abstract class AbstractClient implements ClientInterface
{
Expand Down Expand Up @@ -181,12 +181,7 @@ public function fullDownload(string $zone, DateTimeImmutable $start_date, DateTi
* We NEED to check memory usage to avoid running out of memory
* @see DBmysql::doQuery()
*/
$memory_limit = GlpiToolbox::getMemoryLimit() - 8 * 1024 * 1024;
if ($memory_limit < 0) {
// May happen in test seems that ini_get("memory_limits") returns
// enpty string in PHPUnit environment
$memory_limit = null;
}
$memory_limit = Toolbox::getMemoryLimit();

// Traverse each month from start_date to end_date
$current_date = DateTime::createFromImmutable($start_date);
Expand Down
76 changes: 76 additions & 0 deletions src/DataSource/Lca/Boaviztapi/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
use GlpiPlugin\Carbon\Config as CarbonConfig;
use GlpiPlugin\Carbon\DataSource\Lca\AbstractClient;
use GlpiPlugin\Carbon\DataSource\RestApiClientInterface;
use GlpiPlugin\Carbon\DataTracking\TrackedFloat;
use GlpiPlugin\Carbon\Impact\Type;
use GlpiPlugin\Carbon\Source;
use GlpiPlugin\Carbon\Source_Zone;
use GlpiPlugin\Carbon\Zone;
Expand All @@ -48,6 +50,32 @@ class Client extends AbstractClient
private string $base_url;
private static string $source_name = 'Boaviztapi';

/** @var array Supported impact criterias and the multiplier unit of the value returned by Boaviztapi */
protected array $criteria_units = [
'gwp' => 1000, // Kg
'adp' => 1000, // Kg
'pe' => 1000000, // MJ
'gwppb' => 1000, // Kg
'gwppf' => 1000, // Kg
'gwpplu' => 1000, // Kg
'ir' => 1000, // Kg
'lu' => 1, // (no unit)
'odp' => 1000, // Kg
'pm' => 1, // (no unit)
'pocp' => 1000, // Kg
'wu' => 1, // M^3
'mips' => 1000, // Kg
'adpe' => 1000, // Kg
'adpf' => 1000000, // MJ
'ap' => 1, // mol
'ctue' => 1, // CTUe
// 'ctuh_c' => 1, // CTUh request fails when this criteria is added, not a URL encoding issue
// 'ctuh_nc' => 1, // CTUh request fails when this criteria is added, not a URL encoding issue
'epf' => 1000, // Kg
'epm' => 1000, // Kg
'ept' => 1, // mol
];

public function __construct(RestApiClientInterface $client, string $url = '')
{
$this->client = $client;
Expand Down Expand Up @@ -139,6 +167,11 @@ public function queryZones(): array
return $response;
}

public function getCriteriaUnits(): array
{
return $this->criteria_units;
}

/**
* Save zones into database
*
Expand Down Expand Up @@ -211,6 +244,49 @@ public static function getZones()
return $zones;
}

/**
* Read the response to find the impacts provided by Boaviztapi
*
* @param array $response
* @param string $scope (must be either embedded or use)
* @return array
*/
public function parseResponse(array $response, string $scope): array
{
$impacts = [];
$types = Type::getImpactTypes();
foreach ($response['impacts'] as $type => $impact) {
if (!in_array($type, $types)) {
trigger_error(sprintf('Unsupported impact type %s in class %s', $type, __CLASS__));
continue;
}
$impact_id = Type::getImpactId($type);
if ($impact_id === false) {
continue;
}
$impacts[$impact_id] = $this->parseCriteria($type, $response['impacts'][$type][$scope]);
}

return $impacts;
}

protected function parseCriteria(string $name, $impact): ?TrackedFloat
{
if ($impact === 'not implemented') {
return null;
}

/** @var array $impact */
$unit_multiplier = $this->getCriteriaUnits()[$name];
$value = new TrackedFloat(
$impact['value'] * $unit_multiplier,
null,
TrackedFloat::DATA_QUALITY_ESTIMATED
);

return $value;
}

/**
* Show a dropdown of zones handleed by Boaviztapi
*
Expand Down
Loading