skilleter-thingy 0.0.40__py3-none-any.whl → 0.0.42__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of skilleter-thingy might be problematic. Click here for more details.
- skilleter_thingy/__init__.py +6 -0
- skilleter_thingy/addpath.py +107 -0
- skilleter_thingy/borger.py +269 -0
- skilleter_thingy/console_colours.py +63 -0
- skilleter_thingy/diskspacecheck.py +67 -0
- skilleter_thingy/docker_purge.py +113 -0
- skilleter_thingy/ffind.py +536 -0
- skilleter_thingy/ggit.py +90 -0
- skilleter_thingy/ggrep.py +154 -0
- skilleter_thingy/git_br.py +180 -0
- skilleter_thingy/git_ca.py +142 -0
- skilleter_thingy/git_cleanup.py +287 -0
- skilleter_thingy/git_co.py +220 -0
- skilleter_thingy/git_common.py +61 -0
- skilleter_thingy/git_hold.py +154 -0
- skilleter_thingy/git_mr.py +92 -0
- skilleter_thingy/git_parent.py +77 -0
- skilleter_thingy/git_review.py +1428 -0
- skilleter_thingy/git_update.py +385 -0
- skilleter_thingy/git_wt.py +96 -0
- skilleter_thingy/gitcmp_helper.py +322 -0
- skilleter_thingy/gitprompt.py +274 -0
- skilleter_thingy/gl.py +174 -0
- skilleter_thingy/gphotosync.py +610 -0
- skilleter_thingy/linecount.py +155 -0
- skilleter_thingy/moviemover.py +133 -0
- skilleter_thingy/photodupe.py +136 -0
- skilleter_thingy/phototidier.py +248 -0
- skilleter_thingy/py_audit.py +131 -0
- skilleter_thingy/readable.py +270 -0
- skilleter_thingy/remdir.py +126 -0
- skilleter_thingy/rmdupe.py +550 -0
- skilleter_thingy/rpylint.py +91 -0
- skilleter_thingy/splitpics.py +99 -0
- skilleter_thingy/strreplace.py +82 -0
- skilleter_thingy/sysmon.py +435 -0
- skilleter_thingy/tfm.py +920 -0
- skilleter_thingy/tfparse.py +101 -0
- skilleter_thingy/thingy/__init__.py +6 -0
- skilleter_thingy/thingy/colour.py +213 -0
- skilleter_thingy/thingy/dc_curses.py +278 -0
- skilleter_thingy/thingy/dc_defaults.py +221 -0
- skilleter_thingy/thingy/dc_util.py +50 -0
- skilleter_thingy/thingy/dircolors.py +308 -0
- skilleter_thingy/thingy/docker.py +95 -0
- skilleter_thingy/thingy/files.py +142 -0
- skilleter_thingy/thingy/git.py +1371 -0
- skilleter_thingy/thingy/git2.py +1307 -0
- skilleter_thingy/thingy/gitlab.py +193 -0
- skilleter_thingy/thingy/logger.py +112 -0
- skilleter_thingy/thingy/path.py +156 -0
- skilleter_thingy/thingy/popup.py +87 -0
- skilleter_thingy/thingy/process.py +112 -0
- skilleter_thingy/thingy/run.py +334 -0
- skilleter_thingy/thingy/tfm_pane.py +595 -0
- skilleter_thingy/thingy/tidy.py +160 -0
- skilleter_thingy/trimpath.py +84 -0
- skilleter_thingy/window_rename.py +92 -0
- skilleter_thingy/xchmod.py +125 -0
- skilleter_thingy/yamlcheck.py +89 -0
- {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/METADATA +5 -1
- skilleter_thingy-0.0.42.dist-info/RECORD +66 -0
- {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/entry_points.txt +1 -0
- skilleter_thingy-0.0.42.dist-info/top_level.txt +1 -0
- skilleter_thingy-0.0.40.dist-info/RECORD +0 -6
- skilleter_thingy-0.0.40.dist-info/top_level.txt +0 -1
- {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/LICENSE +0 -0
- {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Copy a directory full of pictures to a destination, creating subdiretories
|
|
5
|
+
with N pictures in each in the destination directory
|
|
6
|
+
"""
|
|
7
|
+
################################################################################
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import glob
|
|
11
|
+
import argparse
|
|
12
|
+
|
|
13
|
+
from PIL import Image
|
|
14
|
+
|
|
15
|
+
################################################################################
|
|
16
|
+
# Constants
|
|
17
|
+
|
|
18
|
+
DEFAULT_SOURCE_DIR = '/storage/Starred Photos/'
|
|
19
|
+
DEFAULT_DEST_DIR = '/media/jms/48A7-BE16'
|
|
20
|
+
DEFAULT_MAX_SIZE = 3840
|
|
21
|
+
|
|
22
|
+
################################################################################
|
|
23
|
+
|
|
24
|
+
def parse_command_line():
|
|
25
|
+
""" Parse the command line """
|
|
26
|
+
|
|
27
|
+
parser = argparse.ArgumentParser(description='Copy a collection of pictures to a set of numbered directories')
|
|
28
|
+
|
|
29
|
+
parser.add_argument('--pics', type=int, help='Number of pictures per directory (default is not to use numbered subdirectories)', default=None)
|
|
30
|
+
parser.add_argument('--max-size', type=int, help='Maximum size for each image in pixels (default=%d, images will be resized if larger)' %
|
|
31
|
+
DEFAULT_MAX_SIZE, default=DEFAULT_MAX_SIZE)
|
|
32
|
+
parser.add_argument('source', nargs=1, help='Source directory', default=DEFAULT_SOURCE_DIR)
|
|
33
|
+
parser.add_argument('destination', nargs=1, help='Destination directory', default=DEFAULT_DEST_DIR)
|
|
34
|
+
|
|
35
|
+
args = parser.parse_args()
|
|
36
|
+
|
|
37
|
+
return args
|
|
38
|
+
|
|
39
|
+
################################################################################
|
|
40
|
+
|
|
41
|
+
def copy_images(args):
|
|
42
|
+
""" Copy the images """
|
|
43
|
+
|
|
44
|
+
dir_num = -1
|
|
45
|
+
|
|
46
|
+
pictures = glob.glob(os.path.join(args.source[0], '*'))
|
|
47
|
+
dest_dir = args.destination[0]
|
|
48
|
+
|
|
49
|
+
if not os.path.isdir(dest_dir):
|
|
50
|
+
os.makedirs(dest_dir)
|
|
51
|
+
|
|
52
|
+
for index, picture in enumerate(pictures):
|
|
53
|
+
picture_name = os.path.basename(picture)
|
|
54
|
+
|
|
55
|
+
# Create the new directory in the destination every N pcitures
|
|
56
|
+
|
|
57
|
+
if args.pics and index % args.pics == 0:
|
|
58
|
+
dir_num += 1
|
|
59
|
+
dest_dir = os.path.join(args.destination[0], '%05d' % dir_num)
|
|
60
|
+
if not os.path.isdir(dest_dir):
|
|
61
|
+
os.makedirs(dest_dir)
|
|
62
|
+
|
|
63
|
+
print('%d/%d: Copying %s to %s' % (index + 1, len(pictures), picture, dest_dir))
|
|
64
|
+
|
|
65
|
+
# Resize the image if neccessary
|
|
66
|
+
|
|
67
|
+
image = Image.open(picture)
|
|
68
|
+
|
|
69
|
+
if args.max_size and (image.width > args.max_size or image.height > args.max_size):
|
|
70
|
+
if image.width > image.height:
|
|
71
|
+
scale = image.width / args.max_size
|
|
72
|
+
else:
|
|
73
|
+
scale = image.height / args.max_size
|
|
74
|
+
|
|
75
|
+
new_size = (round(image.width / scale), round(image.height / scale))
|
|
76
|
+
|
|
77
|
+
print(' Resizing from %d x %d to %d x %d' % (image.width, image.height, new_size[0], new_size[1]))
|
|
78
|
+
|
|
79
|
+
image.resize(new_size)
|
|
80
|
+
|
|
81
|
+
# Write the image
|
|
82
|
+
|
|
83
|
+
destination = os.path.join(dest_dir, picture_name)
|
|
84
|
+
|
|
85
|
+
image.save(destination)
|
|
86
|
+
|
|
87
|
+
################################################################################
|
|
88
|
+
|
|
89
|
+
def splitpics():
|
|
90
|
+
"""Entry point"""
|
|
91
|
+
|
|
92
|
+
args = parse_command_line()
|
|
93
|
+
|
|
94
|
+
copy_images(args)
|
|
95
|
+
|
|
96
|
+
################################################################################
|
|
97
|
+
|
|
98
|
+
if __name__ == '__main__':
|
|
99
|
+
splitpics()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Textual search and replace
|
|
5
|
+
|
|
6
|
+
For those occasions when you want to search and replace strings with
|
|
7
|
+
regexppy characters that upset sed.
|
|
8
|
+
|
|
9
|
+
Copyright (C) 2018 John Skilleter """
|
|
10
|
+
################################################################################
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import argparse
|
|
15
|
+
import tempfile
|
|
16
|
+
|
|
17
|
+
################################################################################
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
""" Main function """
|
|
21
|
+
|
|
22
|
+
parser = argparse.ArgumentParser(description='Textual search and replace')
|
|
23
|
+
parser.add_argument('-i', '--inplace', action='store_true', help='Do an in-place search and replace on the input file')
|
|
24
|
+
parser.add_argument('search', nargs=1, action='store', help='Search text')
|
|
25
|
+
parser.add_argument('replace', nargs=1, action='store', help='Replacment text')
|
|
26
|
+
parser.add_argument('infile', nargs='?', action='store', help='Input file')
|
|
27
|
+
parser.add_argument('outfile', nargs='?', action='store', help='Output file')
|
|
28
|
+
|
|
29
|
+
args = parser.parse_args()
|
|
30
|
+
|
|
31
|
+
# Sanity check
|
|
32
|
+
|
|
33
|
+
if args.inplace and not args.infile or args.outfile:
|
|
34
|
+
print('For in-place operations you must specify and input file and no output file')
|
|
35
|
+
|
|
36
|
+
# Open the input file
|
|
37
|
+
|
|
38
|
+
if args.infile:
|
|
39
|
+
infile = open(args.infile, 'r')
|
|
40
|
+
else:
|
|
41
|
+
infile = sys.stdin
|
|
42
|
+
|
|
43
|
+
# Open the output file, using a temporary file in the same directory as the input file
|
|
44
|
+
# if we are doing in-place operations
|
|
45
|
+
|
|
46
|
+
if args.outfile:
|
|
47
|
+
outfile = open(args.outfile, 'w')
|
|
48
|
+
elif args.inplace:
|
|
49
|
+
outfile = tempfile.NamedTemporaryFile(mode='w', delete=False, dir=os.path.dirname(args.infile))
|
|
50
|
+
else:
|
|
51
|
+
outfile = sys.stdout
|
|
52
|
+
|
|
53
|
+
# Perform the searchy-replacey-ness
|
|
54
|
+
|
|
55
|
+
for data in infile:
|
|
56
|
+
outfile.write(data.replace(args.search[0], args.replace[0]))
|
|
57
|
+
|
|
58
|
+
# If we doing in-place then juggle the temporary and input files
|
|
59
|
+
|
|
60
|
+
if args.inplace:
|
|
61
|
+
mode = os.stat(args.infile).st_mode
|
|
62
|
+
outfile.close()
|
|
63
|
+
infile.close()
|
|
64
|
+
os.rename(outfile.name, args.infile)
|
|
65
|
+
os.chmod(args.infile, mode)
|
|
66
|
+
|
|
67
|
+
################################################################################
|
|
68
|
+
|
|
69
|
+
def strreplace():
|
|
70
|
+
"""Entry point"""
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
main()
|
|
74
|
+
except KeyboardInterrupt:
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
except BrokenPipeError:
|
|
77
|
+
sys.exit(2)
|
|
78
|
+
|
|
79
|
+
################################################################################
|
|
80
|
+
|
|
81
|
+
if __name__ == '__main__':
|
|
82
|
+
strreplace()
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
"""Very simple system monitoring dashboard"""
|
|
5
|
+
################################################################################
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import datetime
|
|
9
|
+
import time
|
|
10
|
+
import curses
|
|
11
|
+
|
|
12
|
+
import psutil
|
|
13
|
+
|
|
14
|
+
################################################################################
|
|
15
|
+
|
|
16
|
+
NUM_BOXES_V = 5
|
|
17
|
+
NUM_BOXES_H = 2
|
|
18
|
+
|
|
19
|
+
UPDATE_PERIOD = 1
|
|
20
|
+
|
|
21
|
+
################################################################################
|
|
22
|
+
|
|
23
|
+
def show_system_load(scr, first, w, h, x, y):
|
|
24
|
+
"""Load averaged"""
|
|
25
|
+
|
|
26
|
+
load = psutil.getloadavg()
|
|
27
|
+
|
|
28
|
+
x += 2
|
|
29
|
+
|
|
30
|
+
if first:
|
|
31
|
+
scr.addstr(y+1, x, '1 minute:')
|
|
32
|
+
scr.addstr(y+2, x, '5 minute:')
|
|
33
|
+
scr.addstr(y+3, x, '15 minute:')
|
|
34
|
+
else:
|
|
35
|
+
scr.addstr(y+1, x+10, f'{load[0]:6.2f}')
|
|
36
|
+
scr.addstr(y+2, x+10, f'{load[1]:6.2f}')
|
|
37
|
+
scr.addstr(y+3, x+10, f'{load[2]:6.2f}')
|
|
38
|
+
|
|
39
|
+
################################################################################
|
|
40
|
+
|
|
41
|
+
def show_cpu_times(scr, first, w, h, x, y):
|
|
42
|
+
"""CPU times"""
|
|
43
|
+
|
|
44
|
+
info = psutil.cpu_times()
|
|
45
|
+
|
|
46
|
+
x += 2
|
|
47
|
+
|
|
48
|
+
if first:
|
|
49
|
+
scr.addstr(y+1, x, 'Idle:')
|
|
50
|
+
scr.addstr(y+2, x, 'System:')
|
|
51
|
+
scr.addstr(y+3, x, 'User:')
|
|
52
|
+
scr.addstr(y+4, x, 'Nice:')
|
|
53
|
+
|
|
54
|
+
x += w//3
|
|
55
|
+
|
|
56
|
+
scr.addstr(y+1, x, 'I/O Wait:')
|
|
57
|
+
scr.addstr(y+2, x, 'IRQ:')
|
|
58
|
+
scr.addstr(y+3, x, 'Soft IRQ:')
|
|
59
|
+
|
|
60
|
+
x+= w//3
|
|
61
|
+
|
|
62
|
+
scr.addstr(y+1, x, 'Guest:')
|
|
63
|
+
scr.addstr(y+2, x, 'Guest Nice:')
|
|
64
|
+
else:
|
|
65
|
+
total = (info.user + info.system + info.idle + info.nice + info.iowait + info.irq + info.softirq + info.guest + info.guest_nice)/100
|
|
66
|
+
|
|
67
|
+
user = info.user / total
|
|
68
|
+
system = info.system / total
|
|
69
|
+
idle = info.idle / total
|
|
70
|
+
nice = info.nice / total
|
|
71
|
+
iowait = info.iowait / total
|
|
72
|
+
irq = info.irq / total
|
|
73
|
+
softirq = info.softirq / total
|
|
74
|
+
guest = info.guest / total
|
|
75
|
+
guest_nice = info.guest_nice / total
|
|
76
|
+
|
|
77
|
+
scr.addstr(y+1, x+9, f'{idle:6.2f}')
|
|
78
|
+
scr.addstr(y+2, x+9, f'{system:6.2f}')
|
|
79
|
+
scr.addstr(y+3, x+9, f'{user:6.2f}')
|
|
80
|
+
scr.addstr(y+4, x+9, f'{nice:6.2f}')
|
|
81
|
+
|
|
82
|
+
x += w//3
|
|
83
|
+
|
|
84
|
+
scr.addstr(y+1, x+10, f'{iowait:6.2f}')
|
|
85
|
+
scr.addstr(y+2, x+10, f'{irq:6.2f}')
|
|
86
|
+
scr.addstr(y+3, x+10, f'{softirq:6.2f}')
|
|
87
|
+
|
|
88
|
+
x += w//3
|
|
89
|
+
|
|
90
|
+
scr.addstr(y+1, x+12, f'{guest:6.2f}')
|
|
91
|
+
scr.addstr(y+2, x+12, f'{guest_nice:6.2f}')
|
|
92
|
+
|
|
93
|
+
################################################################################
|
|
94
|
+
|
|
95
|
+
def show_disk_access(scr, first, w, h, x, y):
|
|
96
|
+
"""Disk I/O statistics"""
|
|
97
|
+
|
|
98
|
+
info = psutil.disk_io_counters()
|
|
99
|
+
|
|
100
|
+
x += 2
|
|
101
|
+
|
|
102
|
+
if first:
|
|
103
|
+
scr.addstr(y+1, x, 'Read count:')
|
|
104
|
+
scr.addstr(y+2, x, 'Write count:')
|
|
105
|
+
|
|
106
|
+
scr.addstr(y+4, x, 'Read bytes:')
|
|
107
|
+
scr.addstr(y+5, x, 'Write bytes:')
|
|
108
|
+
|
|
109
|
+
x += w//3
|
|
110
|
+
|
|
111
|
+
scr.addstr(y+1, x, 'Read time:')
|
|
112
|
+
scr.addstr(y+2, x, 'Write time:')
|
|
113
|
+
|
|
114
|
+
scr.addstr(y+4, x, 'I/O time:')
|
|
115
|
+
|
|
116
|
+
x += w//3
|
|
117
|
+
|
|
118
|
+
scr.addstr(y+1, x, 'Read merged:')
|
|
119
|
+
scr.addstr(y+2, x, 'Write merged:')
|
|
120
|
+
else:
|
|
121
|
+
scr.addstr(y+1, x+14, f'{info.read_count:12}')
|
|
122
|
+
scr.addstr(y+2, x+14, f'{info.write_count:12}')
|
|
123
|
+
|
|
124
|
+
scr.addstr(y+4, x+14, f'{info.read_bytes:12}')
|
|
125
|
+
scr.addstr(y+5, x+14, f'{info.write_bytes:12}')
|
|
126
|
+
|
|
127
|
+
x += w//3
|
|
128
|
+
|
|
129
|
+
scr.addstr(y+1, x+14, f'{info.read_time:12}')
|
|
130
|
+
scr.addstr(y+2, x+14, f'{info.write_time:12}')
|
|
131
|
+
|
|
132
|
+
scr.addstr(y+4, x+14, f'{info.busy_time:12}')
|
|
133
|
+
|
|
134
|
+
x += w//3
|
|
135
|
+
|
|
136
|
+
scr.addstr(y+1, x+14, f'{info.read_merged_count:12}')
|
|
137
|
+
scr.addstr(y+2, x+14, f'{info.write_merged_count:12}')
|
|
138
|
+
|
|
139
|
+
################################################################################
|
|
140
|
+
|
|
141
|
+
def show_processes(scr, first, w, h, x, y):
|
|
142
|
+
"""TBD: Process information"""
|
|
143
|
+
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
################################################################################
|
|
147
|
+
|
|
148
|
+
def show_memory(scr, first, w, h, x, y):
|
|
149
|
+
"""Memory usage"""
|
|
150
|
+
|
|
151
|
+
x += 2
|
|
152
|
+
|
|
153
|
+
if first:
|
|
154
|
+
scr.addstr(y+1, x, 'Total:')
|
|
155
|
+
scr.addstr(y+2, x, 'Used:')
|
|
156
|
+
scr.addstr(y+3, x, 'Buffers:')
|
|
157
|
+
scr.addstr(y+4, x, 'Free:')
|
|
158
|
+
|
|
159
|
+
x += w//3
|
|
160
|
+
|
|
161
|
+
scr.addstr(y+1, x, 'Active:')
|
|
162
|
+
scr.addstr(y+2, x, 'Inactive:')
|
|
163
|
+
|
|
164
|
+
scr.addstr(y+4, x, 'Usage:')
|
|
165
|
+
|
|
166
|
+
x += w//3
|
|
167
|
+
|
|
168
|
+
scr.addstr(y+1, x, 'Shared:')
|
|
169
|
+
scr.addstr(y+2, x, 'Slab:')
|
|
170
|
+
else:
|
|
171
|
+
meminfo = psutil.virtual_memory()
|
|
172
|
+
x += 11
|
|
173
|
+
|
|
174
|
+
scr.addstr(y+1, x, f'{meminfo.total:12}')
|
|
175
|
+
scr.addstr(y+2, x, f'{meminfo.used:12}')
|
|
176
|
+
scr.addstr(y+3, x, f'{meminfo.buffers:12}')
|
|
177
|
+
scr.addstr(y+4, x, f'{meminfo.free:12}')
|
|
178
|
+
|
|
179
|
+
x += w//3
|
|
180
|
+
|
|
181
|
+
scr.addstr(y+1, x, f'{meminfo.active:12}')
|
|
182
|
+
scr.addstr(y+2, x, f'{meminfo.inactive:12}')
|
|
183
|
+
|
|
184
|
+
scr.addstr(y+4, x, f'{meminfo.percent:6.1f}%')
|
|
185
|
+
|
|
186
|
+
x += w//3
|
|
187
|
+
|
|
188
|
+
scr.addstr(y+1, x, f'{meminfo.shared:12}')
|
|
189
|
+
scr.addstr(y+2, x, f'{meminfo.slab:12}')
|
|
190
|
+
|
|
191
|
+
################################################################################
|
|
192
|
+
|
|
193
|
+
def show_voltages(scr, first, w, h, x, y):
|
|
194
|
+
"""TBD: Voltages"""
|
|
195
|
+
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
################################################################################
|
|
199
|
+
|
|
200
|
+
def show_cpu_load(scr, first, w, h, x, y):
|
|
201
|
+
"""CPU load and frequencies"""
|
|
202
|
+
|
|
203
|
+
info = psutil.cpu_percent(percpu=True)
|
|
204
|
+
freq = psutil.cpu_freq(percpu=True)
|
|
205
|
+
|
|
206
|
+
stats = psutil.cpu_stats()
|
|
207
|
+
|
|
208
|
+
xo = yo = 0
|
|
209
|
+
for n, cpu in enumerate(info):
|
|
210
|
+
if first:
|
|
211
|
+
scr.addstr(y+yo+1, x+xo+5, 'CPU # : % at MHz')
|
|
212
|
+
else:
|
|
213
|
+
scr.addstr(y+yo+1, x+xo+10, f'{n:<2}')
|
|
214
|
+
scr.addstr(y+yo+1, x+xo+14, f'{cpu:5.1f}%')
|
|
215
|
+
scr.addstr(y+yo+1, x+xo+23, f'{freq[n].current:8.2f}')
|
|
216
|
+
|
|
217
|
+
yo += 1
|
|
218
|
+
|
|
219
|
+
if yo > h-2:
|
|
220
|
+
xo += w//3
|
|
221
|
+
yo = 0
|
|
222
|
+
|
|
223
|
+
x += w//2
|
|
224
|
+
|
|
225
|
+
if first:
|
|
226
|
+
scr.addstr(y+1, x, 'Context switches:')
|
|
227
|
+
scr.addstr(y+2, x, 'Interrupts:')
|
|
228
|
+
scr.addstr(y+3, x, 'Soft interrupts:')
|
|
229
|
+
else:
|
|
230
|
+
x += 18
|
|
231
|
+
scr.addstr(y+1, x, f'{stats.ctx_switches:12}')
|
|
232
|
+
scr.addstr(y+2, x, f'{stats.interrupts:12}')
|
|
233
|
+
scr.addstr(y+3, x, f'{stats.soft_interrupts:12}')
|
|
234
|
+
|
|
235
|
+
################################################################################
|
|
236
|
+
|
|
237
|
+
def show_swappery(scr, first, w, h, x, y):
|
|
238
|
+
"""Swap info"""
|
|
239
|
+
|
|
240
|
+
x += 2
|
|
241
|
+
|
|
242
|
+
if first:
|
|
243
|
+
scr.addstr(y+1, x, 'Swap total:')
|
|
244
|
+
scr.addstr(y+2, x, 'Swap used:')
|
|
245
|
+
scr.addstr(y+3, x, 'Swap free:')
|
|
246
|
+
|
|
247
|
+
x += w//3
|
|
248
|
+
|
|
249
|
+
scr.addstr(y+1, x, 'Swap used:')
|
|
250
|
+
|
|
251
|
+
x += w//3
|
|
252
|
+
|
|
253
|
+
scr.addstr(y+1, x, 'Swapped in:')
|
|
254
|
+
scr.addstr(y+2, x, 'Swapped out:')
|
|
255
|
+
else:
|
|
256
|
+
info = psutil.swap_memory()
|
|
257
|
+
|
|
258
|
+
x += 14
|
|
259
|
+
|
|
260
|
+
scr.addstr(y+1, x, f'{info.total:12}')
|
|
261
|
+
scr.addstr(y+2, x, f'{info.used:12}')
|
|
262
|
+
scr.addstr(y+3, x, f'{info.free:12}')
|
|
263
|
+
|
|
264
|
+
x += w//3
|
|
265
|
+
|
|
266
|
+
scr.addstr(y+1, x, f'{info.percent:6.2f}%')
|
|
267
|
+
|
|
268
|
+
x += w//3
|
|
269
|
+
|
|
270
|
+
scr.addstr(y+1, x, f'{info.sin:12}')
|
|
271
|
+
scr.addstr(y+2, x, f'{info.sout:12}')
|
|
272
|
+
|
|
273
|
+
################################################################################
|
|
274
|
+
|
|
275
|
+
def show_network(scr, first, w, h, x, y):
|
|
276
|
+
"""Network statistics"""
|
|
277
|
+
|
|
278
|
+
x += 2
|
|
279
|
+
|
|
280
|
+
if first:
|
|
281
|
+
scr.addstr(y+1, x, 'Bytes sent:')
|
|
282
|
+
scr.addstr(y+2, x, 'Bytes received:')
|
|
283
|
+
|
|
284
|
+
scr.addstr(y+4, x, 'Packets sent:')
|
|
285
|
+
scr.addstr(y+5, x, 'Packets received:')
|
|
286
|
+
|
|
287
|
+
x += w//2
|
|
288
|
+
|
|
289
|
+
scr.addstr(y+1, x, 'Send errors:')
|
|
290
|
+
scr.addstr(y+2, x, 'Receive errors:')
|
|
291
|
+
|
|
292
|
+
scr.addstr(y+4, x, 'Outgoing dropped:')
|
|
293
|
+
scr.addstr(y+5, x, 'Incoming dropped:')
|
|
294
|
+
else:
|
|
295
|
+
info = psutil.net_io_counters()
|
|
296
|
+
x += 19
|
|
297
|
+
|
|
298
|
+
scr.addstr(y+1, x, f'{info.bytes_sent:12}')
|
|
299
|
+
scr.addstr(y+2, x, f'{info.bytes_recv:12}')
|
|
300
|
+
|
|
301
|
+
scr.addstr(y+4, x, f'{info.packets_sent:12}')
|
|
302
|
+
scr.addstr(y+5, x, f'{info.packets_recv:12}')
|
|
303
|
+
|
|
304
|
+
x += w//2
|
|
305
|
+
|
|
306
|
+
scr.addstr(y+1, x, f'{info.errout:12}')
|
|
307
|
+
scr.addstr(y+2, x, f'{info.errin:12}')
|
|
308
|
+
|
|
309
|
+
scr.addstr(y+4, x, f'{info.dropout:12}')
|
|
310
|
+
scr.addstr(y+5, x, f'{info.dropin:12}')
|
|
311
|
+
|
|
312
|
+
################################################################################
|
|
313
|
+
|
|
314
|
+
def show_temperatures(scr, first, w, h, x, y):
|
|
315
|
+
"""TBD: Temperatures"""
|
|
316
|
+
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
################################################################################
|
|
320
|
+
|
|
321
|
+
# Panel title and the functions used to update them
|
|
322
|
+
|
|
323
|
+
BOXES= {
|
|
324
|
+
'System Load': show_system_load,
|
|
325
|
+
'Disk Access': show_disk_access,
|
|
326
|
+
'Processes': show_processes,
|
|
327
|
+
'Memory': show_memory,
|
|
328
|
+
'Voltages': show_voltages,
|
|
329
|
+
|
|
330
|
+
'CPU Load': show_cpu_load,
|
|
331
|
+
'Swap and Paging': show_swappery,
|
|
332
|
+
'Temperatures': show_temperatures,
|
|
333
|
+
'Network': show_network,
|
|
334
|
+
'Total CPU Times': show_cpu_times,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
################################################################################
|
|
338
|
+
|
|
339
|
+
def main(stdscr):
|
|
340
|
+
"""Main function"""
|
|
341
|
+
|
|
342
|
+
# Configure curses
|
|
343
|
+
|
|
344
|
+
curses.curs_set(0)
|
|
345
|
+
curses.start_color()
|
|
346
|
+
curses.noecho()
|
|
347
|
+
curses.cbreak()
|
|
348
|
+
curses.use_default_colors()
|
|
349
|
+
|
|
350
|
+
curses.init_pair(1, curses.COLOR_GREEN, 15)
|
|
351
|
+
curses.init_pair(2, curses.COLOR_BLUE, 15)
|
|
352
|
+
|
|
353
|
+
# Set up the display
|
|
354
|
+
|
|
355
|
+
stdscr.keypad(1)
|
|
356
|
+
stdscr.nodelay(True)
|
|
357
|
+
stdscr.bkgdset(' ', curses.color_pair(0))
|
|
358
|
+
|
|
359
|
+
# Outer loop iterates whenever the console window changes size
|
|
360
|
+
|
|
361
|
+
terminate = False
|
|
362
|
+
|
|
363
|
+
while not terminate:
|
|
364
|
+
stdscr.clear()
|
|
365
|
+
|
|
366
|
+
height, width = stdscr.getmaxyx()
|
|
367
|
+
|
|
368
|
+
box_h = height // NUM_BOXES_V
|
|
369
|
+
box_w = width // NUM_BOXES_H
|
|
370
|
+
|
|
371
|
+
# Draw the titles and text on the first iteration
|
|
372
|
+
# Just draw the statistics on the subsequent ones
|
|
373
|
+
|
|
374
|
+
first_time = True
|
|
375
|
+
window_resize = False
|
|
376
|
+
|
|
377
|
+
# Inner loop just updates display until
|
|
378
|
+
|
|
379
|
+
while not window_resize and not terminate:
|
|
380
|
+
now = datetime.datetime.now()
|
|
381
|
+
|
|
382
|
+
stdscr.addstr(0, 1, now.strftime('%02H:%02M:%02S'), curses.COLOR_BLACK)
|
|
383
|
+
stdscr.addstr(0, width-11, now.strftime('%02Y-%02m-%02d'), curses.COLOR_BLACK)
|
|
384
|
+
|
|
385
|
+
for i, box in enumerate(BOXES):
|
|
386
|
+
x, y = divmod(i, NUM_BOXES_V)
|
|
387
|
+
|
|
388
|
+
x *= box_w
|
|
389
|
+
y *= box_h
|
|
390
|
+
|
|
391
|
+
title_x = x+(box_w - len(box))//2
|
|
392
|
+
stdscr.addstr(y, title_x, box, curses.A_BOLD)
|
|
393
|
+
|
|
394
|
+
stdscr.attron(curses.color_pair(1 if first_time else 2))
|
|
395
|
+
|
|
396
|
+
BOXES[box](stdscr, first_time, box_w, box_h, x, y+1)
|
|
397
|
+
|
|
398
|
+
# Update the display, clear the first-time draw-static-content flag
|
|
399
|
+
|
|
400
|
+
stdscr.refresh()
|
|
401
|
+
first_time = False
|
|
402
|
+
|
|
403
|
+
# Wait for the next update
|
|
404
|
+
|
|
405
|
+
elapsed = (datetime.datetime.now() - now).total_seconds()
|
|
406
|
+
|
|
407
|
+
if elapsed < UPDATE_PERIOD:
|
|
408
|
+
time.sleep(UPDATE_PERIOD - elapsed)
|
|
409
|
+
|
|
410
|
+
# Check for keypress or window resize
|
|
411
|
+
|
|
412
|
+
keyboard = stdscr.getch()
|
|
413
|
+
|
|
414
|
+
if keyboard in (ord('Q'), ord('q')):
|
|
415
|
+
terminate = True
|
|
416
|
+
|
|
417
|
+
elif keyboard == curses.KEY_RESIZE:
|
|
418
|
+
window_resize = True
|
|
419
|
+
|
|
420
|
+
################################################################################
|
|
421
|
+
|
|
422
|
+
def sysmon():
|
|
423
|
+
"""Entry point"""
|
|
424
|
+
|
|
425
|
+
try:
|
|
426
|
+
curses.wrapper(main)
|
|
427
|
+
except KeyboardInterrupt:
|
|
428
|
+
sys.exit(1)
|
|
429
|
+
except BrokenPipeError:
|
|
430
|
+
sys.exit(2)
|
|
431
|
+
|
|
432
|
+
################################################################################
|
|
433
|
+
|
|
434
|
+
if __name__ == "__main__":
|
|
435
|
+
sysmon()
|