| /* |
| htop - AffinityPanel.c |
| (C) 2004-2011 Hisham H. Muhammad |
| Released under the GNU GPLv2+, see the COPYING file |
| in the source distribution for its full text. |
| */ |
| |
| #include "config.h" // IWYU pragma: keep |
| |
| #include "AffinityPanel.h" |
| |
| #include <assert.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "CRT.h" |
| #include "FunctionBar.h" |
| #include "Object.h" |
| #include "ProvideCurses.h" |
| #include "RichString.h" |
| #include "Settings.h" |
| #include "Vector.h" |
| #include "XUtils.h" |
| |
| #ifdef HAVE_LIBHWLOC |
| #include <hwloc.h> |
| #include <hwloc/bitmap.h> |
| #endif |
| |
| |
| typedef struct MaskItem_ { |
| Object super; |
| char* text; |
| char* indent; /* used also as an condition whether this is a tree node */ |
| int value; /* tri-state: 0 - off, 1 - some set, 2 - all set */ |
| int sub_tree; /* tri-state: 0 - no sub-tree, 1 - open sub-tree, 2 - closed sub-tree */ |
| Vector* children; |
| #ifdef HAVE_LIBHWLOC |
| bool ownCpuset; |
| hwloc_bitmap_t cpuset; |
| #else |
| int cpu; |
| #endif |
| } MaskItem; |
| |
| static void MaskItem_delete(Object* cast) { |
| MaskItem* this = (MaskItem*) cast; |
| free(this->text); |
| free(this->indent); |
| Vector_delete(this->children); |
| #ifdef HAVE_LIBHWLOC |
| if (this->ownCpuset) |
| hwloc_bitmap_free(this->cpuset); |
| #endif |
| free(this); |
| } |
| |
| static void MaskItem_display(const Object* cast, RichString* out) { |
| const MaskItem* this = (const MaskItem*)cast; |
| assert (this != NULL); |
| RichString_appendAscii(out, CRT_colors[CHECK_BOX], "["); |
| if (this->value == 2) { |
| RichString_appendAscii(out, CRT_colors[CHECK_MARK], "x"); |
| } else if (this->value == 1) { |
| RichString_appendAscii(out, CRT_colors[CHECK_MARK], "o"); |
| } else { |
| RichString_appendAscii(out, CRT_colors[CHECK_MARK], " "); |
| } |
| RichString_appendAscii(out, CRT_colors[CHECK_BOX], "]"); |
| RichString_appendAscii(out, CRT_colors[CHECK_TEXT], " "); |
| if (this->indent) { |
| RichString_appendWide(out, CRT_colors[PROCESS_TREE], this->indent); |
| RichString_appendWide(out, CRT_colors[PROCESS_TREE], |
| this->sub_tree == 2 |
| ? CRT_treeStr[TREE_STR_OPEN] |
| : CRT_treeStr[TREE_STR_SHUT]); |
| RichString_appendAscii(out, CRT_colors[CHECK_TEXT], " "); |
| } |
| RichString_appendWide(out, CRT_colors[CHECK_TEXT], this->text); |
| } |
| |
| static const ObjectClass MaskItem_class = { |
| .display = MaskItem_display, |
| .delete = MaskItem_delete |
| }; |
| |
| #ifdef HAVE_LIBHWLOC |
| |
| static MaskItem* MaskItem_newMask(const char* text, const char* indent, hwloc_bitmap_t cpuset, bool owner) { |
| MaskItem* this = AllocThis(MaskItem); |
| this->text = xStrdup(text); |
| this->indent = xStrdup(indent); /* nonnull for tree node */ |
| this->value = 0; |
| this->ownCpuset = owner; |
| this->cpuset = cpuset; |
| this->sub_tree = hwloc_bitmap_weight(cpuset) > 1 ? 1 : 0; |
| this->children = Vector_new(Class(MaskItem), true, DEFAULT_SIZE); |
| return this; |
| } |
| |
| #endif |
| |
| static MaskItem* MaskItem_newSingleton(const char* text, int cpu, bool isSet) { |
| MaskItem* this = AllocThis(MaskItem); |
| this->text = xStrdup(text); |
| this->indent = NULL; /* not a tree node */ |
| this->sub_tree = 0; |
| this->children = Vector_new(Class(MaskItem), true, DEFAULT_SIZE); |
| |
| #ifdef HAVE_LIBHWLOC |
| this->ownCpuset = true; |
| this->cpuset = hwloc_bitmap_alloc(); |
| hwloc_bitmap_set(this->cpuset, cpu); |
| #else |
| this->cpu = cpu; |
| #endif |
| this->value = isSet ? 2 : 0; |
| |
| return this; |
| } |
| |
| typedef struct AffinityPanel_ { |
| Panel super; |
| Machine* host; |
| bool topoView; |
| Vector* cpuids; |
| unsigned width; |
| |
| #ifdef HAVE_LIBHWLOC |
| MaskItem* topoRoot; |
| hwloc_const_cpuset_t allCpuset; |
| hwloc_bitmap_t workCpuset; |
| #endif |
| } AffinityPanel; |
| |
| static void AffinityPanel_delete(Object* cast) { |
| AffinityPanel* this = (AffinityPanel*) cast; |
| Panel* super = (Panel*) this; |
| Panel_done(super); |
| Vector_delete(this->cpuids); |
| #ifdef HAVE_LIBHWLOC |
| hwloc_bitmap_free(this->workCpuset); |
| MaskItem_delete((Object*) this->topoRoot); |
| #endif |
| free(this); |
| } |
| |
| #ifdef HAVE_LIBHWLOC |
| |
| static void AffinityPanel_updateItem(AffinityPanel* this, MaskItem* item) { |
| Panel* super = (Panel*) this; |
| |
| item->value = hwloc_bitmap_isincluded(item->cpuset, this->workCpuset) ? 2 : |
| hwloc_bitmap_intersects(item->cpuset, this->workCpuset) ? 1 : 0; |
| |
| Panel_add(super, (Object*) item); |
| } |
| |
| static void AffinityPanel_updateTopo(AffinityPanel* this, MaskItem* item) { |
| AffinityPanel_updateItem(this, item); |
| |
| if (item->sub_tree == 2) |
| return; |
| |
| for (int i = 0; i < Vector_size(item->children); i++) |
| AffinityPanel_updateTopo(this, (MaskItem*) Vector_get(item->children, i)); |
| } |
| |
| #endif |
| |
| static void AffinityPanel_update(AffinityPanel* this, bool keepSelected) { |
| Panel* super = (Panel*) this; |
| |
| FunctionBar_setLabel(super->currentBar, KEY_F(3), this->topoView ? "Collapse/Expand" : ""); |
| |
| int oldSelected = Panel_getSelectedIndex(super); |
| Panel_prune(super); |
| |
| #ifdef HAVE_LIBHWLOC |
| if (this->topoView) { |
| AffinityPanel_updateTopo(this, this->topoRoot); |
| } else { |
| for (int i = 0; i < Vector_size(this->cpuids); i++) { |
| AffinityPanel_updateItem(this, (MaskItem*) Vector_get(this->cpuids, i)); |
| } |
| } |
| #else |
| Panel_splice(super, this->cpuids); |
| #endif |
| |
| if (keepSelected) |
| Panel_setSelected(super, oldSelected); |
| |
| super->needsRedraw = true; |
| } |
| |
| static HandlerResult AffinityPanel_eventHandler(Panel* super, int ch) { |
| AffinityPanel* this = (AffinityPanel*) super; |
| |
| HandlerResult result = IGNORED; |
| MaskItem* selected = (MaskItem*) Panel_getSelected(super); |
| |
| bool keepSelected = true; |
| |
| switch (ch) { |
| case KEY_MOUSE: |
| case KEY_RECLICK: |
| case ' ': |
| if (!selected) { |
| return result; |
| } |
| |
| #ifdef HAVE_LIBHWLOC |
| if (selected->value == 2) { |
| /* Item was selected, so remove this mask from the top cpuset. */ |
| hwloc_bitmap_andnot(this->workCpuset, this->workCpuset, selected->cpuset); |
| selected->value = 0; |
| } else { |
| /* Item was not or only partial selected, so set all bits from this object |
| in the top cpuset. */ |
| hwloc_bitmap_or(this->workCpuset, this->workCpuset, selected->cpuset); |
| selected->value = 2; |
| } |
| #else |
| selected->value = selected->value ? 0 : 2; /* toggle between 0 and 2 */ |
| #endif |
| |
| result = HANDLED; |
| break; |
| |
| #ifdef HAVE_LIBHWLOC |
| |
| case KEY_F(1): |
| hwloc_bitmap_copy(this->workCpuset, this->allCpuset); |
| result = HANDLED; |
| break; |
| |
| case KEY_F(2): |
| this->topoView = !this->topoView; |
| keepSelected = false; |
| |
| result = HANDLED; |
| break; |
| |
| case KEY_F(3): |
| case '-': |
| case '+': |
| if (!selected) { |
| break; |
| } |
| |
| if (selected->sub_tree) { |
| selected->sub_tree = 1 + !(selected->sub_tree - 1); /* toggle between 1 and 2 */ |
| } |
| |
| result = HANDLED; |
| break; |
| |
| #endif |
| |
| case 0x0a: |
| case 0x0d: |
| case KEY_ENTER: |
| result = BREAK_LOOP; |
| break; |
| } |
| |
| if (HANDLED == result) |
| AffinityPanel_update(this, keepSelected); |
| |
| return result; |
| } |
| |
| #ifdef HAVE_LIBHWLOC |
| |
| static MaskItem* AffinityPanel_addObject(AffinityPanel* this, hwloc_obj_t obj, unsigned indent, MaskItem* parent) { |
| const char* type_name = hwloc_obj_type_string(obj->type); |
| const char* index_prefix = "#"; |
| unsigned depth = obj->depth; |
| unsigned index = obj->logical_index; |
| size_t off = 0, left = 10 * depth; |
| char buf[64], indent_buf[left + 1]; |
| |
| if (obj->type == HWLOC_OBJ_PU) { |
| index = Settings_cpuId(this->host->settings, obj->os_index); |
| type_name = "CPU"; |
| index_prefix = ""; |
| } |
| |
| indent_buf[0] = '\0'; |
| if (depth > 0) { |
| for (unsigned i = 1; i < depth; i++) { |
| xSnprintf(&indent_buf[off], left, "%s ", (indent & (1U << i)) ? CRT_treeStr[TREE_STR_VERT] : " "); |
| size_t len = strlen(&indent_buf[off]); |
| off += len; |
| left -= len; |
| } |
| xSnprintf(&indent_buf[off], left, "%s", |
| obj->next_sibling ? CRT_treeStr[TREE_STR_RTEE] : CRT_treeStr[TREE_STR_BEND]); |
| // Uncomment when further appending to indent_buf |
| //size_t len = strlen(&indent_buf[off]); |
| //off += len; |
| //left -= len; |
| } |
| |
| xSnprintf(buf, sizeof(buf), "%s %s%u", type_name, index_prefix, index); |
| |
| MaskItem* item = MaskItem_newMask(buf, indent_buf, obj->complete_cpuset, false); |
| if (parent) |
| Vector_add(parent->children, item); |
| |
| if (item->sub_tree && parent && parent->sub_tree == 1) { |
| /* if obj is fully included or fully excluded, collapse the item */ |
| hwloc_bitmap_t result = hwloc_bitmap_alloc(); |
| hwloc_bitmap_and(result, obj->complete_cpuset, this->workCpuset); |
| int weight = hwloc_bitmap_weight(result); |
| hwloc_bitmap_free(result); |
| if (weight == 0 || weight == (hwloc_bitmap_weight(this->workCpuset) + hwloc_bitmap_weight(obj->complete_cpuset))) { |
| item->sub_tree = 2; |
| } |
| } |
| |
| /* "[x] " + "|- " * depth + ("- ")?(if root node) + name */ |
| unsigned width = 4 + 3 * depth + (2 * !depth) + strlen(buf); |
| if (width > this->width) { |
| this->width = width; |
| } |
| |
| return item; |
| } |
| |
| static MaskItem* AffinityPanel_buildTopology(AffinityPanel* this, hwloc_obj_t obj, unsigned indent, MaskItem* parent) { |
| MaskItem* item = AffinityPanel_addObject(this, obj, indent, parent); |
| if (obj->next_sibling) { |
| indent |= (1U << obj->depth); |
| } else { |
| indent &= ~(1U << obj->depth); |
| } |
| |
| for (unsigned i = 0; i < obj->arity; i++) { |
| AffinityPanel_buildTopology(this, obj->children[i], indent, item); |
| } |
| |
| return parent == NULL ? item : NULL; |
| } |
| |
| #endif |
| |
| const PanelClass AffinityPanel_class = { |
| .super = { |
| .extends = Class(Panel), |
| .delete = AffinityPanel_delete |
| }, |
| .eventHandler = AffinityPanel_eventHandler |
| }; |
| |
| static const char* const AffinityPanelFunctions[] = { |
| "Set ", |
| "Cancel ", |
| #ifdef HAVE_LIBHWLOC |
| "All", |
| "Topology", |
| " ", |
| #endif |
| NULL |
| }; |
| static const char* const AffinityPanelKeys[] = {"Enter", "Esc", "F1", "F2", "F3"}; |
| static const int AffinityPanelEvents[] = {13, 27, KEY_F(1), KEY_F(2), KEY_F(3)}; |
| |
| Panel* AffinityPanel_new(Machine* host, const Affinity* affinity, int* width) { |
| AffinityPanel* this = AllocThis(AffinityPanel); |
| Panel* super = (Panel*) this; |
| Panel_init(super, 1, 1, 1, 1, Class(MaskItem), false, FunctionBar_new(AffinityPanelFunctions, AffinityPanelKeys, AffinityPanelEvents)); |
| |
| this->host = host; |
| /* defaults to 15, this also includes the gap between the panels, |
| * but this will be added by the caller */ |
| this->width = 14; |
| |
| this->cpuids = Vector_new(Class(MaskItem), true, DEFAULT_SIZE); |
| |
| #ifdef HAVE_LIBHWLOC |
| this->topoView = host->settings->topologyAffinity; |
| #else |
| this->topoView = false; |
| #endif |
| |
| #ifdef HAVE_LIBHWLOC |
| this->allCpuset = hwloc_topology_get_complete_cpuset(host->topology); |
| this->workCpuset = hwloc_bitmap_alloc(); |
| #endif |
| |
| Panel_setHeader(super, "Use CPUs:"); |
| |
| unsigned int curCpu = 0; |
| for (unsigned int i = 0; i < host->existingCPUs; i++) { |
| if (!Machine_isCPUonline(host, i)) |
| continue; |
| |
| char number[16]; |
| xSnprintf(number, 9, "CPU %d", Settings_cpuId(host->settings, i)); |
| unsigned cpu_width = 4 + strlen(number); |
| if (cpu_width > this->width) { |
| this->width = cpu_width; |
| } |
| |
| bool isSet = false; |
| if (curCpu < affinity->used && affinity->cpus[curCpu] == i) { |
| #ifdef HAVE_LIBHWLOC |
| hwloc_bitmap_set(this->workCpuset, i); |
| #endif |
| isSet = true; |
| curCpu++; |
| } |
| |
| MaskItem* cpuItem = MaskItem_newSingleton(number, i, isSet); |
| Vector_add(this->cpuids, (Object*) cpuItem); |
| } |
| |
| #ifdef HAVE_LIBHWLOC |
| this->topoRoot = AffinityPanel_buildTopology(this, hwloc_get_root_obj(host->topology), 0, NULL); |
| #endif |
| |
| if (width) { |
| *width = this->width; |
| } |
| |
| AffinityPanel_update(this, false); |
| |
| return super; |
| } |
| |
| Affinity* AffinityPanel_getAffinity(Panel* super, Machine* host) { |
| const AffinityPanel* this = (AffinityPanel*) super; |
| Affinity* affinity = Affinity_new(host); |
| |
| #ifdef HAVE_LIBHWLOC |
| int i; |
| hwloc_bitmap_foreach_begin(i, this->workCpuset) |
| Affinity_add(affinity, (unsigned)i); |
| hwloc_bitmap_foreach_end(); |
| #else |
| for (int i = 0; i < Vector_size(this->cpuids); i++) { |
| const MaskItem* item = (const MaskItem*)Vector_get(this->cpuids, i); |
| if (item->value) { |
| Affinity_add(affinity, item->cpu); |
| } |
| } |
| #endif |
| |
| return affinity; |
| } |