xref: /openbmc/phosphor-fan-presence/control/json/actions/target_from_group_max.cpp (revision 1b3bcc3240277b0dcfd8c65e693f9c8f7024c851)
1  /**
2   * Copyright © 2022 Ampere Computing
3   *
4   * Licensed under the Apache License, Version 2.0 (the "License");
5   * you may not use this file except in compliance with the License.
6   * You may obtain a copy of the License at
7   *
8   *     http://www.apache.org/licenses/LICENSE-2.0
9   *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  #include "target_from_group_max.hpp"
17  
18  #include "../manager.hpp"
19  
20  #include <format>
21  #include <iostream>
22  
23  namespace phosphor::fan::control::json
24  {
25  
26  std::map<size_t, uint64_t> TargetFromGroupMax::_speedFromGroupsMap;
27  size_t TargetFromGroupMax::_groupIndexCounter = 0;
28  
29  using json = nlohmann::json;
30  using namespace phosphor::logging;
31  
32  TargetFromGroupMax::TargetFromGroupMax(const json& jsonObj,
33                                         const std::vector<Group>& groups) :
34      ActionBase(jsonObj, groups)
35  {
36      setHysteresis(jsonObj);
37      setMap(jsonObj);
38      setIndex();
39  }
40  
41  void TargetFromGroupMax::run(Zone& zone)
42  {
43      // Holds the max property value of groups
44      auto maxGroup = processGroups();
45  
46      // Group with non-numeric property value will be skipped from processing
47      if (maxGroup)
48      {
49          /*The maximum property value from the group*/
50          uint64_t groupValue =
51              static_cast<uint64_t>(std::get<double>(maxGroup.value()));
52  
53          // Only check if previous and new values differ
54          if (groupValue != _prevGroupValue)
55          {
56              // Value is decreasing and the change is greater than the
57              // positive hysteresis; or value is increasing and the change
58              // is greater than the positive hysteresis
59              if (((groupValue < _prevGroupValue) &&
60                   (_prevGroupValue - groupValue > _posHysteresis)) ||
61                  ((groupValue > _prevGroupValue) &&
62                   (groupValue - _prevGroupValue > _negHysteresis)))
63              {
64                  /*The speed derived from mapping*/
65                  uint64_t groupSpeed = _speedFromGroupsMap[_groupIndex];
66  
67                  // Looping through the mapping table
68                  for (auto it = _valueToSpeedMap.begin();
69                       it != _valueToSpeedMap.end(); ++it)
70                  {
71                      // Value is at/below the first map key, or at/above the
72                      // last map key, set speed to the first or the last map
73                      // key's value respectively
74                      if (((it == _valueToSpeedMap.begin()) &&
75                           (groupValue <= it->first)) ||
76                          ((std::next(it, 1) == _valueToSpeedMap.end()) &&
77                           (groupValue >= it->first)))
78                      {
79                          groupSpeed = it->second;
80                          break;
81                      }
82                      // Value transitioned across a map key, update the speed
83                      // to this map key's value when the new group value is at
84                      // or above the map's key and below the next key
85                      if (groupValue >= it->first &&
86                          groupValue < std::next(it, 1)->first)
87                      {
88                          groupSpeed = it->second;
89                          break;
90                      }
91                  }
92                  _prevGroupValue = groupValue;
93                  _speedFromGroupsMap[_groupIndex] = groupSpeed;
94              }
95          }
96          // Get the maximum speed derived from all groups, and set target
97          // for the Zone
98          auto maxSpeedFromGroupsIter = std::max_element(
99              _speedFromGroupsMap.begin(), _speedFromGroupsMap.end(),
100              [](const auto& x, const auto& y) { return x.second < y.second; });
101  
102          zone.setTarget(maxSpeedFromGroupsIter->second);
103      }
104      else
105      {
106          // std::cerr << "Failed to process groups for " << ActionBase::getName()
107          //           << ": Further processing will be skipped \n";
108      }
109  }
110  
111  void TargetFromGroupMax::setHysteresis(const json& jsonObj)
112  {
113      if (!jsonObj.contains("neg_hysteresis") ||
114          !jsonObj.contains("pos_hysteresis"))
115      {
116          throw ActionParseError{
117              ActionBase::getName(),
118              "Missing required neg_hysteresis or pos_hysteresis value"};
119      }
120      _negHysteresis = jsonObj["neg_hysteresis"].get<uint64_t>();
121      _posHysteresis = jsonObj["pos_hysteresis"].get<uint64_t>();
122  }
123  
124  void TargetFromGroupMax::setIndex()
125  {
126      _groupIndex = _groupIndexCounter;
127      // Initialize the map of each group and their max values
128      _speedFromGroupsMap[_groupIndex] = 0;
129  
130      // Increase the index counter by one to specify the next group key
131      _groupIndexCounter += 1;
132  }
133  
134  void TargetFromGroupMax::setMap(const json& jsonObj)
135  {
136      if (jsonObj.contains("map"))
137      {
138          for (const auto& map : jsonObj.at("map"))
139          {
140              if (!map.contains("value") || !map.contains("target"))
141              {
142                  throw ActionParseError{ActionBase::getName(),
143                                         "Missing value or target in map"};
144              }
145              else
146              {
147                  uint64_t val = map["value"].get<uint64_t>();
148                  uint64_t target = map["target"].get<uint64_t>();
149                  _valueToSpeedMap.insert(
150                      std::pair<uint64_t, uint64_t>(val, target));
151              }
152          }
153      }
154  
155      else
156      {
157          throw ActionParseError{ActionBase::getName(), "Missing required map"};
158      }
159  }
160  
161  std::optional<PropertyVariantType> TargetFromGroupMax::processGroups()
162  {
163      // Holds the max property value of groups
164      std::optional<PropertyVariantType> max;
165  
166      for (const auto& group : _groups)
167      {
168          const auto& members = group.getMembers();
169          for (const auto& member : members)
170          {
171              PropertyVariantType value;
172              bool invalid = false;
173              try
174              {
175                  value = Manager::getObjValueVariant(
176                      member, group.getInterface(), group.getProperty());
177              }
178              catch (const std::out_of_range&)
179              {
180                  continue;
181              }
182  
183              // Only allow a group members to be
184              // numeric. Unlike with std::is_arithmetic, bools are not
185              // considered numeric here.
186              std::visit(
187                  [&group, &invalid, this](auto&& val) {
188                  using V = std::decay_t<decltype(val)>;
189                  if constexpr (!std::is_same_v<double, V> &&
190                                !std::is_same_v<int32_t, V> &&
191                                !std::is_same_v<int64_t, V>)
192                  {
193                      log<level::ERR>(std::format("{}: Group {}'s member "
194                                                  "isn't numeric",
195                                                  ActionBase::getName(),
196                                                  group.getName())
197                                          .c_str());
198                      invalid = true;
199                  }
200              },
201                  value);
202              if (invalid)
203              {
204                  break;
205              }
206  
207              if (max && (value > max))
208              {
209                  max = value;
210              }
211              else if (!max)
212              {
213                  max = value;
214              }
215          }
216      }
217      return max;
218  }
219  
220  } // namespace phosphor::fan::control::json
221