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