0
0
Fork 0
mirror of https://github.com/netdata/netdata.git synced 2025-04-29 07:00:01 +00:00

remove python.d/boinc ()

This commit is contained in:
Ilya Mashchenko 2024-08-23 18:41:28 +03:00 committed by GitHub
parent 96fc4d5c16
commit dde8e19a7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 0 additions and 1188 deletions
CMakeLists.txt
src/collectors/python.d.plugin

View file

@ -2831,7 +2831,6 @@ install(FILES src/collectors/python.d.plugin/python.d.conf
install(FILES
src/collectors/python.d.plugin/am2320/am2320.conf
src/collectors/python.d.plugin/anomalies/anomalies.conf
src/collectors/python.d.plugin/boinc/boinc.conf
src/collectors/python.d.plugin/ceph/ceph.conf
src/collectors/python.d.plugin/go_expvar/go_expvar.conf
src/collectors/python.d.plugin/haproxy/haproxy.conf
@ -2850,7 +2849,6 @@ install(FILES
install(FILES
src/collectors/python.d.plugin/am2320/am2320.chart.py
src/collectors/python.d.plugin/anomalies/anomalies.chart.py
src/collectors/python.d.plugin/boinc/boinc.chart.py
src/collectors/python.d.plugin/ceph/ceph.chart.py
src/collectors/python.d.plugin/go_expvar/go_expvar.chart.py
src/collectors/python.d.plugin/haproxy/haproxy.chart.py

View file

@ -1 +0,0 @@
integrations/boinc.md

View file

@ -1,168 +0,0 @@
# -*- coding: utf-8 -*-
# Description: BOINC netdata python.d module
# Author: Austin S. Hemmelgarn (Ferroin)
# SPDX-License-Identifier: GPL-3.0-or-later
import socket
from bases.FrameworkServices.SimpleService import SimpleService
from third_party import boinc_client
ORDER = [
'tasks',
'states',
'sched_states',
'process_states',
]
CHARTS = {
'tasks': {
'options': [None, 'Overall Tasks', 'tasks', 'boinc', 'boinc.tasks', 'line'],
'lines': [
['total', 'Total', 'absolute', 1, 1],
['active', 'Active', 'absolute', 1, 1]
]
},
'states': {
'options': [None, 'Tasks per State', 'tasks', 'boinc', 'boinc.states', 'line'],
'lines': [
['new', 'New', 'absolute', 1, 1],
['downloading', 'Downloading', 'absolute', 1, 1],
['downloaded', 'Ready to Run', 'absolute', 1, 1],
['comperror', 'Compute Errors', 'absolute', 1, 1],
['uploading', 'Uploading', 'absolute', 1, 1],
['uploaded', 'Uploaded', 'absolute', 1, 1],
['aborted', 'Aborted', 'absolute', 1, 1],
['upload_failed', 'Failed Uploads', 'absolute', 1, 1]
]
},
'sched_states': {
'options': [None, 'Tasks per Scheduler State', 'tasks', 'boinc', 'boinc.sched', 'line'],
'lines': [
['uninit_sched', 'Uninitialized', 'absolute', 1, 1],
['preempted', 'Preempted', 'absolute', 1, 1],
['scheduled', 'Scheduled', 'absolute', 1, 1]
]
},
'process_states': {
'options': [None, 'Tasks per Process State', 'tasks', 'boinc', 'boinc.process', 'line'],
'lines': [
['uninit_proc', 'Uninitialized', 'absolute', 1, 1],
['executing', 'Executing', 'absolute', 1, 1],
['suspended', 'Suspended', 'absolute', 1, 1],
['aborting', 'Aborted', 'absolute', 1, 1],
['quit', 'Quit', 'absolute', 1, 1],
['copy_pending', 'Copy Pending', 'absolute', 1, 1]
]
}
}
# A simple template used for pre-loading the return dictionary to make
# the _get_data() method simpler.
_DATA_TEMPLATE = {
'total': 0,
'active': 0,
'new': 0,
'downloading': 0,
'downloaded': 0,
'comperror': 0,
'uploading': 0,
'uploaded': 0,
'aborted': 0,
'upload_failed': 0,
'uninit_sched': 0,
'preempted': 0,
'scheduled': 0,
'uninit_proc': 0,
'executing': 0,
'suspended': 0,
'aborting': 0,
'quit': 0,
'copy_pending': 0
}
# Map task states to dimensions
_TASK_MAP = {
boinc_client.ResultState.NEW: 'new',
boinc_client.ResultState.FILES_DOWNLOADING: 'downloading',
boinc_client.ResultState.FILES_DOWNLOADED: 'downloaded',
boinc_client.ResultState.COMPUTE_ERROR: 'comperror',
boinc_client.ResultState.FILES_UPLOADING: 'uploading',
boinc_client.ResultState.FILES_UPLOADED: 'uploaded',
boinc_client.ResultState.ABORTED: 'aborted',
boinc_client.ResultState.UPLOAD_FAILED: 'upload_failed'
}
# Map scheduler states to dimensions
_SCHED_MAP = {
boinc_client.CpuSched.UNINITIALIZED: 'uninit_sched',
boinc_client.CpuSched.PREEMPTED: 'preempted',
boinc_client.CpuSched.SCHEDULED: 'scheduled',
}
# Maps process states to dimensions
_PROC_MAP = {
boinc_client.Process.UNINITIALIZED: 'uninit_proc',
boinc_client.Process.EXECUTING: 'executing',
boinc_client.Process.SUSPENDED: 'suspended',
boinc_client.Process.ABORT_PENDING: 'aborted',
boinc_client.Process.QUIT_PENDING: 'quit',
boinc_client.Process.COPY_PENDING: 'copy_pending'
}
class Service(SimpleService):
def __init__(self, configuration=None, name=None):
SimpleService.__init__(self, configuration=configuration, name=name)
self.order = ORDER
self.definitions = CHARTS
self.host = self.configuration.get('host', 'localhost')
self.port = self.configuration.get('port', 0)
self.password = self.configuration.get('password', '')
self.client = boinc_client.BoincClient(host=self.host, port=self.port, passwd=self.password)
self.alive = False
def check(self):
return self.connect()
def connect(self):
self.client.connect()
self.alive = self.client.connected and self.client.authorized
return self.alive
def reconnect(self):
# The client class itself actually disconnects existing
# connections when it is told to connect, so we don't need to
# explicitly disconnect when we're just trying to reconnect.
return self.connect()
def is_alive(self):
if not self.alive:
return self.reconnect()
return True
def _get_data(self):
if not self.is_alive():
return None
data = dict(_DATA_TEMPLATE)
try:
results = self.client.get_tasks()
except socket.error:
self.error('Connection is dead')
self.alive = False
return None
for task in results:
data['total'] += 1
data[_TASK_MAP[task.state]] += 1
try:
if task.active_task:
data['active'] += 1
data[_SCHED_MAP[task.scheduler_state]] += 1
data[_PROC_MAP[task.active_task_state]] += 1
except AttributeError:
pass
return data or None

View file

@ -1,66 +0,0 @@
# netdata python.d.plugin configuration for boinc
#
# This file is in YaML format. Generally the format is:
#
# name: value
#
# There are 2 sections:
# - global variables
# - one or more JOBS
#
# JOBS allow you to collect values from multiple sources.
# Each source will have its own set of charts.
#
# JOB parameters have to be indented (using spaces only, example below).
# ----------------------------------------------------------------------
# Global Variables
# These variables set the defaults for all JOBs, however each JOB
# may define its own, overriding the defaults.
# update_every sets the default data collection frequency.
# If unset, the python.d.plugin default is used.
# update_every: 1
# priority controls the order of charts at the netdata dashboard.
# Lower numbers move the charts towards the top of the page.
# If unset, the default for python.d.plugin is used.
# priority: 60000
# penalty indicates whether to apply penalty to update_every in case of failures.
# Penalty will increase every 5 failed updates in a row. Maximum penalty is 10 minutes.
# penalty: yes
# autodetection_retry sets the job re-check interval in seconds.
# The job is not deleted if check fails.
# Attempts to start the job are made once every autodetection_retry.
# This feature is disabled by default.
# autodetection_retry: 0
# ----------------------------------------------------------------------
# JOBS (data collection sources)
#
# The default JOBS share the same *name*. JOBS with the same name
# are mutually exclusive. Only one of them will be allowed running at
# any time. This allows autodetection to try several alternatives and
# pick the one that works.
#
# Any number of jobs is supported.
#
# All python.d.plugin JOBS (for all its modules) support a set of
# predefined parameters. These are:
#
# job_name:
# name: myname # the JOB's name as it will appear at the
# # dashboard (by default is the job_name)
# # JOBs sharing a name are mutually exclusive
# update_every: 1 # the JOB's data collection frequency
# priority: 60000 # the JOB's order on the dashboard
# penalty: yes # the JOB's penalty
# autodetection_retry: 0 # the JOB's re-check interval in seconds
#
# Additionally to the above, boinc also supports the following:
#
# hostname: localhost # The host running the BOINC client
# port: 31416 # The remote GUI RPC port for BOINC
# password: '' # The remote GUI RPC password

View file

@ -1,238 +0,0 @@
<!--startmeta
custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/boinc/README.md"
meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/boinc/metadata.yaml"
sidebar_label: "BOINC"
learn_status: "Published"
learn_rel_path: "Collecting Metrics/Distributed Computing Systems"
most_popular: False
message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
endmeta-->
# BOINC
<img src="https://netdata.cloud/img/bolt.svg" width="150"/>
Plugin: python.d.plugin
Module: boinc
<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
## Overview
This collector monitors task counts for the Berkeley Open Infrastructure Networking Computing (BOINC) distributed computing client.
It uses the same RPC interface that the BOINC monitoring GUI does.
This collector is supported on all platforms.
This collector supports collecting metrics from multiple instances of this integration, including remote instances.
### Default Behavior
#### Auto-Detection
By default, the module will try to auto-detect the password to the RPC interface by looking in `/var/lib/boinc` for this file (this is the location most Linux distributions use for a system-wide BOINC installation), so things may just work without needing configuration for a local system.
#### Limits
The default configuration for this integration does not impose any limits on data collection.
#### Performance Impact
The default configuration for this integration is not expected to impose a significant performance impact on the system.
## Metrics
Metrics grouped by *scope*.
The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
### Per BOINC instance
These metrics refer to the entire monitored application.
This scope has no labels.
Metrics:
| Metric | Dimensions | Unit |
|:------|:----------|:----|
| boinc.tasks | Total, Active | tasks |
| boinc.states | New, Downloading, Ready to Run, Compute Errors, Uploading, Uploaded, Aborted, Failed Uploads | tasks |
| boinc.sched | Uninitialized, Preempted, Scheduled | tasks |
| boinc.process | Uninitialized, Executing, Suspended, Aborted, Quit, Copy Pending | tasks |
## Alerts
The following alerts are available:
| Alert name | On metric | Description |
|:------------|:----------|:------------|
| [ boinc_total_tasks ](https://github.com/netdata/netdata/blob/master/src/health/health.d/boinc.conf) | boinc.tasks | average number of total tasks over the last 10 minutes |
| [ boinc_active_tasks ](https://github.com/netdata/netdata/blob/master/src/health/health.d/boinc.conf) | boinc.tasks | average number of active tasks over the last 10 minutes |
| [ boinc_compute_errors ](https://github.com/netdata/netdata/blob/master/src/health/health.d/boinc.conf) | boinc.states | average number of compute errors over the last 10 minutes |
| [ boinc_upload_errors ](https://github.com/netdata/netdata/blob/master/src/health/health.d/boinc.conf) | boinc.states | average number of failed uploads over the last 10 minutes |
## Setup
### Prerequisites
#### Boinc RPC interface
BOINC requires use of a password to access it's RPC interface. You can find this password in the `gui_rpc_auth.cfg` file in your BOINC directory.
### Configuration
#### File
The configuration file name for this integration is `python.d/boinc.conf`.
You can edit the configuration file using the `edit-config` script from the
Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
```bash
cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
sudo ./edit-config python.d/boinc.conf
```
#### Options
There are 2 sections:
* Global variables
* One or more JOBS that can define multiple different instances to monitor.
The following options can be defined globally: priority, penalty, autodetection_retry, update_every, but can also be defined per JOB to override the global values.
Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
<details open><summary>Config options</summary>
| Name | Description | Default | Required |
|:----|:-----------|:-------|:--------:|
| update_every | Sets the default data collection frequency. | 5 | no |
| priority | Controls the order of charts at the netdata dashboard. | 60000 | no |
| autodetection_retry | Sets the job re-check interval in seconds. | 0 | no |
| penalty | Indicates whether to apply penalty to update_every in case of failures. | yes | no |
| name | Job name. This value will overwrite the `job_name` value. JOBS with the same name are mutually exclusive. Only one of them will be allowed running at any time. This allows autodetection to try several alternatives and pick the one that works. | | no |
| hostname | Define a hostname where boinc is running. | localhost | no |
| port | The port of boinc RPC interface. | | no |
| password | Provide a password to connect to a boinc RPC interface. | | no |
</details>
#### Examples
##### Configuration of a remote boinc instance
A basic JOB configuration for a remote boinc instance
```yaml
remote:
hostname: '1.2.3.4'
port: 1234
password: 'some-password'
```
##### Multi-instance
> **Note**: When you define multiple jobs, their names must be unique.
Collecting metrics from local and remote instances.
<details open><summary>Config</summary>
```yaml
localhost:
name: 'local'
host: '127.0.0.1'
port: 1234
password: 'some-password'
remote_job:
name: 'remote'
host: '192.0.2.1'
port: 1234
password: some-other-password
```
</details>
## Troubleshooting
### Debug Mode
To troubleshoot issues with the `boinc` collector, run the `python.d.plugin` with the debug option enabled. The output
should give you clues as to why the collector isn't working.
- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
```bash
cd /usr/libexec/netdata/plugins.d/
```
- Switch to the `netdata` user.
```bash
sudo -u netdata -s
```
- Run the `python.d.plugin` to debug the collector:
```bash
./python.d.plugin boinc debug trace
```
### Getting Logs
If you're encountering problems with the `boinc` collector, follow these steps to retrieve logs and identify potential issues:
- **Run the command** specific to your system (systemd, non-systemd, or Docker container).
- **Examine the output** for any warnings or error messages that might indicate issues. These messages should provide clues about the root cause of the problem.
#### System with systemd
Use the following command to view logs generated since the last Netdata service restart:
```bash
journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep boinc
```
#### System without systemd
Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name:
```bash
grep boinc /var/log/netdata/collector.log
```
**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues.
#### Docker Container
If your Netdata runs in a Docker container named "netdata" (replace if different), use this command:
```bash
docker logs netdata 2>&1 | grep boinc
```

View file

@ -1,198 +0,0 @@
plugin_name: python.d.plugin
modules:
- meta:
plugin_name: python.d.plugin
module_name: boinc
monitored_instance:
name: BOINC
link: "https://boinc.berkeley.edu/"
categories:
- data-collection.distributed-computing-systems
icon_filename: "bolt.svg"
related_resources:
integrations:
list: []
info_provided_to_referring_integrations:
description: ""
keywords:
- boinc
- distributed
most_popular: false
overview:
data_collection:
metrics_description: "This collector monitors task counts for the Berkeley Open Infrastructure Networking Computing (BOINC) distributed computing client."
method_description: "It uses the same RPC interface that the BOINC monitoring GUI does."
supported_platforms:
include: []
exclude: []
multi_instance: true
additional_permissions:
description: ""
default_behavior:
auto_detection:
description: "By default, the module will try to auto-detect the password to the RPC interface by looking in `/var/lib/boinc` for this file (this is the location most Linux distributions use for a system-wide BOINC installation), so things may just work without needing configuration for a local system."
limits:
description: ""
performance_impact:
description: ""
setup:
prerequisites:
list:
- title: "Boinc RPC interface"
description: BOINC requires use of a password to access it's RPC interface. You can find this password in the `gui_rpc_auth.cfg` file in your BOINC directory.
configuration:
file:
name: python.d/boinc.conf
options:
description: |
There are 2 sections:
* Global variables
* One or more JOBS that can define multiple different instances to monitor.
The following options can be defined globally: priority, penalty, autodetection_retry, update_every, but can also be defined per JOB to override the global values.
Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
folding:
title: "Config options"
enabled: true
list:
- name: update_every
description: Sets the default data collection frequency.
default_value: 5
required: false
- name: priority
description: Controls the order of charts at the netdata dashboard.
default_value: 60000
required: false
- name: autodetection_retry
description: Sets the job re-check interval in seconds.
default_value: 0
required: false
- name: penalty
description: Indicates whether to apply penalty to update_every in case of failures.
default_value: yes
required: false
- name: name
description: Job name. This value will overwrite the `job_name` value. JOBS with the same name are mutually exclusive. Only one of them will be allowed running at any time. This allows autodetection to try several alternatives and pick the one that works.
default_value: ""
required: false
- name: hostname
description: Define a hostname where boinc is running.
default_value: "localhost"
required: false
- name: port
description: The port of boinc RPC interface.
default_value: ""
required: false
- name: password
description: Provide a password to connect to a boinc RPC interface.
default_value: ""
required: false
examples:
folding:
enabled: true
title: "Config"
list:
- name: Configuration of a remote boinc instance
description: A basic JOB configuration for a remote boinc instance
folding:
enabled: false
config: |
remote:
hostname: '1.2.3.4'
port: 1234
password: 'some-password'
- name: Multi-instance
description: |
> **Note**: When you define multiple jobs, their names must be unique.
Collecting metrics from local and remote instances.
config: |
localhost:
name: 'local'
host: '127.0.0.1'
port: 1234
password: 'some-password'
remote_job:
name: 'remote'
host: '192.0.2.1'
port: 1234
password: some-other-password
troubleshooting:
problems:
list: []
alerts:
- name: boinc_total_tasks
link: https://github.com/netdata/netdata/blob/master/src/health/health.d/boinc.conf
metric: boinc.tasks
info: average number of total tasks over the last 10 minutes
os: "*"
- name: boinc_active_tasks
link: https://github.com/netdata/netdata/blob/master/src/health/health.d/boinc.conf
metric: boinc.tasks
info: average number of active tasks over the last 10 minutes
os: "*"
- name: boinc_compute_errors
link: https://github.com/netdata/netdata/blob/master/src/health/health.d/boinc.conf
metric: boinc.states
info: average number of compute errors over the last 10 minutes
os: "*"
- name: boinc_upload_errors
link: https://github.com/netdata/netdata/blob/master/src/health/health.d/boinc.conf
metric: boinc.states
info: average number of failed uploads over the last 10 minutes
os: "*"
metrics:
folding:
title: Metrics
enabled: false
description: ""
availability: []
scopes:
- name: global
description: "These metrics refer to the entire monitored application."
labels: []
metrics:
- name: boinc.tasks
description: Overall Tasks
unit: "tasks"
chart_type: line
dimensions:
- name: Total
- name: Active
- name: boinc.states
description: Tasks per State
unit: "tasks"
chart_type: line
dimensions:
- name: New
- name: Downloading
- name: Ready to Run
- name: Compute Errors
- name: Uploading
- name: Uploaded
- name: Aborted
- name: Failed Uploads
- name: boinc.sched
description: Tasks per Scheduler State
unit: "tasks"
chart_type: line
dimensions:
- name: Uninitialized
- name: Preempted
- name: Scheduled
- name: boinc.process
description: Tasks per Process State
unit: "tasks"
chart_type: line
dimensions:
- name: Uninitialized
- name: Executing
- name: Suspended
- name: Aborted
- name: Quit
- name: Copy Pending

View file

@ -1,515 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# client.py - Somewhat higher-level GUI_RPC API for BOINC core client
#
# Copyright (C) 2013 Rodrigo Silva (MestreLion) <linux@rodrigosilva.com>
# Copyright (C) 2017 Austin S. Hemmelgarn
#
# SPDX-License-Identifier: GPL-3.0
# Based on client/boinc_cmd.cpp
import hashlib
import socket
import sys
import time
from functools import total_ordering
from xml.etree import ElementTree
GUI_RPC_PASSWD_FILE = "/var/lib/boinc/gui_rpc_auth.cfg"
GUI_RPC_HOSTNAME = None # localhost
GUI_RPC_PORT = 31416
GUI_RPC_TIMEOUT = 1
class Rpc(object):
''' Class to perform GUI RPC calls to a BOINC core client.
Usage in a context manager ('with' block) is recommended to ensure
disconnect() is called. Using the same instance for all calls is also
recommended so it reuses the same socket connection
'''
def __init__(self, hostname="", port=0, timeout=0, text_output=False):
self.hostname = hostname
self.port = port
self.timeout = timeout
self.sock = None
self.text_output = text_output
@property
def sockargs(self):
return (self.hostname, self.port, self.timeout)
def __enter__(self): self.connect(*self.sockargs); return self
def __exit__(self, *args): self.disconnect()
def connect(self, hostname="", port=0, timeout=0):
''' Connect to (hostname, port) with timeout in seconds.
Hostname defaults to None (localhost), and port to 31416
Calling multiple times will disconnect previous connection (if any),
and (re-)connect to host.
'''
if self.sock:
self.disconnect()
self.hostname = hostname or GUI_RPC_HOSTNAME
self.port = port or GUI_RPC_PORT
self.timeout = timeout or GUI_RPC_TIMEOUT
self.sock = socket.create_connection(self.sockargs[0:2], self.sockargs[2])
def disconnect(self):
''' Disconnect from host. Calling multiple times is OK (idempotent)
'''
if self.sock:
self.sock.close()
self.sock = None
def call(self, request, text_output=None):
''' Do an RPC call. Pack and send the XML request and return the
unpacked reply. request can be either plain XML text or a
xml.etree.ElementTree.Element object. Return ElementTree.Element
or XML text according to text_output flag.
Will auto-connect if not connected.
'''
if text_output is None:
text_output = self.text_output
if not self.sock:
self.connect(*self.sockargs)
if not isinstance(request, ElementTree.Element):
request = ElementTree.fromstring(request)
# pack request
end = '\003'
if sys.version_info[0] < 3:
req = "<boinc_gui_rpc_request>\n{0}\n</boinc_gui_rpc_request>\n{1}".format(ElementTree.tostring(request).replace(' />', '/>'), end)
else:
req = "<boinc_gui_rpc_request>\n{0}\n</boinc_gui_rpc_request>\n{1}".format(ElementTree.tostring(request, encoding='unicode').replace(' />', '/>'), end).encode()
try:
self.sock.sendall(req)
except (socket.error, socket.herror, socket.gaierror, socket.timeout):
raise
req = ""
while True:
try:
buf = self.sock.recv(8192)
if not buf:
raise socket.error("No data from socket")
if sys.version_info[0] >= 3:
buf = buf.decode()
except socket.error:
raise
n = buf.find(end)
if not n == -1: break
req += buf
req += buf[:n]
# unpack reply (remove root tag, ie: first and last lines)
req = '\n'.join(req.strip().rsplit('\n')[1:-1])
if text_output:
return req
else:
return ElementTree.fromstring(req)
def setattrs_from_xml(obj, xml, attrfuncdict={}):
''' Helper to set values for attributes of a class instance by mapping
matching tags from a XML file.
attrfuncdict is a dict of functions to customize value data type of
each attribute. It falls back to simple int/float/bool/str detection
based on values defined in __init__(). This would not be needed if
Boinc used standard RPC protocol, which includes data type in XML.
'''
if not isinstance(xml, ElementTree.Element):
xml = ElementTree.fromstring(xml)
for e in list(xml):
if hasattr(obj, e.tag):
attr = getattr(obj, e.tag)
attrfunc = attrfuncdict.get(e.tag, None)
if attrfunc is None:
if isinstance(attr, bool): attrfunc = parse_bool
elif isinstance(attr, int): attrfunc = parse_int
elif isinstance(attr, float): attrfunc = parse_float
elif isinstance(attr, str): attrfunc = parse_str
elif isinstance(attr, list): attrfunc = parse_list
else: attrfunc = lambda x: x
setattr(obj, e.tag, attrfunc(e))
else:
pass
#print "class missing attribute '%s': %r" % (e.tag, obj)
return obj
def parse_bool(e):
''' Helper to convert ElementTree.Element.text to boolean.
Treat '<foo/>' (and '<foo>[[:blank:]]</foo>') as True
Treat '0' and 'false' as False
'''
if e.text is None:
return True
else:
return bool(e.text) and not e.text.strip().lower() in ('0', 'false')
def parse_int(e):
''' Helper to convert ElementTree.Element.text to integer.
Treat '<foo/>' (and '<foo></foo>') as 0
'''
# int(float()) allows casting to int a value expressed as float in XML
return 0 if e.text is None else int(float(e.text.strip()))
def parse_float(e):
''' Helper to convert ElementTree.Element.text to float. '''
return 0.0 if e.text is None else float(e.text.strip())
def parse_str(e):
''' Helper to convert ElementTree.Element.text to string. '''
return "" if e.text is None else e.text.strip()
def parse_list(e):
''' Helper to convert ElementTree.Element to list. For now, simply return
the list of root element's children
'''
return list(e)
class Enum(object):
UNKNOWN = -1 # Not in original API
@classmethod
def name(cls, value):
''' Quick-and-dirty fallback for getting the "name" of an enum item '''
# value as string, if it matches an enum attribute.
# Allows short usage as Enum.name("VALUE") besides Enum.name(Enum.VALUE)
if hasattr(cls, str(value)):
return cls.name(getattr(cls, value, None))
# value not handled in subclass name()
for k, v in cls.__dict__.items():
if v == value:
return k.lower().replace('_', ' ')
# value not found
return cls.name(Enum.UNKNOWN)
class CpuSched(Enum):
''' values of ACTIVE_TASK::scheduler_state and ACTIVE_TASK::next_scheduler_state
"SCHEDULED" is synonymous with "executing" except when CPU throttling
is in use.
'''
UNINITIALIZED = 0
PREEMPTED = 1
SCHEDULED = 2
class ResultState(Enum):
''' Values of RESULT::state in client.
THESE MUST BE IN NUMERICAL ORDER
(because of the > comparison in RESULT::computing_done())
see html/inc/common_defs.inc
'''
NEW = 0
#// New result
FILES_DOWNLOADING = 1
#// Input files for result (WU, app version) are being downloaded
FILES_DOWNLOADED = 2
#// Files are downloaded, result can be (or is being) computed
COMPUTE_ERROR = 3
#// computation failed; no file upload
FILES_UPLOADING = 4
#// Output files for result are being uploaded
FILES_UPLOADED = 5
#// Files are uploaded, notify scheduling server at some point
ABORTED = 6
#// result was aborted
UPLOAD_FAILED = 7
#// some output file permanent failure
class Process(Enum):
''' values of ACTIVE_TASK::task_state '''
UNINITIALIZED = 0
#// process doesn't exist yet
EXECUTING = 1
#// process is running, as far as we know
SUSPENDED = 9
#// we've sent it a "suspend" message
ABORT_PENDING = 5
#// process exceeded limits; send "abort" message, waiting to exit
QUIT_PENDING = 8
#// we've sent it a "quit" message, waiting to exit
COPY_PENDING = 10
#// waiting for async file copies to finish
class _Struct(object):
''' base helper class with common methods for all classes derived from
BOINC's C++ structs
'''
@classmethod
def parse(cls, xml):
return setattrs_from_xml(cls(), xml)
def __str__(self, indent=0):
buf = '{0}{1}:\n'.format('\t' * indent, self.__class__.__name__)
for attr in self.__dict__:
value = getattr(self, attr)
if isinstance(value, list):
buf += '{0}\t{1} [\n'.format('\t' * indent, attr)
for v in value: buf += '\t\t{0}\t\t,\n'.format(v)
buf += '\t]\n'
else:
buf += '{0}\t{1}\t{2}\n'.format('\t' * indent,
attr,
value.__str__(indent+2)
if isinstance(value, _Struct)
else repr(value))
return buf
@total_ordering
class VersionInfo(_Struct):
def __init__(self, major=0, minor=0, release=0):
self.major = major
self.minor = minor
self.release = release
@property
def _tuple(self):
return (self.major, self.minor, self.release)
def __eq__(self, other):
return isinstance(other, self.__class__) and self._tuple == other._tuple
def __ne__(self, other):
return not self.__eq__(other)
def __gt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self._tuple > other._tuple
def __str__(self):
return "{0}.{1}.{2}".format(self.major, self.minor, self.release)
def __repr__(self):
return "{0}{1}".format(self.__class__.__name__, self._tuple)
class Result(_Struct):
''' Also called "task" in some contexts '''
def __init__(self):
# Names and values follow lib/gui_rpc_client.h @ RESULT
# Order too, except when grouping contradicts client/result.cpp
# RESULT::write_gui(), then XML order is used.
self.name = ""
self.wu_name = ""
self.version_num = 0
#// identifies the app used
self.plan_class = ""
self.project_url = "" # from PROJECT.master_url
self.report_deadline = 0.0 # seconds since epoch
self.received_time = 0.0 # seconds since epoch
#// when we got this from server
self.ready_to_report = False
#// we're ready to report this result to the server;
#// either computation is done and all the files have been uploaded
#// or there was an error
self.got_server_ack = False
#// we've received the ack for this result from the server
self.final_cpu_time = 0.0
self.final_elapsed_time = 0.0
self.state = ResultState.NEW
self.estimated_cpu_time_remaining = 0.0
#// actually, estimated elapsed time remaining
self.exit_status = 0
#// return value from the application
self.suspended_via_gui = False
self.project_suspended_via_gui = False
self.edf_scheduled = False
#// temporary used to tell GUI that this result is deadline-scheduled
self.coproc_missing = False
#// a coproc needed by this job is missing
#// (e.g. because user removed their GPU board).
self.scheduler_wait = False
self.scheduler_wait_reason = ""
self.network_wait = False
self.resources = ""
#// textual description of resources used
#// the following defined if active
# XML is generated in client/app.cpp ACTIVE_TASK::write_gui()
self.active_task = False
self.active_task_state = Process.UNINITIALIZED
self.app_version_num = 0
self.slot = -1
self.pid = 0
self.scheduler_state = CpuSched.UNINITIALIZED
self.checkpoint_cpu_time = 0.0
self.current_cpu_time = 0.0
self.fraction_done = 0.0
self.elapsed_time = 0.0
self.swap_size = 0
self.working_set_size_smoothed = 0.0
self.too_large = False
self.needs_shmem = False
self.graphics_exec_path = ""
self.web_graphics_url = ""
self.remote_desktop_addr = ""
self.slot_path = ""
#// only present if graphics_exec_path is
# The following are not in original API, but are present in RPC XML reply
self.completed_time = 0.0
#// time when ready_to_report was set
self.report_immediately = False
self.working_set_size = 0
self.page_fault_rate = 0.0
#// derived by higher-level code
# The following are in API, but are NEVER in RPC XML reply. Go figure
self.signal = 0
self.app = None # APP*
self.wup = None # WORKUNIT*
self.project = None # PROJECT*
self.avp = None # APP_VERSION*
@classmethod
def parse(cls, xml):
if not isinstance(xml, ElementTree.Element):
xml = ElementTree.fromstring(xml)
# parse main XML
result = super(Result, cls).parse(xml)
# parse '<active_task>' children
active_task = xml.find('active_task')
if active_task is None:
result.active_task = False # already the default after __init__()
else:
result.active_task = True # already the default after main parse
result = setattrs_from_xml(result, active_task)
#// if CPU time is nonzero but elapsed time is zero,
#// we must be talking to an old client.
#// Set elapsed = CPU
#// (easier to deal with this here than in the manager)
if result.current_cpu_time != 0 and result.elapsed_time == 0:
result.elapsed_time = result.current_cpu_time
if result.final_cpu_time != 0 and result.final_elapsed_time == 0:
result.final_elapsed_time = result.final_cpu_time
return result
def __str__(self):
buf = '{0}:\n'.format(self.__class__.__name__)
for attr in self.__dict__:
value = getattr(self, attr)
if attr in ['received_time', 'report_deadline']:
value = time.ctime(value)
buf += '\t{0}\t{1}\n'.format(attr, value)
return buf
class BoincClient(object):
def __init__(self, host="", port=0, passwd=None):
self.hostname = host
self.port = port
self.passwd = passwd
self.rpc = Rpc(text_output=False)
self.version = None
self.authorized = False
# Informative, not authoritative. Records status of *last* RPC call,
# but does not infer success about the *next* one.
# Thus, it should be read *after* an RPC call, not prior to one
self.connected = False
def __enter__(self): self.connect(); return self
def __exit__(self, *args): self.disconnect()
def connect(self):
try:
self.rpc.connect(self.hostname, self.port)
self.connected = True
except socket.error:
self.connected = False
return
self.authorized = self.authorize(self.passwd)
self.version = self.exchange_versions()
def disconnect(self):
self.rpc.disconnect()
def authorize(self, password):
''' Request authorization. If password is None and we are connecting
to localhost, try to read password from the local config file
GUI_RPC_PASSWD_FILE. If file can't be read (not found or no
permission to read), try to authorize with a blank password.
If authorization is requested and fails, all subsequent calls
will be refused with socket.error 'Connection reset by peer' (104).
Since most local calls do no require authorization, do not attempt
it if you're not sure about the password.
'''
if password is None and not self.hostname:
password = read_gui_rpc_password() or ""
nonce = self.rpc.call('<auth1/>').text
authhash = hashlib.md5('{0}{1}'.format(nonce, password).encode()).hexdigest().lower()
reply = self.rpc.call('<auth2><nonce_hash>{0}</nonce_hash></auth2>'.format(authhash))
if reply.tag == 'authorized':
return True
else:
return False
def exchange_versions(self):
''' Return VersionInfo instance with core client version info '''
return VersionInfo.parse(self.rpc.call('<exchange_versions/>'))
def get_tasks(self):
''' Same as get_results(active_only=False) '''
return self.get_results(False)
def get_results(self, active_only=False):
''' Get a list of results.
Those that are in progress will have information such as CPU time
and fraction done. Each result includes a name;
Use CC_STATE::lookup_result() to find this result in the current static state;
if it's not there, call get_state() again.
'''
reply = self.rpc.call("<get_results><active_only>{0}</active_only></get_results>".format(1 if active_only else 0))
if not reply.tag == 'results':
return []
results = []
for item in list(reply):
results.append(Result.parse(item))
return results
def read_gui_rpc_password():
''' Read password string from GUI_RPC_PASSWD_FILE file, trim the last CR
(if any), and return it
'''
try:
with open(GUI_RPC_PASSWD_FILE, 'r') as f:
buf = f.read()
if buf.endswith('\n'): return buf[:-1] # trim last CR
else: return buf
except IOError:
# Permission denied or File not found.
pass