File tuptime-5.2.4.obscpio of Package tuptime
07070100000000000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001600000000tuptime-5.2.4/.github07070100000001000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000002000000000tuptime-5.2.4/.github/workflows07070100000002000081A40000000000000000000000016693DA1900000937000000000000000000000000000000000000003400000000tuptime-5.2.4/.github/workflows/codeql-analysis.yml# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
# ******** NOTE ********
name: "CodeQL"
on:
push:
branches: [ dev ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ dev ]
schedule:
- cron: '16 07 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
07070100000003000081A40000000000000000000000016693DA1900002B47000000000000000000000000000000000000001800000000tuptime-5.2.4/CHANGELOGtuptime (5.2.4) unstable; urgency=low
* Move files from /lib to /usr/lib in DEB package
* Replace adduser with sysusers.d in DEB package
* Update documentation
* Minnor code refactoring
-- Ricardo Fraile <r@rfmoz.eu> Sat, 13 Jul 2024 13:21:00 +0100
tuptime (5.2.3) unstable; urgency=low
* Update distribution packages
* Documentation revised
* Minnor code refactoring
-- Ricardo Fraile <r@rfmoz.eu> Fri, 05 Jan 2024 10:32:00 +0100
tuptime (5.2.2) unstable; urgency=low
* Swich between longest/shortest on default output with -i
* Option -x, --silent renamed to -q, --quiet
* Change repository name
-- Ricardo Fraile <r@rfmoz.eu> Thu, 05 Jan 2023 20:17:00 +0100
tuptime (5.2.1) unstable; urgency=low
* Set cron file with systemd execute exclusion on .deb package
* Return -e option as an argument for --dec decimals
* Update .deb package
* Update .rpm package
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 19 Aug 2022 16:21:00 +0100
tuptime (5.2.0) unstable; urgency=low
* Rename timer units to tuptime-sync.timer and tuptime-sync.service
* Using StateDirectory= on systemd unit
* Available tuptime.sysusers file for systemd-sysusers
* Replace cron file with systemd timer on .deb package
* Add rc script for OpenBSD and update installation doc
* Deprecate --pctl option
* Adding -E, --exclude option
* Print longest uptime and downtime values on default output
* Update distribution packages
* Code refactoring
-- Ricardo Fraile <rfraile@rfraile.eu> Tue, 02 Aug 2022 21:55:00 +0100
tuptime (5.1.0) unstable; urgency=low
* Cover DB writes with transactions
* Register DB version on PRAGMA user_version
* Adding --pctl option. Show percentil over average values
* Code refactoring
* Update .deb package
-- Ricardo Fraile <rfraile@rfraile.eu> Sun, 16 Jan 2022 13:09:00 +0100
tuptime (5.0.2) unstable; urgency=low
* Option --decp usable but deprecated
* Using subprocess module on BSDs
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 02 Jan 2021 09:01:00 +0100
tuptime (5.0.1) unstable; urgency=low
* Identify new startups with boot_id on FreeBSD
* Option --decp renamed to --dec and -e equal to it.
* Minnor code refactoring
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 31 Oct 2020 15:06:00 +0100
tuptime (5.0.0) unstable; urgency=low
* Abbreviate format in time counters
* Identify new startups with boot_id on Linux
* Adding -b, --bootid option to show boot IDs
* Adding -i, --invert option to show startup number in reverse count
* Change database specs. Add bootid column
* After a graceful shutdown register, delay 5 seconds next DB update
* Change execution user from 'tuptime' to '_tuptime'
* Large refactoring
* Update .deb package
-- Ricardo Fraile <rfraile@rfraile.eu> Tue, 26 May 2020 19:58:00 +0100
tuptime (4.1.0) unstable; urgency=low
* Register shutdowns lower than a second
* Adding option -A --at
* Refactoring --tat output
* Rename suspend time to sleep time
-- Ricardo Fraile <rfraile@rfraile.eu> Wed, 01 Jan 2020 17:03:00 +0100
tuptime (4.0.0) unstable; urgency=low
* Adding support for running and suspended time. (-p, --power)
* Adding --tat to report system state at specific timestamp.
* Change database specs. Add rntime and spdtime columns.
* Update .deb package
-- Ricardo Fraile <rfraile@rfraile.eu> Sun, 28 Jul 2019 20:12:00 +0100
tuptime (3.5.0) unstable; urgency=low
* Avoid execution if the startup date is prior to 01/Jan/2000 00:00
* Avoid correct drift when it decreases uptime under 0
* Fix register shutdown with negative downtime
* Minnor code refactoring
-- Ricardo Fraile <rfraile@rfraile.eu> Wed, 20 Feb 2019 17:02:00 +0100
tuptime (3.4.2) unstable; urgency=low
* Fix Debian Bug Fails to upgrade (Closes: #914954)
* Change deb architecture from any to all
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 07 Dec 2018 15:48:00 +0100
tuptime (3.4.1) unstable; urgency=low
* Adding Launchd .plist
* Code quality checker fixes
* Update .deb package
* Adding Uptimed migration script
* Fix Debian Bug about real time definition (Closes: #912996)
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 23 Nov 2018 21:16:00 +0100
tuptime (3.4.0) unstable; urgency=low
* Adding DragonflyBSD, OpenBSD and NetBSD support
* Improve error report when checking db path and file
* Arguments refactoring. Change deprecated module optparse to argpaser
* Adding --decp option. Allow change decimal length in percentages
* Fixed equal number of values per line in csv output
* Fix right termination when SIGPIPE signal is received
* Upgrade timer unit with tuptime-cron.timer and tuptime-cron.service
* Adding OpenRC init script
* Tuptime user without /bin/sh shell
* Support environmental variable with DB file path
-- Ricardo Fraile <rfraile@rfraile.eu> Sun, 23 Sep 2018 13:40:00 +0200
tuptime (3.3.3) unstable; urgency=low
* Unify order arguments under -o
* Improve behaviour when time drift
* Requirement of time-sync in systemd unit
* Warn about negative values produced by inconsistent time sync.
* Validate right assignment of system variables
* Update .deb package
* Fix Debian Bug Files to install (Closes: #884955)
* Python 3.x as requirement
-- Ricardo Fraile <rfraile@rfraile.eu> Wed, 03 Jan 2018 23:56:00 +0200
tuptime (3.3.2) unstable; urgency=low
* Fix print kernel in list and table outputs in partial registers
* Consistent decimals output when seconds are used in table and list
* Accurate report in largest/shortest uptime/downtime with partial
registers in narrow ranges
* Avoid import subprocess module
* Report in default output the state of last shutdown
* Adding systemd .timer as alternative to cron schedule
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 21 Jan 2017 19:43:00 +0100
tuptime (3.3.1) unstable; urgency=medium
* Adding dependency of lsb-base in debian package
* Change to native debian package
* Fix typo in doc
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 16 Jan 2017 19:48:27 -0200
tuptime (3.3.0) unstable; urgency=low
* Refactoring code
* Adding -S --since option
* Adding -U --until option
* Adding --tsince option
* Adding --tuntil option
* Adding -n --noup option
* Adding --csv option
* Fix inconsistencies with seconds reports due rounded decimals
* Fix inconsistencies when system clock drift
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 12 Mar 2016 10:51:00 +0100
tuptime (3.2.3) unstable; urgency=low
* Refactoring code
* Change execution user from root to tuptime
* Equivalence between start/stop runlevels in systemd and init.
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 12 Dec 2015 22:40:00 +0100
tuptime (3.2.2) unstable; urgency=low
* Adding compatibility with OS X (Darwin)
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 28 Nov 2015 09:40:00 +0100
tuptime (3.2.01) stable; urgency=low
* Detect database modification
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 12 Oct 2015 11:00:00 -0400
tuptime (3.2.00) stable; urgency=low
* Print singular names if is the case
* Fix round and decimals values
* Clean and order code
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 05 Oct 2015 11:00:00 -0400
tuptime (3.1.00) stable; urgency=low
* Register used kernels
* Adding kernel option
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 28 Sep 2015 11:00:00 -0400
tuptime (3.0.00) stable; urgency=low
* Change printed output format
* Adding max/min uptime/downtime reports
* Adding table format output
* Rename enumerate option to list and change output format
* Adding sorting options
* Change database schema
* Clean and order code
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 25 Sep 2015 11:00:00 -0400
tuptime (2.6.10) stable; urgency=low
* Compatibility between python2.7 and python3.X
* Removing unused modules
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 10 Aug 2015 11:00:00 -0400
tuptime (2.5.20) stable; urgency=low
* Setting data type for ok/bad shutdown in db creation
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 08 Aug 2015 11:00:00 -0400
tuptime (2.5.12) stable; urgency=low
* Setting max decimals in the output
* Fix data type for the ok/bad shutdown registry in db which cause incorrect
counter output
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 07 Aug 2015 11:00:00 -0400
tuptime (2.5.00) stable; urgency=low
* Fix false shutdown count and correct locale date format
-- Ricardo Fraile <rfraile@rfraile.eu> Tue, 04 Aug 2015 11:00:00 -0400
tuptime (2.4.26) stable; urgency=low
* Fix from other point of view false shutdown bug
-- Ricardo Fraile <rfraile@rfraile.eu> Tue, 04 Aug 2015 10:00:00 -0400
tuptime (2.4.10) stable; urgency=low
* Fix false wrong shutdown bug when some kernels rarely report +1 or -1
second inside /proc/stat btime variable
-- Ricardo Fraile <rfraile@rfraile.eu> Sun, 26 Jul 2015 10:00:00 -0400
tuptime (2.4.00) stable; urgency=low
* Adding downtimes reports
* Adding enumerate option
* Fix bad shutdowns count
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 20 Jul 2015 10:00:00 -0400
tuptime (2.2.00) stable; urgency=low
* Completely rewritten in python
-- Ricardo Fraile <rfraile@rfraile.eu> Wed, 06 May 2015 10:00:00 -0400
tuptime (1.6.2) stable; urgency=low
* New init script for debian 7 wheezy
-- Ricardo Fraile <rfraile@rfraile.eu> Thu, 14 May 2013 10:00:00 -0400
tuptime (1.6.0) stable; urgency=low
* Remove usage of syslog for more quick output.
* Change output.
* Print rate in output.
-- Ricardo Fraile <rfraile@rfraile.eu> Thu, 17 Jan 2012 10:00:00 -0400
tuptime (1.5.0) stable; urgency=low
* Print estimated uptime between starts.
* Print actual uptime for the system.
* Fix and improve code, remove repetitive lines and minnor bugs.
* Check if the variable in the conf file is a number.
* Start using Scalar::Util module.
* Print system uptime date.
* Fix bug in first update
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 10 Oct 2011 10:00:00 -0400
tuptime (1.4.0) stable; urgency=low
* Print time more accurate.
* Cron line change to 5 minutes.
* Print time values in live time without update.
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 06 Aug 2011 10:00:00 -0400
tuptime (1.3.0) stable; urgency=low
* Jump blank lines in conf file.
* Adding #REPLACEAT feature.
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 20 Jun 2011 10:00:00 -0400
tuptime (1.2.0) stable; urgency=low
* Change speak option to verbose.
* Fix lost information in files when update.
* Minnor corrections in text strings.
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 13 Jun 2011 10:00:00 -0400
tuptime (1.1.0) stable; urgency=low
* Any user can print the times, not only root.
* Minnor changes to speak option.
* Minnor corrections.
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 10 Jun 2011 10:00:00 -0400
tuptime (1.0.0) stable; urgency=low
* First release.
-- Ricardo Fraile <rfraile@rfraile.eu> Wed, 23 Mar 2011 10:02:18 -0400
07070100000004000081A40000000000000000000000016693DA190000004D000000000000000000000000000000000000001E00000000tuptime-5.2.4/CONTRIBUTING.md# Contributing to Tuptime
Any pull request will go to "dev" branch. Thanks.
07070100000005000081A40000000000000000000000016693DA190000467F000000000000000000000000000000000000001600000000tuptime-5.2.4/LICENSE GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
07070100000006000081A40000000000000000000000016693DA190000180B000000000000000000000000000000000000001800000000tuptime-5.2.4/README.mdtuptime
=======
Tuptime reports the historical and statistical real time of the system, preserving it between restarts. Like uptime command, but with more interesting output.
### Sample output
Just after install:
System startups: 1 since 21:54:09 24/09/15
System shutdowns: 0 ok + 0 bad
System life: 21m 30s
Longest uptime: 21m 30s from 21:54:09 24/09/15
Average uptime: 21m 30s
System uptime: 100.0% = 21m 30s
Longest downtime: 0s
Average downtime: 0s
System downtime: 0.0% = 0s
Current uptime: 21m 30s since 21:54:09 24/09/15
A few days later:
System startups: 110 since 10:15:27 08/08/15
System shutdowns: 107 ok + 2 bad
System life: 47d 12h 2m 15s
Longest uptime: 2h 10m 44s from 20:49:17 09/08/15
Average uptime: 25m 8s
System uptime: 4.04% = 1d 22h 4m 44s
Longest downtime: 7d 10h 17m 26s from 06:09:45 10/08/15
Average downtime: 9h 56m 42s
System downtime: 95.96% = 45d 13h 57m 30s
Current uptime: 23m 33s since 21:54:09 24/09/15
Swich to -t | --table option:
No. Startup T. Uptime Shutdown T. End Downtime
1 10:15:27 08/08/15 42s 10:16:09 08/08/15 OK 16s
2 10:16:26 08/08/15 49s 10:17:15 08/08/15 OK 16s
3 10:17:32 08/08/15 5m 47s 10:23:19 08/08/15 OK 16s
4 10:23:36 08/08/15 9s 10:23:45 08/08/15 BAD 42s
5 10:24:28 08/08/15 2h 9m 27s 12:33:55 08/08/15 OK 41m 44s
. . .
Or swich to -l | --list option:
Startup: 1 at 10:15:27 08/08/15
Uptime: 42s
Shutdown: OK at 10:16:09 08/08/15
Downtime: 16s
Startup: 2 at 10:16:26 08/08/15
Uptime: 49s
Shutdown: OK at 10:17:15 08/08/15
Downtime: 16s
Startup: 3 at 10:17:32 08/08/15
Uptime: 5m 47s
Shutdown: OK at 10:23:19 08/08/15
Downtime: 16s
. . .
### Basic Installation
#### By package manager
* Debian: https://packages.debian.org/tuptime
* Ubuntu: https://packages.ubuntu.com/tuptime
* Fedora, EPEL: https://src.fedoraproject.org/rpms/tuptime
* FreeBSD: https://www.freshports.org/sysutils/tuptime
* Archlinux: https://aur.archlinux.org/packages/tuptime
* OpenSUSE: https://software.opensuse.org/package/tuptime (Community Maintained / Unofficial)
#### By one-liner script
bash < <(curl -Ls https://git.io/tuptime-install.sh)
#### By manual method
Briefly in a Linux or FreeBSD system...
Clone the repo:
git clone --depth=1 https://github.com/rfmoz/tuptime.git
Copy the 'tuptime' file located under 'latest/' directory to '/usr/bin/' and make it executable:
cp tuptime/src/tuptime /usr/bin/tuptime
chmod ugo+x /usr/bin/tuptime
Assure that the system pass the prerequisites:
python 3.X
Run first with a privileged user:
tuptime
Pick from 'src/' folder the right file for your cron and init manager, setup both
properly. See 'tuptime-manual.txt' for more information.
### Highlights about Tuptime internals
- It doesn't run as a daemon, at least, it only needs execution when the init manager startup and shutdown the system. To avoid issues with a switch off without a proper shutdown, like power failures, a cron job and a .timer unit are shipped with the project to update the registers each n minutes. As a system administrator, you can easily choose the best number for your particular system requirements.
- It is written in Python using common modules and as few as possible, quick execution, easy to see what is inside it, and modify it for fit for your particular use case.
- It registers the times in a sqlite database. Any other software can use it. The specs are in the tuptime-manual.txt. Also, it has the option to output the registers in seconds and epoch or/and in csv format, easy to pipe it to other commands.
- Its main purpose is tracking all the system startups/shutdowns and present that information to the user in a more understandable way. Don't have mail alerts when a milestones are reached or the limitation of keep the last n records.
- It's written to avoid false startups registers. This is an issue that sometimes happens when the NTP adjust the system clock, on virtualized environments, on servers with high load, when the system resynchronized with their RTC clock after a suspend and resume cycle...
- It can report:
- Registers as a table or list ordering by any label.
- The whole life of the system or only a part of it, closing the range between startups/shutdowns or timestamps.
- Accumulated running and sleeping time over an uptime.
- The kernel version used and boot idenfiers.
- The system state at specific point in time.
### Alternatives
journalctl --list-boots - Show a tabular list of boot numbers (relative to the current boot), their IDs, and the timestamps of the first and last message pertaining to the boot. Close output than 'tuptime -bit'.
https://github.com/systemd/systemd/
uptimed - Is an uptime record daemon keeping track of the highest uptimes a computer system ever had. It uses the system boot time to keep sessions apart from each other.
https://github.com/rpodgorny/uptimed
downtimed - Is a program for monitoring operating system downtime, uptime, shutdowns and crashes and for keeping record of such events.
https://dist.epipe.com/downtimed/
lastwake - Analyzes the system journal and prints out wake-up and sleep timestamps; for each cycle it tells whether the system was suspended to RAM or to disk (hibernated).
https://github.com/arigit/lastwake.py
(bonus) dateutils - Not an alternative, but it is a nifty collection of tools to work with dates.
https://github.com/hroptatyr/dateutils
ruptime - Is a modern rwhod replacement that is easy to customize, not limited to a network, and does not send clear text data over the network.
https://github.com/alexmyczko/ruptime
### More information
Please, read tuptime-manual.txt for a complete reference guide.
07070100000007000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001500000000tuptime-5.2.4/debian07070100000008000081A40000000000000000000000016693DA1900002EB5000000000000000000000000000000000000001F00000000tuptime-5.2.4/debian/changelogtuptime (5.2.4) unstable; urgency=low
* Move files from /lib to /usr/lib in DEB package
* Replace adduser with sysusers.d in DEB package
* Update documentation
* Minnor code refactoring
-- Ricardo Fraile <r@rfmoz.eu> Sat, 13 Jul 2024 13:21:00 +0100
tuptime (5.2.3) unstable; urgency=low
* Update distribution packages
* Documentation revised
* Minnor code refactoring
-- Ricardo Fraile <r@rfmoz.eu> Fri, 05 Jan 2024 10:32:00 +0100
tuptime (5.2.2) unstable; urgency=low
* Swich between longest/shortest on default output with -i
* Option -x, --silent renamed to -q, --quiet
* Change repository name
-- Ricardo Fraile <r@rfmoz.eu> Thu, 05 Jan 2023 20:17:00 +0100
tuptime (5.2.1) unstable; urgency=low
* Set cron file with systemd execute exclusion on .deb package
* Return -e option as an argument for --dec decimals
* Update .deb package
* Update .rpm package
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 19 Aug 2022 16:21:00 +0100
tuptime (5.2.0) unstable; urgency=low
* Rename timer units to tuptime-sync.timer and tuptime-sync.service
* Using StateDirectory= on systemd unit
* Available tuptime.sysusers file for systemd-sysusers
* Replace cron file with systemd timer on .deb package
* Add rc script for OpenBSD and update installation doc
* Deprecate --pctl option
* Adding -E, --exclude option
* Print longest uptime and downtime values on default output
* Update distribution packages
* Code refactoring
-- Ricardo Fraile <rfraile@rfraile.eu> Tue, 02 Aug 2022 21:55:00 +0100
tuptime (5.1.0) unstable; urgency=low
* Cover DB writes with transactions
* Register DB version on PRAGMA user_version
* Adding --pctl option. Show percentil over average values
* Code refactoring
* Update .deb package
-- Ricardo Fraile <rfraile@rfraile.eu> Sun, 16 Jan 2022 13:09:00 +0100
tuptime (5.0.2) unstable; urgency=low
* Option --decp usable but deprecated
* Using subprocess module on BSDs
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 02 Jan 2021 09:01:00 +0100
tuptime (5.0.1) unstable; urgency=low
* Identify new startups with boot_id on FreeBSD
* Option --decp renamed to --dec and -e equal to it.
* Minnor code refactoring
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 31 Oct 2020 15:06:00 +0100
tuptime (5.0.0) unstable; urgency=low
* Abbreviate format in time counters
* Identify new startups with boot_id
* Adding -b, --bootid option to show boot IDs
* Adding -i, --invert option to show startup number in reverse count
* Change database specs. Add bootid column
* After a graceful shutdown register, delay 5 seconds next DB update
* Change execution user from 'tuptime' to '_tuptime'
* Large refactoring
* Update .deb package
-- Ricardo Fraile <rfraile@rfraile.eu> Tue, 26 May 2020 19:58:00 +0100
tuptime (4.1.0) unstable; urgency=low
* Register shutdowns lower than a second
* Adding option -A --at
* Refactoring --tat output
* Rename suspend time to sleep time
-- Ricardo Fraile <rfraile@rfraile.eu> Wed, 01 Jan 2020 17:03:00 +0100
tuptime (4.0.0) unstable; urgency=low
* Adding support for running and suspended time. (-p, --power)
* Adding --tat to report system state at specific timestamp.
* Change database specs. Add rntime and spdtime columns.
* Update .deb package
-- Ricardo Fraile <rfraile@rfraile.eu> Sun, 28 Jul 2019 20:12:00 +0100
tuptime (3.5.0) unstable; urgency=low
* Avoid execution if the startup date is prior to 01/Jan/2000 00:00
* Avoid correct drift when it decreases uptime under 0
* Fix register shutdown with negative downtime
* Minnor code refactoring
-- Ricardo Fraile <rfraile@rfraile.eu> Wed, 20 Feb 2019 17:02:00 +0100
tuptime (3.4.2) unstable; urgency=low
* Fix Debian Bug Fails to upgrade (Closes: #914954)
* Change deb architecture from any to all
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 07 Dec 2018 15:48:00 +0100
tuptime (3.4.1) unstable; urgency=low
* Adding Launchd .plist
* Code quality checker fixes
* Update .deb package
* Adding Uptimed migration script
* Fix Debian Bug about real time definition (Closes: #912996)
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 23 Nov 2018 21:16:00 +0100
tuptime (3.4.0) unstable; urgency=low
* Adding DragonflyBSD, OpenBSD and NetBSD support
* Improve error report when checking db path and file
* Arguments refactoring. Change deprecated module optparse to argpaser
* Adding --decp option. Allow change decimal length in percentages
* Fixed equal number of values per line in csv output
* Fix right termination when SIGPIPE signal is received
* Upgrade timer unit with tuptime-cron.timer and tuptime-cron.service
* Adding OpenRC init script
* Tuptime user without /bin/sh shell
* Support environmental variable with DB file path
-- Ricardo Fraile <rfraile@rfraile.eu> Sun, 23 Sep 2018 13:40:00 +0200
tuptime (3.3.3) unstable; urgency=low
* Unify order arguments under -o
* Improve behaviour when time drift
* Requirement of time-sync in systemd unit
* Warn about negative values produced by inconsistent time sync.
* Validate right assignment of system variables
* Update .deb package
* Fix Debian Bug Files to install (Closes: #884955)
* Python 3.x as requirement
-- Ricardo Fraile <rfraile@rfraile.eu> Wed, 03 Jan 2018 23:56:00 +0200
tuptime (3.3.2) unstable; urgency=low
* Fix print kernel in list and table outputs in partial registers
* Consistent decimals output when seconds are used in table and list
* Accurate report in largest/shortest uptime/downtime with partial
registers in narrow ranges
* Avoid import subprocess module
* Report in default output the state of last shutdown
* Adding systemd .timer as alternative to cron schedule
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 21 Jan 2017 19:43:00 +0100
tuptime (3.3.1) unstable; urgency=medium
* Adding dependency of lsb-base in debian package
* Change to native debian package
* Fix typo in doc
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 16 Jan 2017 19:48:27 -0200
tuptime (3.3.0-2) unstable; urgency=medium
* Remove creation of /var/lib/tuptime from debian/dirs and fall only in
postinstall adduser command. (Closes: #821725)
-- Ricardo Fraile <rfraile@rfraile.eu> Tue, 19 Apr 2016 17:09:27 -0200
tuptime (3.3.0-1) unstable; urgency=medium
[Antoine Beaupré]
* Complete switch to non-native package
* Fix build with git-buildpackage
-- Ricardo Fraile <rfraile@rfraile.eu> Tue, 12 Apr 2016 08:59:27 -0400
tuptime (3.3.0) unstable; urgency=low
* Refactoring code
* Adding -S --since option
* Adding -U --until option
* Adding --tsince option
* Adding --tuntil option
* Adding -n --noup option
* Adding --csv option
* Fix inconsistencies with seconds reports due rounded decimals
* Fix inconsistencies when system clock drift
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 12 Mar 2016 10:51:00 +0100
tuptime (3.2.3) unstable; urgency=low
* Refactoring code
* Change execution user from root to tuptime
* Equivalence between start/stop runlevels in systemd and init.
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 12 Dec 2015 22:40:00 +0100
tuptime (3.2.2) unstable; urgency=low
* Adding compatibility with OS X (Darwin)
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 28 Nov 2015 09:40:00 +0100
tuptime (3.2.01+git6db666b-2) unstable; urgency=medium
* ship tuptime in /usr/bin, upstream will make it usable by non-root
(Closes: #806380)
-- Antoine Beaupré <anarcat@debian.org> Fri, 27 Nov 2015 09:59:43 -0500
tuptime (3.2.01+git6db666b-1) unstable; urgency=low
* Redo packaging based on upstream binary package. Closes: #638422
-- Antoine Beaupré <anarcat@debian.org> Wed, 25 Nov 2015 11:53:00 -0500
tuptime (3.2.01) stable; urgency=low
* Detect database modification
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 12 Oct 2015 11:00:00 -0400
tuptime (3.2.00) stable; urgency=low
* Print singular names if is the case
* Fix round and decimals values
* Clean and order code
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 05 Oct 2015 11:00:00 -0400
tuptime (3.1.00) stable; urgency=low
* Register used kernels
* Adding kernel option
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 28 Sep 2015 11:00:00 -0400
tuptime (3.0.00) stable; urgency=low
* Change printed output format
* Adding max/min uptime/downtime reports
* Adding table format output
* Rename enumerate option to list and change output format
* Adding sorting options
* Change database schema
* Clean and order code
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 25 Sep 2015 11:00:00 -0400
tuptime (2.6.10) stable; urgency=low
* Compatibility between python2.7 and python3.X
* Removing unused modules
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 10 Aug 2015 11:00:00 -0400
tuptime (2.5.20) stable; urgency=low
* Setting data type for ok/bad shutdown in db creation
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 08 Aug 2015 11:00:00 -0400
tuptime (2.5.12) stable; urgency=low
* Setting max decimals in the output
* Fix data type for the ok/bad shutdown registry in db which cause incorrect
counter output
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 07 Aug 2015 11:00:00 -0400
tuptime (2.5.00) stable; urgency=low
* Fix false shutdown count and correct locale date format
-- Ricardo Fraile <rfraile@rfraile.eu> Tue, 04 Aug 2015 11:00:00 -0400
tuptime (2.4.26) stable; urgency=low
* Fix from other point of view false shutdown bug
-- Ricardo Fraile <rfraile@rfraile.eu> Tue, 04 Aug 2015 10:00:00 -0400
tuptime (2.4.10) stable; urgency=low
* Fix false wrong shutdown bug when some kernels rarely report +1 or -1
second inside /proc/stat btime variable
-- Ricardo Fraile <rfraile@rfraile.eu> Sun, 26 Jul 2015 10:00:00 -0400
tuptime (2.4.00) stable; urgency=low
* Adding downtimes reports
* Adding enumerate option
* Fix bad shutdowns count
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 20 Jul 2015 10:00:00 -0400
tuptime (2.2.00) stable; urgency=low
* Completely rewritten in python
-- Ricardo Fraile <rfraile@rfraile.eu> Wed, 06 May 2015 10:00:00 -0400
tuptime (1.6.2) stable; urgency=low
* New init script for debian 7 wheezy
-- Ricardo Fraile <rfraile@rfraile.eu> Thu, 14 May 2013 10:00:00 -0400
tuptime (1.6.0) stable; urgency=low
* Remove usage of syslog for more quick output.
* Change output.
* Print rate in output.
-- Ricardo Fraile <rfraile@rfraile.eu> Thu, 17 Jan 2012 10:00:00 -0400
tuptime (1.5.0) stable; urgency=low
* Print estimated uptime between starts.
* Print actual uptime for the system.
* Fix and improve code, remove repetitive lines and minnor bugs.
* Check if the variable in the conf file is a number.
* Start using Scalar::Util module.
* Print system uptime date.
* Fix bug in first update
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 10 Oct 2011 10:00:00 -0400
tuptime (1.4.0) stable; urgency=low
* Print time more accurate.
* Cron line change to 5 minutes.
* Print time values in live time without update.
-- Ricardo Fraile <rfraile@rfraile.eu> Sat, 06 Aug 2011 10:00:00 -0400
tuptime (1.3.0) stable; urgency=low
* Jump blank lines in conf file.
* Adding #REPLACEAT feature.
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 20 Jun 2011 10:00:00 -0400
tuptime (1.2.0) stable; urgency=low
* Change speak option to verbose.
* Fix lost information in files when update.
* Minnor corrections in text strings.
-- Ricardo Fraile <rfraile@rfraile.eu> Mon, 13 Jun 2011 10:00:00 -0400
tuptime (1.1.0) stable; urgency=low
* Any user can print the times, not only root.
* Minnor changes to speak option.
* Minnor corrections.
-- Ricardo Fraile <rfraile@rfraile.eu> Fri, 10 Jun 2011 10:00:00 -0400
tuptime (1.0.0) stable; urgency=low
* First release.
-- Ricardo Fraile <rfraile@rfraile.eu> Wed, 23 Mar 2011 10:02:18 -0400
07070100000009000081A40000000000000000000000016693DA1900000229000000000000000000000000000000000000001D00000000tuptime-5.2.4/debian/controlSource: tuptime
Section: utils
Priority: optional
Maintainer: Ricardo Fraile <r@rfmoz.eu>
Build-Depends: debhelper-compat (= 13)
Rules-Requires-Root: no
Standards-Version: 4.7.0
Homepage: https://github.com/rfmoz/tuptime
Package: tuptime
Architecture: all
Multi-Arch: foreign
Pre-Depends: ${misc:Pre-Depends}
Depends: ${misc:Depends}, ${shlibs:Depends}
, python3:any
Description: report historical system real time
Tuptime tracks and reports historical and statistical real time of
the system, preserving the uptime and downtime between shutdowns.
0707010000000A000081A40000000000000000000000016693DA19000003C6000000000000000000000000000000000000001F00000000tuptime-5.2.4/debian/copyrightFormat: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: tuptime
Source: https://github.com/rfmoz/tuptime
Files: *
Copyright: 2011-2018 Ricardo F.
License: GPL-2.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General Public License
Version 2 can be found in `/usr/share/common-licenses/GPL-2'.
0707010000000B000081A40000000000000000000000016693DA190000004F000000000000000000000000000000000000001E00000000tuptime-5.2.4/debian/gbp.conf[DEFAULT]
debian-branch=master
upstream-tag=%(version)s
debian-tag=%(version)s
0707010000000C000081A40000000000000000000000016693DA1900000429000000000000000000000000000000000000001E00000000tuptime-5.2.4/debian/postinst#!/bin/sh
# postinst script for tuptime
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <postinst> `configure' <most-recently-configured-version>
# * <old-postinst> `abort-upgrade' <new version>
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
# <new-version>
# * <postinst> `abort-remove'
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
# <failed-install-package> <version> `removing'
# <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
configure)
# Rename to underscore if previous exists
getent passwd tuptime >/dev/null && usermod -l _tuptime tuptime
getent group tuptime > /dev/null && groupmod -n _tuptime tuptime
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0
0707010000000D000081A40000000000000000000000016693DA190000039A000000000000000000000000000000000000001C00000000tuptime-5.2.4/debian/postrm#!/bin/sh
# postrm script for tuptime
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <postrm> `remove'
# * <postrm> `purge'
# * <old-postrm> `upgrade' <new-version>
# * <new-postrm> `failed-upgrade' <old-version>
# * <new-postrm> `abort-install'
# * <new-postrm> `abort-install' <old-version>
# * <new-postrm> `abort-upgrade' <old-version>
# * <disappearer's-postrm> `disappear' <overwriter>
# <overwriter-version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
purge|disappear)
rm -rf /var/lib/tuptime/
deluser _tuptime || true
;;
remove|upgrade|failed-upgrade|abort-install|abort-upgrade)
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0
0707010000000E000081ED0000000000000000000000016693DA190000017E000000000000000000000000000000000000001B00000000tuptime-5.2.4/debian/rules#!/usr/bin/make -f
# You must remove unused comment lines for the released package.
#export DH_VERBOSE = 1
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
override_dh_installinit:
dh_installinit --onlyscripts
# this is needed until dh compat 14
dh_installsysusers
%:
dh $@
0707010000000F000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001C00000000tuptime-5.2.4/debian/source07070100000010000081A40000000000000000000000016693DA190000000D000000000000000000000000000000000000002300000000tuptime-5.2.4/debian/source/format3.0 (native)
07070100000011000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001B00000000tuptime-5.2.4/debian/tests07070100000012000081A40000000000000000000000016693DA190000008E000000000000000000000000000000000000002300000000tuptime-5.2.4/debian/tests/controlTests: exec-simple
Depends: @
Restrictions: isolation-container
Tests: exec-debug
Depends: @
Restrictions: isolation-container, allow-stderr
07070100000013000081A40000000000000000000000016693DA190000001E000000000000000000000000000000000000002600000000tuptime-5.2.4/debian/tests/exec-debug#!/bin/sh
set -e
tuptime -v
07070100000014000081A40000000000000000000000016693DA190000001B000000000000000000000000000000000000002700000000tuptime-5.2.4/debian/tests/exec-simple#!/bin/sh
set -e
tuptime
07070100000015000081A40000000000000000000000016693DA190000011C000000000000000000000000000000000000002400000000tuptime-5.2.4/debian/tuptime.cron.d# /etc/cron.d/tuptime: crontab entry for tuptime update.
# NOTE: Decrease the execution time for increase accuracity.
# Skip in favour of systemd timer
*/5 * * * * _tuptime if [ ! -d /run/systemd/system ] && [ -x /usr/bin/tuptime ]; then /usr/bin/tuptime -q > /dev/null; fi
07070100000016000081A40000000000000000000000016693DA1900000013000000000000000000000000000000000000002200000000tuptime-5.2.4/debian/tuptime.docstuptime-manual.txt
07070100000017000081A40000000000000000000000016693DA1900000117000000000000000000000000000000000000002500000000tuptime-5.2.4/debian/tuptime.installsrc/systemd/tuptime.service usr/lib/systemd/system/
src/systemd/tuptime-sync.service usr/lib/systemd/system/
src/systemd/tuptime-sync.timer usr/lib/systemd/system/
src/systemd/sysusers.d/tuptime.conf usr/lib/sysusers.d/
src/init.d/debian/tuptime etc/init.d/
src/tuptime usr/bin/
07070100000018000081A40000000000000000000000016693DA1900000012000000000000000000000000000000000000002600000000tuptime-5.2.4/debian/tuptime.manpagessrc/man/tuptime.1
07070100000019000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001300000000tuptime-5.2.4/misc0707010000001A000081A40000000000000000000000016693DA1900000DFB000000000000000000000000000000000000002A00000000tuptime-5.2.4/misc/bsd-manual-install.txt====================================
| FreeBSD manual install - Tuptime |
====================================
Install dependencies, latest versions:
# pkg install python3 git
# pkg install py39-sqlite3 # (version number must match "python3 -V")
Clone repository and copy executable file:
# git clone --depth=1 https://github.com/rfmoz/tuptime.git
# install -m 755 tuptime/src/tuptime /usr/local/bin/tuptime
Add tuptime user:
# pw adduser _tuptime -d /nonexistent -s /usr/sbin/nologin -c "Tuptime execution user"
Execute tuptime with a privileged user for create db path:
# tuptime
Change owner of the db path and file:
# chown -R _tuptime:_tuptime /var/lib/tuptime
Add the cron entry, create a '/usr/local/etc/cron.d/tuptime' file with this content:
*/5 * * * * _tuptime (/usr/sbin/service tuptime enabled) && /usr/local/bin/tuptime -q
Copy rc.d file:
# install -m 555 tuptime/src/rc.d/freebsd/tuptime /usr/local/etc/rc.d/tuptime
# sysrc tuptime_enable=YES
# service tuptime start
It's all done.
====================================
| OpenBSD manual install - Tuptime |
====================================
Install dependencies, latest versions:
# pkg_add git python
Clone repository and copy executable file:
# git clone --depth=1 https://github.com/rfmoz/tuptime.git
# install -m 755 tuptime/src/tuptime /usr/local/bin/tuptime
Add tuptime user:
# useradd -s /sbin/nologin -d /var/empty -L daemon _tuptime
Execute tuptime with a privileged user for create db path:
# tuptime
Change owner of the db path and file:
# chown -R _tuptime:_tuptime /var/lib/tuptime
Add the cron entry, create a '/var/cron/tabs/_tuptime' file with this content:
SHELL=/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin/
*/5 * * * * tuptime -q
Set the right permissions to cron file and restart the service:
# chown _tuptime:crontab /var/cron/tabs/_tuptime
# chmod 600 /var/cron/tabs/_tuptime
Copy rc.d file:
# install -m 555 tuptime/src/rc.d/openbsd/tuptime /etc/rc.d/tuptime
# rcctl enable tuptime
# rcctl start tuptime
It's all done.
===================================
| NetBSD manual install - Tuptime |
===================================
Install dependencies:
# pkg_add git python3? py3?-sqlite3
Create a symlink for the Python3 interpreter:
# mkdir -p /usr/local/bin/
# ln -s /usr/pkg/bin/python3.? /usr/local/bin/python3
Install tls certs before clone the repo:
# pkg_add mozilla-rootcerts
# mozilla-rootcerts install
Clone repository and copy executable file:
# git clone --depth=1 https://github.com/rfmoz/tuptime.git
# install -m 755 tuptime/src/tuptime /usr/local/bin/tuptime
Execute tuptime with a privileged user for create db path:
# tuptime
Add tuptime user:
# useradd -d /var/lib/tuptime -s /sbin/nologin _tuptime
Change owner of the db path and file:
# chown -R _tuptime:_tuptime /var/lib/tuptime
Add the cron entry, create a '/var/cron/tabs/_tuptime' file with this content:
SHELL=/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin/
*/5 * * * * tuptime -q
Set the right permissions to cron file and restart the service:
# chown _tuptime /var/cron/tabs/_tuptime
# chmod 600 /var/cron/tabs/_tuptime
# service cron restart
Copy rc.d file:
# install -m 555 tuptime/src/rc.d/freebsd/tuptime /etc/rc.d/tuptime
# echo 'tuptime_enable="YES"' >> /etc/rc.conf
It's all done.
0707010000001B000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001700000000tuptime-5.2.4/misc/deb0707010000001C000081A40000000000000000000000016693DA19000002B7000000000000000000000000000000000000002600000000tuptime-5.2.4/misc/deb/deb-readme.txt=====================
| Build Tuptime DEB |
=====================
1.- Install dependencies:
apt-get install dpkg-dev debhelper git
2.- Change to any unprivileged user and create the package. Not build packages using root.
cd ~
git clone --depth=1 https://github.com/rfmoz/tuptime.git
cd tuptime
dpkg-buildpackage -us -uc
3.- Here is:
ls ../tuptime-*.deb
4.- As root, install and check if all was ok:
dpkg -i tuptime-*.deb
systemctl status tuptime.service
Z.- For testing with "dev" branch. Replace step "2" with the following:
cd ~
git clone -b dev --depth=1 https://github.com/rfmoz/tuptime.git
cd tuptime
dpkg-buildpackage -us -uc
0707010000001D000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001A00000000tuptime-5.2.4/misc/docker0707010000001E000081A40000000000000000000000016693DA1900000081000000000000000000000000000000000000002500000000tuptime-5.2.4/misc/docker/DockerfileFROM alpine:latest
RUN apk add tuptime --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/
ENTRYPOINT [ "tuptime" ]
0707010000001F000081A40000000000000000000000016693DA1900000654000000000000000000000000000000000000002B00000000tuptime-5.2.4/misc/docker/docker-notes.txt==========================
| Docker notes - Tuptime |
==========================
Tuptime could be executed inside a container via Docker.
It only requires the creation of the path /var/lib/tuptime/ on the host to store the
database file, which is done by Systemd automatically. All other runtime requirements
reside inside the container and their execution is managed via Systemd too.
The container is built over Alpine image, because their small footprint. But it could
be built over any other OS image.
Begin with this installation method getting the repo and creating the container:
# git clone https://github.com/rfmoz/tuptime.git
# cd tuptime/misc/docker/
# docker build -t 'tuptime' .
Copy the systemd files, the main service and the sync ones for the scheduled execution:
# install -m 644 tuptime-docker.service /etc/systemd/system/
# install -m 644 tuptime-sync-docker.* /etc/systemd/system/
# systemctl daemon-reload
# systemctl enable tuptime-docker.service && systemctl start tuptime-docker.service
# systemctl enable tuptime-sync-docker.timer && systemctl start tuptime-sync-docker.timer
Also, create an alias for an easy execution (Maybe set it inside your ~/.profile or
~/.bashrc):
# alias tuptime='/usr/bin/docker run --network none --rm -v /var/lib/tuptime/:/var/lib/tuptime/ tuptime'
# tuptime
That's all.
Note: If the same host has a local Tuptime installation, disable their Systemd execution:
# systemctl disable tuptime.service && systemctl stop tuptime.service
# systemctl disable tuptime-sync.timer && systemctl stop tuptime-sync.timer
07070100000020000081A40000000000000000000000016693DA19000001A3000000000000000000000000000000000000003100000000tuptime-5.2.4/misc/docker/tuptime-docker.service[Unit]
Description=Tuptime docker service
After=docker.service
Requires=docker.service
Conflicts=tuptime.service
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/docker run --network none --rm -v /var/lib/tuptime/:/var/lib/tuptime/ tuptime -q
ExecStop=/usr/bin/docker run --network none --rm -v /var/lib/tuptime/:/var/lib/tuptime/ tuptime -qg
StateDirectory=tuptime
[Install]
WantedBy=multi-user.target
07070100000021000081A40000000000000000000000016693DA19000000F4000000000000000000000000000000000000003600000000tuptime-5.2.4/misc/docker/tuptime-sync-docker.service[Unit]
Description=Tuptime docker scheduled sync service
Requires=tuptime-docker.service
Conflicts=tuptime-sync.service
[Service]
Type=oneshot
ExecStart=/usr/bin/docker run --network none --rm -v /var/lib/tuptime/:/var/lib/tuptime/ tuptime -q
07070100000022000081A40000000000000000000000016693DA19000000A3000000000000000000000000000000000000003400000000tuptime-5.2.4/misc/docker/tuptime-sync-docker.timer[Unit]
Description=Tuptime docker scheduled sync timer
[Timer]
OnBootSec=1min
OnCalendar=*:0/5
Unit=tuptime-sync-docker.service
[Install]
WantedBy=timers.target
07070100000023000081A40000000000000000000000016693DA19000004C4000000000000000000000000000000000000002E00000000tuptime-5.2.4/misc/openwrt-manual-install.txt====================================
| OpenWRT manual install - Tuptime |
====================================
Install dependencies:
# opkg update && opkg install python3-light python3-sqlite3 python3-logging wget unzip
Get the code and copy the executable file:
# wget https://github.com/rfmoz/tuptime/archive/master.zip
# unzip master.zip
# cp tuptime/src/tuptime /usr/bin/ && chmod 755 /usr/bin/tuptime
As OpenWRT have a symlink from /var/ to /tmp/ and Tuptime saves their db on /var/lib/tuptime, their content will be lost in each startup. So, it's needed a new destination for the db (cron init file do the same for /var/spool):
# mkdir -p /opt/tuptime
# ln -s /opt/tuptime/ /var/lib/tuptime
Add the cron execution. Include the following line in '/etc/crontabs/root':
*/5 * * * * /usr/bin/python3 /usr/bin/tuptime -q
Copy the init file and set the right permissions:
# cp tuptime/src/init.d/openwrt/tuptime /etc/init.d/tuptime
# chmod 755 /etc/init.d/tuptime
Start and enable cron and tuptime:
# /etc/init.d/cron enable
# /etc/init.d/cron start
# /etc/init.d/tuptime enable
# /etc/init.d/tuptime start
Test it:
# tuptime
That's all, enjoy it.
07070100000024000081A40000000000000000000000016693DA19000002FD000000000000000000000000000000000000002100000000tuptime-5.2.4/misc/osx-notes.txt=======================
| OSX notes - Tuptime |
=======================
Tuptime is compatible with OSX, which comes with 'python' installed by default.
But for now, I haven't found a clean way to configure a Launchd task to be
executed before shutdown, so, Tuptime can't register the whole time and the
shutdown status.
The closest approach is to have a Launchd plist that runs at startup and each
minute. The drawbacks will be an error range <60 secs. and a BAD shutdown
status always.
To install the .plist, copy file to the right location:
# git clone --depth=1 https://github.com/rfmoz/tuptime.git
# cp src/launchd/localhost.tuptime.plist /Library/LaunchDaemons/
Load it:
# launchctl load -w /Library/LaunchDaemons/localhost.tuptime.plist
07070100000025000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001700000000tuptime-5.2.4/misc/rpm07070100000026000081A40000000000000000000000016693DA190000060E000000000000000000000000000000000000002600000000tuptime-5.2.4/misc/rpm/rpm-readme.txt=====================
| Build Tuptime RPM |
=====================
1.- Install dependencies:
Latest releases of Fedora, RedHat 8, Centos 8:
dnf -y install rpmdevtools wget python3-rpm-macros python-srpm-macros rpmlint systemd python3-devel
Older releases of Fedora, Redhat 7, CentOS 7, install from EPEL:
yum -y install rpmdevtools wget python3-rpm-macros python-srpm-macros
2.- Change to any unprivileged user and create the package. Not build packages using root.
cd ~
rpmdev-setuptree
cd ~/rpmbuild/SPECS/
wget 'https://raw.githubusercontent.com/rfmoz/tuptime/master/misc/rpm/tuptime.spec'
spectool -g -R tuptime.spec
rpmbuild -ba --target=noarch tuptime.spec
3.- Here is:
ls ~/rpmbuild/RPMS/noarch/tuptime-*.rpm
4.- As root, install and enable:
dnf install tuptime-*rpm # (old way: rpm -i tuptime-*.rpm)
systemctl enable tuptime.service && systemctl start tuptime.service
systemctl enable tuptime-sync.timer && systemctl start tuptime-sync.timer
5.- Check it all was ok:
systemctl status tuptime.service
systemctl status tuptime-sync.timer
Z.- For testing with "dev" branch. Install "git" on step "1" and replace step "2" with the following:
dnf -y install git
cd ~
git clone -b dev --depth=1 https://github.com/rfmoz/tuptime.git tuptime-5.2.4
rpmdev-setuptree
cd ~/rpmbuild/SPECS/
cp ../../tuptime-5.2.4/misc/rpm/tuptime.spec .
tar -czvf ../SOURCES/5.2.4.tar.gz ../../tuptime-5.2.4
rpmbuild -ba --target=noarch tuptime.spec
07070100000027000081A40000000000000000000000016693DA1900000FCA000000000000000000000000000000000000002400000000tuptime-5.2.4/misc/rpm/tuptime.specName: tuptime
Version: 5.2.4
Release: 1%{?dist}
Summary: Report historical system real time
License: GPL-2.0-or-later
BuildArch: noarch
URL: https://github.com/rfmoz/tuptime/
Source0: https://github.com/rfmoz/tuptime/archive/%{version}.tar.gz
%{?systemd_requires}
# Check for EPEL Python (python34, python36)
%if 0%{?python3_pkgversion}
BuildRequires: python%{python3_pkgversion}-devel
%else
BuildRequires: python3-devel
%endif
%if 0%{?el7}
BuildRequires: systemd
%else
BuildRequires: systemd-rpm-macros
%endif
Requires: systemd
Requires(pre): shadow-utils
%description
Tuptime tracks and reports historical and statistical real time of
the system, preserving the uptime and downtime between shutdowns.
%prep
%autosetup
# Fix python shebang
%if %{?py3_shebang_fix:1}%{!?py3_shebang_fix:0}
%py3_shebang_fix src/tuptime
%else
# EPEL7 does not have py3_shebang_fix
/usr/bin/pathfix.py -pni "%{__python3} -s" src/tuptime
%endif
%pre
# Conversion to new group and usernames for previously installed version
getent group tuptime >/dev/null && groupmod --new-name _tuptime tuptime
getent passwd tuptime >/dev/null && usermod --login _tuptime tuptime
getent group _tuptime >/dev/null || groupadd --system _tuptime
getent passwd _tuptime >/dev/null || useradd --system --gid _tuptime --home-dir "/var/lib/tuptime" --shell '/sbin/nologin' --comment 'Tuptime execution user' _tuptime > /dev/null
%build
%install
install -d %{buildroot}%{_bindir}/
install -d %{buildroot}%{_unitdir}/
install -d %{buildroot}%{_mandir}/man1/
install -d %{buildroot}%{_sharedstatedir}/tuptime/
install -d %{buildroot}%{_datadir}/tuptime/
cp src/tuptime %{buildroot}%{_bindir}/
cp src/systemd/tuptime.service %{buildroot}%{_unitdir}/
cp src/systemd/tuptime-sync.service %{buildroot}%{_unitdir}/
cp src/systemd/tuptime-sync.timer %{buildroot}%{_unitdir}/
cp src/man/tuptime.1 %{buildroot}%{_mandir}/man1/
cp misc/scripts/* %{buildroot}%{_datadir}/tuptime/
chmod +x %{buildroot}%{_datadir}/tuptime/*.sh
chmod +x %{buildroot}%{_datadir}/tuptime/*.py
%post
# Create and initialise the tuptime DB with consistent permissions, etc.
su -s /bin/sh _tuptime -c "(umask 0022 && /usr/bin/tuptime -q)"
%systemd_post tuptime.service
%systemd_post tuptime-sync.service
%systemd_post tuptime-sync.timer
%preun
%systemd_preun tuptime.service
%systemd_preun tuptime-sync.service
%systemd_preun tuptime-sync.timer
%postun
%systemd_postun_with_restart tuptime.service
%systemd_postun_with_restart tuptime-sync.service
%systemd_postun_with_restart tuptime-sync.timer
%files
%{_unitdir}/tuptime.service
%{_unitdir}/tuptime-sync.service
%{_unitdir}/tuptime-sync.timer
%attr(0755, root, root) %{_bindir}/tuptime
%dir %attr(0755, _tuptime, _tuptime) %{_sharedstatedir}/tuptime/
%doc tuptime-manual.txt
%doc CHANGELOG README.md CONTRIBUTING.md
%license LICENSE
%{_mandir}/man1/tuptime.1.*
%dir %{_datadir}/tuptime
%{_datadir}/tuptime/*
%changelog
* Fri Jul 13 2024 Ricardo Fraile <rfraile@rfraile.eu> 5.2.4-1
- New release
* Fri Jan 05 2024 Ricardo Fraile <rfraile@rfraile.eu> 5.2.3-1
- New release
* Thu Jan 07 2023 Frank Crawford <frank@crawford.emu.id.au> 5.2.2-2
- Updated to SPDX license
* Thu Jan 05 2023 Ricardo Fraile <rfraile@rfraile.eu> 5.2.2-1
- New release
* Fri Aug 19 2022 Ricardo Fraile <rfraile@rfraile.eu> 5.2.1-1
- New release
* Mon Aug 15 2022 Ricardo Fraile <rfraile@rfraile.eu> 5.2.0-1
- New release
* Sun Jan 16 2022 Ricardo Fraile <rfraile@rfraile.eu> 5.1.0-1
- Bump new release
* Thu Jan 06 2022 Frank Crawford <frank@crawford.emu.id.au> 5.0.2-5
- First offical release in Fedora
* Tue Jan 04 2022 Frank Crawford <frank@crawford.emu.id.au> 5.0.2-4
- Futher updates to spec file following review comments
* Mon Dec 13 2021 Frank Crawford <frank@crawford.emu.id.au> 5.0.2-3
- Update spec file following review comments
* Sun Sep 26 2021 Frank Crawford <frank@crawford.emu.id.au> 5.0.2-2
- Update spec file for Fedora package review
- Copy all relevant documentation
* Sat Jan 02 2021 Ricardo Fraile <rfraile@rfraile.eu> 5.0.2-1
- RPM release
07070100000028000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001B00000000tuptime-5.2.4/misc/scripts07070100000029000081A40000000000000000000000016693DA19000005B2000000000000000000000000000000000000002600000000tuptime-5.2.4/misc/scripts/README.txt#### Scripts #####
____Tools____
tuptime_dbcheck.py
Test database integrity. Catch and fix errors.
tuptime_join.py
Join two tuptime db files into an other one.
tuptime_modify.py
Modify registers preserving nearest values in sync.
Allow change 'end status', 'startup timestamp' and 'shutdown timestamp'.
____DB migrations____
db-tuptime-migrate-2.0-to-3.0.sh
Update tuptime database format from version 2.0.0 or above to 3.0.0.
db-tuptime-migrate-3.0-to-3.1.sh
Update tuptime database format from version 3.0.0 to 3.1.0.
db-tuptime-migrate-3.1-to-4.0.sh
Update tuptime database format from version 3.1.0 or above to 4.0.0.
Tuptime v.4 do it automatically.
db-tuptime-migrate-4.0-to-5.0.sh
Update tuptime database format from version 4.0.0 or above to 5.0.0.
Tuptime v.5 do it automatically.
____Plots____
tuptime-plot1.py
Graph a plot with the number of hours (default) or events (-x swich)
per state along each day. It gets the info from tuptime csv output.
Playground script.
tuptime-plot2.py
Graph a plot with the state events per hour along each day (default)
or accumulated events per hour (-x swich). It gets the info from
tuptime csv output. Playground script.
tuptime-powerplot.py
Graph a plot with the power consumption for every day uptime. At
least, it needs as argument the -k kWh or -m mWh price and the power
consumption value. Playground script.
0707010000002A000081A40000000000000000000000016693DA1900000CD1000000000000000000000000000000000000003C00000000tuptime-5.2.4/misc/scripts/db-tuptime-migrate-2.0-to-3.0.sh#!/bin/bash
# This script update the tuptime database format from versions previous 3.0.00
# Change the db origin:
# uptime real, btime integer, shutdown integer
# to:
# btime integer, uptime real, offbtime integer, endst integer, downtime real
SOURCE_DB='/var/lib/tuptime/tuptime.db'
# Check bash execution
if [ ! -n "$BASH" ]; then
echo "--- WARNING - execute only with BASH ---"
fi
# Test file permissions
if [ -w "${SOURCE_DB}" ]; then
echo "Migrating tuptime database format"
else
echo "Please, execute this script with a privileged user that can write in: ${SOURCE_DB}"
exit 1
fi
# Test sqlite3 command
sqlite3 -version > /dev/null
if [ $? -ne 0 ]; then
echo "Please, install \"sqlite3\" command for manage sqlite v3 databases"
exit 2
fi
# Test bc command
bc -version > /dev/null
if [ $? -ne 0 ]; then
echo "Please, install \"bc\" command"
exit 2
fi
TMP_DB=$(mktemp) # For temporary process db
cp "${SOURCE_DB}" "${TMP_DB}"
# Check db format
sqlite3 "${TMP_DB}" "PRAGMA table_info(tuptime);" | grep -E 'end_state|downtime|offbtime' > /dev/null
if [ $? -eq 0 ]; then
echo "Database is already in new format"
exit 3
fi
# Change shutdown column to end_state
sqlite3 "${TMP_DB}" "ALTER TABLE tuptime RENAME TO tuptime_old;"
sqlite3 "${TMP_DB}" "CREATE TABLE tuptime (uptime REAL, btime INT, endst INT, downtime REAL, offbtime INT);"
sqlite3 "${TMP_DB}" "INSERT INTO tuptime(uptime, btime, endst) SELECT uptime, btime, shutdown FROM tuptime_old;"
sqlite3 "${TMP_DB}" "DROP TABLE tuptime_old;"
# Adding values for new columns downtime and offbtime
ROWS=$(sqlite3 "${TMP_DB}" "select max(oid) from tuptime;")
for I in $(seq 1 "${ROWS}"); do
UPTIME=$(sqlite3 "${TMP_DB}" "SELECT uptime from tuptime where oid = ${I};")
BTIME=$(sqlite3 "${TMP_DB}" "SELECT btime from tuptime where oid = ${I};")
Z=$((I+1))
NEXT_BTIME=$(sqlite3 "${TMP_DB}" "SELECT btime from tuptime where oid = ${Z};")
OFFBTIME=$(echo "${UPTIME}" + "${BTIME}" | bc)
DOWNBTIME=$(echo "${NEXT_BTIME}" - "${OFFBTIME}" | bc)
sqlite3 "${TMP_DB}" "UPDATE tuptime SET downtime = ${DOWNBTIME}, offbtime = ${OFFBTIME} where oid = ${I}"
done
# Clear last row shutdown values
sqlite3 "${TMP_DB}" "UPDATE tuptime SET downtime = '-1', offbtime = '-1' where oid = ${I}"
# Order columns
sqlite3 "${TMP_DB}" "ALTER TABLE tuptime RENAME TO tuptime_old;"
sqlite3 "${TMP_DB}" "CREATE TABLE tuptime (btime INT, uptime REAL, offbtime INT, endst INT, downtime REAL);"
sqlite3 "${TMP_DB}" "INSERT INTO tuptime(btime, uptime, offbtime, endst, downtime) SELECT btime, uptime, offbtime, endst, downtime FROM tuptime_old;"
sqlite3 "${TMP_DB}" "DROP TABLE tuptime_old;"
# Adding new column
sqlite3 "${TMP_DB}" "ALTER TABLE tuptime RENAME TO tuptime_old;"
sqlite3 "${TMP_DB}" "CREATE TABLE tuptime (btime INT, uptime REAL, offbtime INT, endst INT, downtime REAL, kernel TEXT);"
sqlite3 "${TMP_DB}" "INSERT INTO tuptime(btime, uptime, offbtime, endst, downtime, kernel) SELECT btime, uptime, offbtime, endst, downtime, '' FROM tuptime_old;"
sqlite3 "${TMP_DB}" "DROP TABLE tuptime_old;"
# Backup old db and restore the new
mv "${SOURCE_DB}" "${SOURCE_DB}".back
mv "${TMP_DB}" "${SOURCE_DB}"
chmod 644 "${SOURCE_DB}"
rm -f "${TMP_DB}"
echo "Backup file in: ${SOURCE_DB}.back"
echo "Process completed OK"
0707010000002B000081A40000000000000000000000016693DA19000007B9000000000000000000000000000000000000003C00000000tuptime-5.2.4/misc/scripts/db-tuptime-migrate-3.0-to-3.1.sh#!/bin/bash
# This script update the tuptime database format from version 3.0.00 to 3.1.00
# Change the db origin:
# btime integer, uptime real, offbtime integer, endst integer, downtime real
# to:
# btime integer, uptime real, offbtime integer, endst integer, downtime real, kernel text
SOURCE_DB='/var/lib/tuptime/tuptime.db'
# Check bash execution
if [ ! -n "$BASH" ]; then
echo "--- WARNING - execute only with BASH ---"
fi
# Test file permissions
if [ -w "${SOURCE_DB}" ]; then
echo "Migrating tuptime database format"
else
echo "Please, execute this script with a privileged user that can write in: ${SOURCE_DB}"
exit 1
fi
# Test sqlite3 command
sqlite3 -version > /dev/null
if [ $? -ne 0 ]; then
echo "Please, install \"sqlite3\" command for manage sqlite v3 databases"
exit 2
fi
# Test bc command
bc -version > /dev/null
if [ $? -ne 0 ]; then
echo "Please, install \"bc\" command"
exit 3
fi
TMP_DB=$(mktemp) # For temporary process db
cp "${SOURCE_DB}" "${TMP_DB}" || exit 4
# Adding new column
sqlite3 "${TMP_DB}" "ALTER TABLE tuptime RENAME TO tuptime_old;" && \
sqlite3 "${TMP_DB}" "CREATE TABLE tuptime (btime INT, uptime REAL, offbtime INT, endst INT, downtime REAL, kernel TEXT);" && \
sqlite3 "${TMP_DB}" "INSERT INTO tuptime(btime, uptime, offbtime, endst, downtime, kernel) SELECT btime, uptime, offbtime, endst, downtime, '' FROM tuptime_old;" && \
sqlite3 "${TMP_DB}" "DROP TABLE tuptime_old;" || exit 5
## Adding values for new columns downtime and offbtime
#ROWS=`sqlite3 "${TMP_DB}" "select max(oid) from tuptime;"`
#
#for I in $(seq 1 ${ROWS}); do
# KERNEL='Linux-3.16.0-4-amd64-x86_64-with-debian-8.0'
# sqlite3 "${TMP_DB}" "UPDATE tuptime SET kernel = \'${KERNEL}\' where oid = ${I}"
#done
# Backup old db and restore the new
mv "${SOURCE_DB}" "${SOURCE_DB}".back && \
mv "${TMP_DB}" "${SOURCE_DB}" && \
chmod 644 "${SOURCE_DB}" || exit 6
echo "Backup file in: ${SOURCE_DB}.back"
rm -f "${TMP_DB}"
echo "Process completed OK"
0707010000002C000081A40000000000000000000000016693DA1900000996000000000000000000000000000000000000003C00000000tuptime-5.2.4/misc/scripts/db-tuptime-migrate-3.1-to-4.0.sh#!/bin/bash
# This script update the tuptime database format from version 3.1.0 or above to to 4.0.0
#
# Usage:
# Execute this script.
# It will update the db file on /var/lib/tuptime/tuptime.db
# The original db file will be renamed to /var/lib/tuptime/tuptime.[date].back
#
# Change the db origin:
# btime integer, uptime real, offbtime integer, endst integer, downtime real, kernel text
# to:
# btime integer, uptime real, rntime real, slptime real, offbtime integer, endst integer, downtime real, kernel text
SOURCE_DB='/var/lib/tuptime/tuptime.db'
USER_DB=$(stat -c '%U' "${SOURCE_DB}")
TMP_DBF=$(mktemp)
BKP_DATE=$(date +%s)
# Check bash execution
if [ ! -n "$BASH" ]; then
echo "--- WARNING - execute only with BASH ---"
fi
# Test file permissions
if [ -w "${SOURCE_DB}" ]; then
echo -e "\n## Migrating tuptime database format ##\n"
echo "Source file: ${SOURCE_DB}"
else
echo "Please, execute this script with a privileged user that can write in: ${SOURCE_DB}"
exit 1
fi
# Test sqlite3 command
sqlite3 -version > /dev/null
if [ $? -ne 0 ]; then
echo "Please, install \"sqlite3\" command for manage sqlite v3 databases."
exit 2
fi
# Work with a db copy
cp "${SOURCE_DB}" "${TMP_DBF}" || exit 4
# Adding new columns
sqlite3 "${TMP_DBF}" "CREATE TABLE tuptimeNew (btime integer, uptime integer, rntime integer, slptime integer, offbtime integer, endst integer, downtime integer, kernel text);" && \
sqlite3 "${TMP_DBF}" "UPDATE tuptime SET offbtime = cast(round(offbtime) as int);" && \
sqlite3 "${TMP_DBF}" "UPDATE tuptime SET uptime = cast(round(uptime) as int);" && \
sqlite3 "${TMP_DBF}" "UPDATE tuptime SET downtime = cast(round(downtime) as int);" && \
sqlite3 "${TMP_DBF}" "INSERT INTO tuptimeNew(btime, uptime, offbtime, endst, downtime, kernel) SELECT btime, uptime, offbtime, endst, downtime, kernel FROM tuptime;" && \
sqlite3 "${TMP_DBF}" "UPDATE tuptimeNew SET rntime = uptime;" && \
sqlite3 "${TMP_DBF}" "UPDATE tuptimeNew SET slptime = 0;" && \
sqlite3 "${TMP_DBF}" "DROP TABLE tuptime;" && \
sqlite3 "${TMP_DBF}" "ALTER TABLE tuptimeNew RENAME TO tuptime;" || exit 5
# Backup original db and rename the temp db as source
mv "${SOURCE_DB}" "${SOURCE_DB}"."${BKP_DATE}".back && \
mv "${TMP_DBF}" "${SOURCE_DB}" || exit 6
echo "Backup file: ${SOURCE_DB}.${BKP_DATE}.back"
# Set permission and user
chmod 644 "${SOURCE_DB}" && \
chown "${USER_DB}":"${USER_DB}" "${SOURCE_DB}" || exit 7
echo "Process completed: OK"
0707010000002D000081A40000000000000000000000016693DA19000009AB000000000000000000000000000000000000003C00000000tuptime-5.2.4/misc/scripts/db-tuptime-migrate-4.0-to-5.0.sh#!/bin/bash
# This script update the tuptime database format from version 4.0.0 or above to to 5.0.0
#
# Usage:
# Execute this script.
# It will update the db file on /var/lib/tuptime/tuptime.db
# The original db file will be renamed to /var/lib/tuptime/tuptime.[date].back
#
# Change the db origin:
# btime integer, uptime real, rntime real, slptime real, offbtime integer, endst integer, downtime real, kernel text
# to:
# bootid text, btime integer, uptime real, rntime real, slptime real, offbtime integer, endst integer, downtime real, kernel text
SOURCE_DB='/var/lib/tuptime/tuptime.db'
USER_DB=$(stat -c '%U' "${SOURCE_DB}")
TMP_DBF=$(mktemp)
BKP_DATE=$(date +%s)
#BOOTID=$(cat /proc/sys/kernel/random/boot_id)
# Check bash execution
if [ ! -n "$BASH" ]; then
echo "--- WARNING - execute only with BASH ---"
fi
# Test file permissions
if [ -w "${SOURCE_DB}" ]; then
echo -e "\n## Migrating tuptime database format ##\n"
echo "Source file: ${SOURCE_DB}"
else
echo "Please, execute this script with a privileged user that can write in: ${SOURCE_DB}"
exit 1
fi
# Test sqlite3 command
sqlite3 -version > /dev/null
if [ $? -ne 0 ]; then
echo "Please, install \"sqlite3\" command for manage sqlite v3 databases."
exit 2
fi
# Work with a db copy
cp "${SOURCE_DB}" "${TMP_DBF}" || exit 4
# Adding new columns
sqlite3 "${TMP_DBF}" "CREATE TABLE tuptimeNew (bootid text, btime integer, uptime integer, rntime integer, slptime integer, offbtime integer, endst integer, downtime integer, kernel text);" && \
sqlite3 "${TMP_DBF}" "INSERT INTO tuptimeNew(btime, uptime, rntime, slptime, offbtime, endst, downtime, kernel) SELECT btime, uptime, rntime, slptime, offbtime, endst, downtime, kernel FROM tuptime;" && \
#sqlite3 "${TMP_DBF}" "update tuptimeNew set bootid = \"${BOOTID}\" where rowid = (select max(rowid) from tuptimeNew)" && \
sqlite3 "${TMP_DBF}" "update tuptimeNew set bootid = 'None';" && \
sqlite3 "${TMP_DBF}" "update tuptimeNew set kernel = 'None' where kernel = '';" && \
sqlite3 "${TMP_DBF}" "DROP TABLE tuptime;" && \
sqlite3 "${TMP_DBF}" "ALTER TABLE tuptimeNew RENAME TO tuptime;" || exit 5
# Backup original db and rename the temp db as source
mv "${SOURCE_DB}" "${SOURCE_DB}"."${BKP_DATE}".back && \
mv "${TMP_DBF}" "${SOURCE_DB}" || exit 6
echo "Backup file: ${SOURCE_DB}.${BKP_DATE}.back"
# Set permission and user
chmod 644 "${SOURCE_DB}" && \
chown "${USER_DB}":"${USER_DB}" "${SOURCE_DB}" || exit 7
echo "Process completed: OK"
0707010000002E000081A40000000000000000000000016693DA190000045D000000000000000000000000000000000000002700000000tuptime-5.2.4/misc/scripts/runplots.sh#!/bin/bash
# This script executes all the plots available at the same time
# with the same arguments.
# Defaults
PyEx='python3'
Size='22x10'
pDays='30'
EndDT=$(date +"%d-%m-%y")
while getopts s:p:e: flag; do
case "${flag}" in
s)
Size=$(grep -oP '\d+x\d+' <<< ${OPTARG})
;;
p)
pDays=$(grep -oP '\d+' <<< ${OPTARG})
;;
e)
EndDT=$(grep -oP '\d+-\w+-\d+' <<< ${OPTARG})
;;
*)
echo "ERROR: Invalid argument flag" && exit -1
;;
esac
done
# Set X and Y size
Xcm=$(cut -dx -f1 <<< $Size)
Ycm=$(cut -dx -f2 <<< $Size)
XnY="-W $Xcm -H $Ycm"
echo "Execution: $0 [-s Width x Height] [ -p Past Days] [ -e End Date]"
echo "Example: $0 -s 22x10 -p 30 -e 31-Dec-20"
echo ""
echo -e "Making 4 plots in background...\n"
echo -e "Wide x Height:\t ${Xcm}x${Ycm}"
echo -e "Past Days:\t ${pDays}"
echo -e "End Date:\t ${EndDT}\n"
$PyEx ./tuptime-plot1.py $XnY -e $EndDT -p $pDays > /dev/null &
$PyEx ./tuptime-plot1.py $XnY -e $EndDT -p $pDays -x > /dev/null &
$PyEx ./tuptime-plot2.py $XnY -e $EndDT -p $pDays > /dev/null &
$PyEx ./tuptime-plot2.py $XnY -e $EndDT -p $pDays -x
0707010000002F000081A40000000000000000000000016693DA190000263F000000000000000000000000000000000000002C00000000tuptime-5.2.4/misc/scripts/tuptime-plot1.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Sample plot that reports the number of hours/events per every state
in each day. It extracts the info from tuptime command execution"""
from datetime import datetime, timedelta
import subprocess, csv, argparse, tempfile
import numpy as np
import matplotlib.pyplot as plt
import dateutil.parser
def get_arguments():
"""Get arguments from command line"""
parser = argparse.ArgumentParser()
parser.add_argument(
'-b', '--bdate',
dest='bdate',
action='store',
help='begin date to plot, format:"d-m-Y"',
type=str
)
parser.add_argument(
'-e', '--edate',
dest='edate',
action='store',
help='end date to plot, format:"d-m-Y" (default today)',
type=str
)
parser.add_argument(
'-f', '--filedb',
dest='dbfile',
default=None,
action='store',
help='database file'
)
parser.add_argument(
'-H', '--height',
dest='height',
default=13,
action='store',
help='window height in cm (default 13)',
type=int
)
parser.add_argument(
'-p', '--pastdays',
dest='pdays',
default=7,
action='store',
help='past days before edate to plot (default is 7)',
type=int
)
parser.add_argument(
'-W', '--width',
dest='width',
default=17,
action='store',
help='window width in cm (default 17)',
type=int
)
parser.add_argument(
'-x',
dest='report_events',
action='store_true',
default=False,
help='swich to report startup/shutdown events instead of hours'
)
arg = parser.parse_args()
return arg
def date_check(arg):
"""Check and clean dates"""
# Set user provided or default end date
if arg.edate:
end_date = dateutil.parser.parse(arg.edate, dayfirst=True)
else:
end_date = datetime.today()
print('Default end:\tnow')
# Set user provided or default begind date. Days ago...
if arg.bdate:
begin_date = dateutil.parser.parse(arg.bdate, dayfirst=True)
else:
begin_date = end_date - timedelta(days=arg.pdays)
print('Default begin:\tsince ' + str(arg.pdays) + ' days ago')
# Adjust date to the start or end time range and set the format
begin_date = begin_date.replace(hour=0, minute=0, second=0).strftime("%d-%b-%Y %H:%M:%S")
end_date = end_date.replace(hour=23, minute=59, second=59).strftime("%d-%b-%Y %H:%M:%S")
print('Begin datetime:\t' + str(begin_date))
print('End datetime:\t' + str(end_date))
return([begin_date, end_date])
def date_range(date_limits):
"""Get the range of dates to apply"""
dlimit = [] # date range in human date
ranepo = [] # date range in epoch
xlegend = [] # legend to x axis
# Get datetime objects from dates
dstart = dateutil.parser.parse(date_limits[0])
dend = dateutil.parser.parse(date_limits[1])
# Split time range in days
while dstart <= dend:
dlimit.append(dstart)
dstart += timedelta(days=1)
dlimit.append(dend) # Finally add last day time range until midnight
# Convert to epoch dates, pack two of them, begin and end for each split, and create a list with all
for reg in range(1, len(dlimit)):
ranepo.append([int(dlimit[reg-1].timestamp()), int(dlimit[reg].timestamp())])
xlegend.append(datetime.fromtimestamp(dlimit[reg-1].timestamp()).strftime('%d-%b-%y'))
print('Ranges on list:\t' + str(len(ranepo)))
return(ranepo, xlegend)
def main():
"""Core logic"""
arg = get_arguments()
date_limits = date_check(arg)
date_list, xlegend = date_range(date_limits)
daysplt = [] # List for all day splits with their events
ftmp = tempfile.NamedTemporaryFile().name # File to store Tuptime csv
shutst = None
# Iterate over each element in (since, until) list
for nran, _ in enumerate(date_list):
tsince = str(int(date_list[nran][0])) # Timestamp arg tsince
tuntil = str(int(date_list[nran][1])) # timestamp arg tuntil
# Query tuptime for every (since, until) and save output to a file
with open(ftmp, "wb", 0) as out:
if arg.dbfile: # If a file is passed, avoid update it
subprocess.call(["tuptime", "-lsc", "--tsince", tsince, "--tuntil", tuntil, "-f", arg.dbfile, "-n"], stdout=out)
else:
subprocess.call(["tuptime", "-lsc", "--tsince", tsince, "--tuntil", tuntil], stdout=out)
# Parse csv file
daysplit_events = []
with open(ftmp) as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
for row in csv_reader:
l_row = [0, 0, 0] # Events in csv rows
# Control how was the shutdown
if row[0] == 'Shutdown':
if row[1] == 'BAD': shutst = 'BAD'
if row[1] == 'OK': shutst = 'OK'
if arg.report_events:
# Populate list with (startup, shutdown ok, shutdown bad)
if ((row[0] == 'Startup') or (row[0] == 'Shutdown')) and len(row) > 2:
if row[0] == 'Startup' and row[2] == 'at':
l_row[0] = 1
if row[0] == 'Shutdown' and row[2] == 'at':
if shutst == 'BAD':
l_row[2] = 1
else:
l_row[1] = 1
else:
# Populate list with (uptime, downtime ok, downtime bad)
if (row[0] == 'Uptime') or (row[0] == 'Downtime'):
if row[0] == 'Uptime':
l_row[0] = int(row[1])
if row[0] == 'Downtime':
if shutst == 'BAD':
l_row[2] = int(row[1])
else:
l_row[1] = int(row[1])
# Add to events list per day
daysplit_events.append(l_row)
print(str(nran) + ' range --->\t' + str(len([i for i in daysplit_events if i != [0, 0, 0]])) + ' events')
# Per day, get total value for each type of event
if arg.report_events:
daysplit_events = [(sum(j)) for j in zip(*daysplit_events)]
else:
# Convert seconds to hours
daysplit_events = [(sum(j) / 3600) for j in zip(*daysplit_events)]
# Populate daysplt list with totals
daysplt.append(daysplit_events)
print('Ranges got:\t' + str(len(daysplt)))
# At this point daysplt have one of these:
#
# list_with_days[
# list_with_total_time of_each_type_of_event[
# uptime, downtime_ok, downtime_bad ]]
#
# list_with_days[
# list_with_total_counter of_each_type_of_event[
# startup, shutdown_ok, shutdown_bad ]]
# Matplotlib requires stack with slices
#
# y
# | up up up
# | down_ok down_ok down_ok
# | down_bad down_bad down_bad
# |----------------------------------x
# | day1 day2 dayN
# Get each state values slice from each day
days = {'up': [], 'down_ok': [], 'down_bad': []}
for i in daysplt:
days['up'].append(i[0])
days['down_ok'].append(i[1])
days['down_bad'].append(i[2])
ind = np.arange(len(daysplt)) # number of days on x
# Set width and height from inches to cm
plt.figure(figsize=((arg.width / 2.54), (arg.height / 2.54)))
if arg.report_events:
plt.ylabel('Events Counter')
plt.title('Events per State by Day')
maxv = max(i for v in days.values() for i in v) # Get max value on all ranges
plt.yticks(np.arange(0, (maxv + 1), 1))
plt.ylim(top=(maxv + 1))
rlabel = ['Startup', 'Shutdown Ok', 'Shutdown Bad']
width = 0.42 # column size
# Set position of bar on X axis
pst1 = np.arange(len(ind))
pst2 = [x + width for x in pst1]
plt.bar(pst1, days['up'], width, color='forestgreen', label=rlabel[0], edgecolor='white')
plt.bar(pst2, days['down_ok'], width, color='grey', label=rlabel[1], edgecolor='white', bottom=days['down_bad'])
plt.bar(pst2, days['down_bad'], width, color='black', label=rlabel[2], edgecolor='white')
ind = ind + width / 2
else:
plt.ylabel('Hours Counter')
plt.title('Hours per State by Day')
plt.yticks(np.arange(0, 25, 2))
plt.ylim(top=26)
rlabel = ['Uptime', 'Downtime']
# Merge all downtimes
days['down'] = [x + y for x, y in zip(days['down_ok'], days['down_bad'])]
# Old bar plot
#width = 0.9 # column size
#plt.bar(ind, days['up'], width, color='forestgreen', label=rlabel[0])
#plt.bar(ind, days['down'], width, color='grey', label=rlabel[1], bottom=days['up'])
plt.plot(ind, days['up'], linewidth=2, marker='o', color='forestgreen', label=rlabel[0])
plt.plot(ind, days['down'], linewidth=2, marker='o', color='grey', linestyle='--', label=rlabel[1])
plt.grid(color='lightblue', linestyle='--', linewidth=0.5, axis='x')
plt.xticks(ind, xlegend)
plt.gcf().autofmt_xdate()
plt.margins(y=0, x=0.01)
plt.grid(color='lightgrey', linestyle='--', linewidth=0.5, axis='y')
plt.tight_layout()
cfig = plt.get_current_fig_manager()
cfig.canvas.manager.set_window_title("Tuptime")
plt.legend()
plt.show()
if __name__ == "__main__":
main()
07070100000030000081A40000000000000000000000016693DA190000224D000000000000000000000000000000000000002C00000000tuptime-5.2.4/misc/scripts/tuptime-plot2.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import subprocess, csv, argparse, tempfile
from datetime import datetime, timedelta
from collections import Counter
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import dateutil.parser
def get_arguments():
"""Get arguments from command line"""
parser = argparse.ArgumentParser()
parser.add_argument(
'-b', '--bdate',
dest='bdate',
action='store',
help='begin date to plot, format:"d-m-Y"',
type=str
)
parser.add_argument(
'-e', '--edate',
dest='edate',
action='store',
help='end date to plot, format:"d-m-Y" (default today)',
type=str
)
parser.add_argument(
'-f', '--filedb',
dest='dbfile',
default=None,
action='store',
help='database file'
)
parser.add_argument(
'-H', '--height',
dest='height',
default=13,
action='store',
help='window height in cm (default 13)',
type=int
)
parser.add_argument(
'-p', '--pastdays',
dest='pdays',
default=7,
action='store',
help='past days before edate to plot (default is 7)',
type=int
)
parser.add_argument(
'-W', '--width',
dest='width',
default=17,
action='store',
help='window width in cm (default 17)',
type=int
)
parser.add_argument(
'-x',
dest='report_pie',
action='store_true',
default=False,
help='swich to pie report with accumulated hours'
)
arg = parser.parse_args()
return arg
def date_check(arg):
"""Check and clean dates"""
# Set user provided or default end date
if arg.edate:
end_date = dateutil.parser.parse(arg.edate, dayfirst=True)
else:
end_date = datetime.today()
print('Default end:\tnow')
# Set user provided or default begind date. Days ago...
if arg.bdate:
begin_date = dateutil.parser.parse(arg.bdate, dayfirst=True)
else:
begin_date = end_date - timedelta(days=arg.pdays)
print('Default begin:\tsince ' + str(arg.pdays) + ' days ago')
# Adjust date to the start or end time range and set the format
begin_date = begin_date.replace(hour=0, minute=0, second=0).strftime("%d-%b-%Y %H:%M:%S")
end_date = end_date.replace(hour=23, minute=59, second=59).strftime("%d-%b-%Y %H:%M:%S")
print('Begin datetime:\t' + str(begin_date))
print('End datetime:\t' + str(end_date))
return([begin_date, end_date])
def main():
"""Core logic"""
arg = get_arguments()
date_limits = date_check(arg)
ftmp = tempfile.NamedTemporaryFile().name # For Tuptime csv file
tst = {'up': [], 'down': [], 'down_ok': [], 'down_bad': []} # Store events list on range
# Get datetime objects from date limits in timestamp format
tsince = str(int(dateutil.parser.parse(date_limits[0]).timestamp()))
tuntil = str(int(dateutil.parser.parse(date_limits[1]).timestamp()))
# Query tuptime for every (since, until) and save output to a file
with open(ftmp, "wb", 0) as out:
if arg.dbfile: # If a file is passed, avoid update it
subprocess.call(["tuptime", "-tsc", "--tsince", tsince, "--tuntil", tuntil, "-f", arg.dbfile, "-n"], stdout=out)
else:
subprocess.call(["tuptime", "-tsc", "--tsince", tsince, "--tuntil", tuntil], stdout=out)
# Parse csv file
with open(ftmp) as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
for row in csv_reader:
if row[0] == 'No.':
continue
#print('Startup T.: ' + row[1])
#print('Uptime: ' + row[2])
#print('Shutdown T.: ' + row[3])
#print('End: ' + row[4])
#print('Downtime: ' + row[5])
if row[1] != '':
tst['up'].append(row[1])
if row[3] != '':
if row[4] == 'BAD':
tst['down_bad'].append(row[3])
else:
tst['down_ok'].append(row[3])
# Set whole downtimes and convert to datetime object
tst['down'] = tst['down_ok'] + tst['down_bad']
for state in tst:
tst[state] = [datetime.fromtimestamp(int(elem)) for elem in tst[state]]
if arg.report_pie:
pie = {'up': [], 'down': []} # One plot for each type
# From datetime, get only hour
for elem in tst['up']: pie['up'].append(str(elem.hour))
for elem in tst['down']: pie['down'].append(str(elem.hour))
# Count elements on list or set '0' if empty. Get list with items
pie['up'] = dict(Counter(pie['up'])).items() if pie['up'] else [('0', 0)]
pie['down'] = dict(Counter(pie['down'])).items() if pie['down'] else [('0', 0)]
# Values ordered by first element on list that was key on source dict
pie['up'] = sorted(pie['up'], key=lambda ordr: int(ordr[0]))
pie['down'] = sorted(pie['down'], key=lambda ordr: int(ordr[0]))
# Set two plots and their frame size
_, axs = plt.subplots(1, 2, figsize=((arg.width / 2.54), (arg.height / 2.54)))
# Set values for each pie plot
axs[0].pie([v[1] for v in pie['up']], labels=[k[0].rjust(2, '0') + str('h') for k in pie['up']],
autopct=lambda p : '{:.1f}%\n{:,.0f}'.format(p,p * sum([v[1] for v in pie['up']])/100),
startangle=90, counterclock=False,
textprops={'fontsize': 8}, wedgeprops={'alpha':0.85})
axs[0].set(aspect="equal", title='Startup')
axs[1].pie([v[1] for v in pie['down']], labels=[str(k[0]).rjust(2, '0') + str('h') for k in pie['down']],
autopct=lambda p : '{:.1f}%\n{:,.0f}'.format(p,p * sum([v[1] for v in pie['down']])/100),
startangle=90, counterclock=False,
textprops={'fontsize': 8}, wedgeprops={'alpha':0.85})
axs[1].set(aspect="equal", title='Shutdown')
plt.suptitle("Events per Hours in Range", fontsize=14)
else:
# Reset date allows position circles inside the same 00..24 range on y-axis
scatt_y = {'up': [], 'down_ok': [], 'down_bad': []}
scatt_y['up'] = [elem.replace(year=1970, month=1, day=1) for elem in tst['up']]
scatt_y['down_ok'] = [elem.replace(year=1970, month=1, day=1) for elem in tst['down_ok']]
scatt_y['down_bad'] = [elem.replace(year=1970, month=1, day=1) for elem in tst['down_bad']]
# Reset hour allows position circles straight over the date tick on x-axis
scatt_x = {'up': [], 'down_ok': [], 'down_bad': []}
scatt_x['up'] = [elem.replace(hour=00, minute=00, second=00) for elem in tst['up']]
scatt_x['down_ok'] = [elem.replace(hour=00, minute=00, second=00) for elem in tst['down_ok']]
scatt_x['down_bad'] = [elem.replace(hour=00, minute=00, second=00) for elem in tst['down_bad']]
# Set width and height from inches to cm
plt.figure(figsize=((arg.width / 2.54), (arg.height / 2.54)))
# Set scatter plot values
plt.scatter(scatt_x['up'], scatt_y['up'], s=200, color='forestgreen', edgecolors='white', alpha=0.85, marker="X", label='Up')
plt.scatter(scatt_x['down_ok'], scatt_y['down_ok'], s=200, color='grey', edgecolors='white', alpha=0.85, marker="X", label='Down ok')
plt.scatter(scatt_x['down_bad'], scatt_y['down_bad'], s=200, color='black', edgecolors='white', alpha=0.85, marker="X", label='Down bad')
# Format axes:
plt.gcf().autofmt_xdate()
axs = plt.gca()
# X as days and defined limits with their margin
axs.xaxis.set_major_formatter(mdates.DateFormatter('%d-%b-%y'))
axs.xaxis.set_major_locator(mdates.DayLocator())
plt.xlim(datetime.strptime(date_limits[0], '%d-%b-%Y %H:%M:%S') - timedelta(hours=4),
datetime.strptime(date_limits[1], '%d-%b-%Y %H:%M:%S') - timedelta(hours=20))
# Y as 24 hours range
axs.yaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
axs.yaxis.set_major_locator(mdates.HourLocator(byhour=range(0, 24, 2)))
plt.ylim([datetime(1970, 1, 1, 00, 00), datetime(1970, 1, 1, 23, 59, 59)])
axs.set_axisbelow(True)
axs.invert_yaxis()
plt.grid(True)
plt.ylabel('Day Time')
plt.title('Events on Time by Day')
plt.margins(y=0, x=0.01)
plt.grid(color='lightgrey', linestyle='--', linewidth=0.9, axis='y')
plt.legend()
plt.tight_layout()
cfig = plt.get_current_fig_manager()
cfig.canvas.manager.set_window_title("Tuptime")
plt.show()
if __name__ == "__main__":
main()
07070100000031000081A40000000000000000000000016693DA1900001D7D000000000000000000000000000000000000003000000000tuptime-5.2.4/misc/scripts/tuptime-powerplot.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Sample plot that reports the roughly electric cost per day based on uptime.
It extracts the info from tuptime command execution"""
# Basic usage:
#
# A power consumption of 23 kWh and a kWh cost of 0,66089€:
#
# $ tuptime-powerplot.py -k 0.66089 23
#
# A power consumption of 55 kWh and a MWh cost of 150€:
#
# $ tuptime-powerplot.py -m 150 55
#
# A power consumption of 35 kWh, a kWh cost of 0.59$ for last 15 days:
#
# $ tuptime-powerplot.py -p 15 -k 0.59 35
#
# A power consumption of 40 kWh, a kWh cost of 0.44€ since 1-Jan-2020 to 1-Feb-2020:
#
# $ tuptime-powerplot.py -k 0.44 -b "01-01-2020" -e "01-02-2020" 40
#
# A power consumption of 40 kWh, a kWh cost of 0.44€ for last 15 days:
#
# $ tuptime-powerplot.py -k 0.44 -p 15 40
#
from datetime import datetime, timedelta
import subprocess, csv, argparse, tempfile
import numpy as np
import matplotlib.pyplot as plt
import dateutil.parser
def get_arguments():
"""Get arguments from command line"""
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
parser.add_argument(
'-b', '--bdate',
dest='bdate',
action='store',
help='begin date to plot, format:"d-m-Y"',
type=str
)
parser.add_argument(
'consum',
default=None,
help='power consumption of the device in Watts / hour',
metavar='kWh_consumption',
type=float
)
parser.add_argument(
'-e', '--edate',
dest='edate',
action='store',
help='end date to plot, format:"d-m-Y" (default today)',
type=str
)
parser.add_argument(
'-f', '--filedb',
dest='dbfile',
default=None,
action='store',
help='database file'
)
parser.add_argument(
'-H', '--height',
dest='height',
default=13,
action='store',
help='window height in cm (default 13)',
type=int
)
group.add_argument(
'-k', '--kwh',
dest='kwh',
default=None,
action='store',
help='set price for Kilowatt hour',
type=float,
metavar='kWh'
)
group.add_argument(
'-m', '--mwh',
dest='mwh',
default=None,
action='store',
help='set price for Megawatt hour',
type=float,
metavar='MWh'
)
parser.add_argument(
'-p', '--pastdays',
dest='pdays',
default=7,
action='store',
help='past days before edate to plot (default is 7)',
type=int
)
parser.add_argument(
'-W', '--width',
dest='width',
default=17,
action='store',
help='window width in cm (default 17)',
type=int
)
arg = parser.parse_args()
if not (arg.kwh or arg.mwh):
parser.error('Set almost one price for -m (MWh) or -k (kWh)')
return arg
def date_check(arg):
"""Check and clean dates"""
# Set user provided or default end date
if arg.edate:
end_date = dateutil.parser.parse(arg.edate, dayfirst=True)
else:
end_date = datetime.today()
print('Default end:\tnow')
# Set user provided or default begind date. Days ago...
if arg.bdate:
begin_date = dateutil.parser.parse(arg.bdate, dayfirst=True)
else:
begin_date = end_date - timedelta(days=arg.pdays)
print('Default begin:\tsince ' + str(arg.pdays) + ' days ago')
# Adjust date to the start or end time range and set the format
begin_date = begin_date.replace(hour=0, minute=0, second=0).strftime("%d-%b-%Y %H:%M:%S")
end_date = end_date.replace(hour=23, minute=59, second=59).strftime("%d-%b-%Y %H:%M:%S")
print('Begin datetime:\t' + str(begin_date))
print('End datetime:\t' + str(end_date))
return([begin_date, end_date])
def date_range(date_limits):
"""Get the range of dates to apply"""
dlimit = [] # date range in human date
ranepo = [] # date range in epoch
xlegend = [] # legend to x axis
# Get datetime objects from dates
dstart = dateutil.parser.parse(date_limits[0])
dend = dateutil.parser.parse(date_limits[1])
# Split time range in days
while dstart <= dend:
dlimit.append(dstart)
dstart += timedelta(days=1)
dlimit.append(dend) # Finally add last day time range until midnight
# Convert to epoch dates, pack two of them, begin and end for each split, and create a list with all
for reg in range(1, len(dlimit)):
ranepo.append([int(dlimit[reg-1].timestamp()), int(dlimit[reg].timestamp())])
xlegend.append(datetime.fromtimestamp(dlimit[reg-1].timestamp()).strftime('%d-%b-%y'))
print('Ranges on list:\t' + str(len(ranepo)))
return(ranepo, xlegend)
def main():
"""Core logic"""
arg = get_arguments()
date_limits = date_check(arg)
date_list, xlegend = date_range(date_limits)
# Set price for Watts per second
if arg.mwh:
arg.kwh = arg.mwh / 1000
wth = arg.kwh / 1000
wts = wth / 3600
consum = arg.consum
days = [] # List for day values
ftmp = tempfile.NamedTemporaryFile().name # File to store Tuptime csv
# Iterate over each element in (since, until) list
for nran, _ in enumerate(date_list):
tsince = str(int(date_list[nran][0])) # Timestamp arg tsince
tuntil = str(int(date_list[nran][1])) # timestamp arg tuntil
# Query tuptime for every (since, until) and save output into a file
with open(ftmp, "wb", 0) as out:
if arg.dbfile: # If a file is passed, avoid update it
subprocess.call(["tuptime", "-lsc", "--tsince", tsince, "--tuntil", tuntil, "-f", arg.dbfile, "-n"], stdout=out)
else:
subprocess.call(["tuptime", "-lsc", "--tsince", tsince, "--tuntil", tuntil], stdout=out)
# Parse csv file
with open(ftmp) as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
uptimes = [] # Uptime events in csv rows
for row in csv_reader:
# Populate list with all uptimes inside each day
if row[0] == 'Uptime':
uptimes.append(int(row[1]))
# Populate with total price calculation
days.append(sum(uptimes) * wts * consum)
print(str(nran) + ' range --->\t' + str(sum(uptimes)) + 's - ' + str(days[-1]))
print('Ranges got:\t' + str(len(days)))
print('Power cons.:\t' + str(consum) + ' kWh')
print('kWh price:\t' + str(arg.kwh))
print('Total cost:\t' + str(round(sum(days), 2)))
# Create plot
ind = np.arange(len(days)) # number of days on x
plt.figure(figsize=((arg.width / 2.54), (arg.height / 2.54))) # Set values from inches to cm
plt.title('Roughly Electric Cost per Day')
plt.figtext(0.005, 0.005, "Total: " + str(round(sum(days), 2)), weight=1000)
plt.bar(ind, days, color='steelblue')
#plt.plot(ind, days, linewidth=2, marker='o', color='forestgreen')
plt.grid(color='lightblue', linestyle='--', linewidth=0.5, axis='x')
plt.xticks(ind, xlegend)
plt.gcf().autofmt_xdate()
plt.ylabel('Cost in currency')
plt.margins(y=0.01, x=0.01)
plt.grid(color='lightgrey', linestyle='--', linewidth=0.5, axis='y')
plt.tight_layout()
cfig = plt.get_current_fig_manager()
cfig.canvas.manager.set_window_title("Tuptime")
plt.show()
if __name__ == "__main__":
main()
07070100000032000081A40000000000000000000000016693DA19000020A1000000000000000000000000000000000000002E00000000tuptime-5.2.4/misc/scripts/tuptime_dbcheck.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
Test database integrity. Try to catch weird errors and fix them.
'''
import sys, argparse, locale, signal, logging, sqlite3
__version__ = '1.1.1'
DB_FILE = '/var/lib/tuptime/tuptime.db'
fixcnt = 0
errcnt = 0
# List of tests to auto-execute
TESTS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Terminate when SIGPIPE signal is received
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
# Set locale to the users default settings (LANG env. var)
locale.setlocale(locale.LC_ALL, '')
def get_arguments():
"""Get arguments from command line"""
parser = argparse.ArgumentParser()
parser.add_argument(
'-f', '--filedb',
dest='db_file',
default=DB_FILE,
action='store',
help='database file (' + DB_FILE + ')',
metavar='FILE'
)
parser.add_argument(
'--fix',
dest='fix',
default=False,
action='store_true',
help='fix errors on db file'
)
parser.add_argument(
'-t', '--test',
dest='test',
nargs='+',
type=int,
default=TESTS,
help='execute only this test'
)
parser.add_argument(
'-v', '--verbose',
dest='verbose',
default=False,
action='store_true',
help='verbose output'
)
arg = parser.parse_args()
if arg.verbose:
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.info('Version: %s', (__version__))
logging.info('Arguments: %s', vars(arg))
return arg
def err_cnt(arg):
global errcnt
global fixcnt
if arg.fix:
fixcnt += 1
errcnt += 1
def test0(arg, db_rows, conn):
if len(db_rows) != db_rows[-1]['startup']:
print(' Possible deleted rows in db. Real startups are not equal to enumerate startups')
if arg.fix:
conn.execute('vacuum')
print(' FIXED: vacuum')
err_cnt(arg)
def test1(arg, row, conn):
if row['offbtime'] and \
row['btime'] > row['offbtime']:
print(row['startup'])
print(' row btime > offbtime')
print(' ' + str(row['btime']) + ' > ' + str(row['offbtime']))
if arg.fix:
conn.execute('delete from tuptime where rowid =?', (row['startup'],))
print(' FIXED: delete row = ' + str(row['startup']))
err_cnt(arg)
def test2(arg, row, conn, prev_row):
if prev_row['offbtime'] > row['btime']:
print(row['startup'])
print(' prev_row offbtime > btime')
print(' ' + str(prev_row['offbtime']) + ' > ' + str(row['btime']))
if arg.fix:
conn.execute('delete from tuptime where rowid =?', (row['startup'],))
print(' FIXED: delete row = ' + str(row['startup']))
err_cnt(arg)
def test3(arg, row, conn, prev_row):
if prev_row['offbtime'] + prev_row['downtime'] != row['btime']:
print(row['startup'])
print(' prev_row offbtime + prev_row downtime != btime')
print(' ' + str(prev_row['offbtime']) + ' + ' + str(prev_row['downtime']) + ' != ' + str(row['btime']))
if arg.fix:
fixed = row['btime'] - prev_row['offbtime']
conn.execute('update tuptime set downtime =? where rowid =?', (fixed, (row['startup'] - 1)))
print(' FIXED: prev_row downtime = ' + str(fixed))
err_cnt(arg)
def test4(arg, row, conn):
if row['offbtime'] and \
row['btime'] + row['uptime'] != row['offbtime']:
print(row['startup'])
print(' row btime + uptime != offbtime')
print(' ' + str(row['btime']) + ' + ' + str(row['uptime']) + ' != ' + str(row['offbtime']))
if arg.fix:
fixed = row['offbtime'] - row['btime']
conn.execute('update tuptime set uptime =? where rowid =?', (fixed, row['startup']))
print(' FIXED: uptime = ' + str(fixed))
err_cnt(arg)
def test5(arg, row, conn):
if row['rntime'] + row['slptime'] != row['uptime']:
print(row['startup'])
print(' rntime + slptime != uptime')
print(' ' + str(row['rntime']) + ' + ' + str(row['slptime']) + ' != ' + str(row['uptime']))
if arg.fix:
fixed = row['rntime'] + row['slptime'] - row['uptime']
if row['rntime'] > row['slptime'] and row['rntime'] - fixed > 0:
fixed2 = row['rntime'] - fixed
conn.execute('update tuptime set rntime =? where rowid =?', (fixed2, row['startup']))
print(' FIXED: rntime = ' + str(fixed2))
elif row['slptime'] >= row['rntime'] and row['slptime'] - fixed > 0:
fixed2 = row['slptime'] - fixed
conn.execute('update tuptime set slptime =? where rowid =?', (fixed2, row['startup']))
print(' FIXED: slptime = ' + str(fixed2))
else:
conn.execute('update tuptime set rntime =?, slptime = 0 where rowid =?', (row['uptime'], row['startup']))
print(' FIXED: rntime = ' + str(row['uptime']))
print(' FIXED: slptime = 0')
err_cnt(arg)
def test6(arg, row, conn):
if row['uptime'] < 0:
print(row['startup'])
print(' uptime < 0')
print(' ' + str(row['uptime']) + ' < 0')
if arg.fix:
conn.execute('delete from tuptime where rowid =?', (row['startup'],))
print(' FIXED: delete row = ' + str(row['startup']))
err_cnt(arg)
def test7(arg, row, conn):
if row['rntime'] < 0:
print(row['startup'])
print(' rntime < 0')
print(' ' + str(row['rntime']) + ' < 0')
if arg.fix:
conn.execute('delete from tuptime where rowid =?', (row['startup'],))
print(' FIXED: delete row = ' + str(row['startup']))
err_cnt(arg)
def test8(arg, row, conn):
if row['slptime'] < 0:
print(row['startup'])
print(' slptime < 0')
print(' ' + str(row['slptime']) + ' < 0')
if arg.fix:
conn.execute('delete from tuptime where rowid =?', (row['startup'],))
print(' FIXED: delete row = ' + str(row['startup']))
err_cnt(arg)
def test9(arg, row, conn):
if row['downtime'] and \
row['downtime'] < 0:
print(row['startup'])
print(' downtime < 0')
print(' ' + str(row['downtime']) + ' < 0')
if arg.fix:
conn.execute('delete from tuptime where rowid =?', (row['startup'],))
print(' FIXED: delete row = ' + str(row['startup']))
err_cnt(arg)
def main():
arg = get_arguments()
db_conn = sqlite3.connect(arg.db_file)
db_conn.row_factory = sqlite3.Row
db_conn.set_trace_callback(logging.debug)
conn = db_conn.cursor()
# Check if DB have the old format
columns = [i[1] for i in conn.execute('PRAGMA table_info(tuptime)')]
if 'rntime' and 'slptime' and 'bootid' not in columns:
logging.error('DB format outdated')
sys.exit(1)
print('Processing ' + str(arg.db_file) + ' --->')
for i in arg.test:
print('\n### Test ' + str(i) + ' ###')
conn.execute('select rowid as startup, * from tuptime')
db_rows = conn.fetchall()
for row in db_rows:
if arg.verbose:
print('\n' + str(row['startup']) + '\n ' + str(dict(row)))
if i == 1:
test1(arg, row, conn)
if i == 2:
if row != db_rows[0]: # Only after first row
test2(arg, row, conn, prev_row)
if i == 3:
if row != db_rows[0]: # Only after first row
test3(arg, row, conn, prev_row)
if i == 4:
test4(arg, row, conn)
if i == 5:
test5(arg, row, conn)
if i == 6:
test6(arg, row, conn)
if i == 7:
test7(arg, row, conn)
if i == 8:
test8(arg, row, conn)
if i == 9:
test9(arg, row, conn)
prev_row = row
if i == 10:
test0(arg, db_rows, conn)
db_conn.commit()
db_conn.close()
print('\n' + '-' * 25)
print('Errors: ' + str(errcnt))
print('Fixed: ' + str(fixcnt))
print('')
if __name__ == "__main__":
main()
07070100000033000081A40000000000000000000000016693DA1900001D40000000000000000000000000000000000000002B00000000tuptime-5.2.4/misc/scripts/tuptime_join.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
This script join two tuptime db files into an other one. It works as follows:
- Takes two paths of db files and an output file as destination of the join.
- Look for first btime on both files and take the one with the older value as starting DB
- Look on starting DB for last register and sum (btime + uptime)
- Look on the newer file for a btime greater than previous sum value
- Insert registers that match on destination file DB
# tuptime_join.py /path/to/old.db /path/to/new.db -d /path/to/joined.db
Maybe after upgrade your computer and install new stuff, you want to continue with the registers
that you have before. It is possible to join the new ones into the old ones without any awkward jump.
Please, see the following example:
Join registers of two db files into other:
tuptime_join.py /backup/old/tuptime.db /var/lib/tuptime/tuptime.db -d /tmp/tt.db
Check if all is ok:
tuptime --noup -t -f /tmp/tt.db
Check owner (usually tuptime:tuptime) and copy modified file to right location. Re-check owner:
ls -al /var/lib/tuptime/tuptime.db
cp /tmp/tt.db /var/lib/tuptime/tuptime.db
ls -al /var/lib/tuptime/tuptime.db
'''
import sys, argparse, locale, signal, logging, sqlite3
from shutil import copyfile
__version__ = '1.2.1'
# Terminate when SIGPIPE signal is received
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
# Set locale to the users default settings (LANG env. var)
locale.setlocale(locale.LC_ALL, '')
def get_arguments():
"""Get arguments from command line"""
parser = argparse.ArgumentParser()
parser.add_argument(
'files',
metavar='FILE',
nargs=2,
type=str,
help='files to join'
)
parser.add_argument(
'-d', '--dest',
dest='dest',
default=False,
action='store',
type=str,
required=True,
help='destination file to store join'
)
parser.add_argument(
'-v', '--verbose',
dest='verbose',
action='count',
default=0,
help='verbose output (vv=detail)'
)
arg = parser.parse_args()
if arg.verbose:
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.info('Version: %s', (__version__))
logging.info('Arguments: %s', vars(arg))
return arg
def order_files(arg):
"""Identify older file"""
# Open file0 DB
db_conn0 = sqlite3.connect(arg.files[0])
db_conn0.row_factory = sqlite3.Row
db_conn0.set_trace_callback(logging.debug)
conn0 = db_conn0.cursor()
# Open file1 DB
db_conn1 = sqlite3.connect(arg.files[1])
db_conn1.row_factory = sqlite3.Row
db_conn1.set_trace_callback(logging.debug)
conn1 = db_conn1.cursor()
# Check if DBs have the old format
for conn, fname in ((conn0, arg.files[0]), (conn1, arg.files[1])):
columns = [i[1] for i in conn.execute('PRAGMA table_info(tuptime)')]
if 'rntime' and 'slptime' and 'bootid' not in columns:
logging.error('DB format outdated on file: %', str(fname))
sys.exit(1)
# Check older file
conn0.execute('select btime from tuptime where rowid = (select min(rowid) from tuptime)')
conn1.execute('select btime from tuptime where rowid = (select min(rowid) from tuptime)')
# File with large btime is newer
if (conn0.fetchone()[0]) > (conn1.fetchone()[0]):
return arg.files[1], arg.files[0]
return arg.files[0], arg.files[1]
def main():
"""Main logic"""
arg = get_arguments()
f_old, f_new = order_files(arg)
print('Old source file: \t' + str(f_old))
print('New source file:\t' + str(f_new))
# Use old file as starting DB. Copy as destination file.
copyfile(f_old, arg.dest)
fl0 = {'path': arg.dest} # file0 is destination file
fl1 = {'path': f_new} # file1 is source newer file
print('Destination file: \t' + str(fl0['path']))
# Open file0 DB
db_conn0 = sqlite3.connect(fl0['path'])
db_conn0.row_factory = sqlite3.Row
db_conn0.set_trace_callback(logging.debug)
conn0 = db_conn0.cursor()
# Open file1 DB
db_conn1 = sqlite3.connect(fl1['path'])
db_conn1.row_factory = sqlite3.Row
db_conn1.set_trace_callback(logging.debug)
conn1 = db_conn1.cursor()
# Get all rows from source file0 and print raw rows
conn0.execute('select rowid as startup, * from tuptime')
db_rows = conn0.fetchall()
print('\nDestination rows before:\t' + str(len(db_rows)))
if arg.verbose > 1:
for row in db_rows:
print("\t", end='')
print(dict(row))
# Get last startup, btime and uptime from last row on file0 to calculate offbtime
conn0.execute('select rowid, btime, uptime from tuptime where rowid = (select max(rowid) from tuptime)')
fl0['startup'], fl0['btime'], fl0['uptime'] = conn0.fetchone()
fl0['offbtime'] = fl0['btime'] + fl0['uptime']
# Get all rows from file1 where btime is greater than file0 offbtime
conn1.execute('select rowid as startup, * from tuptime where btime >?', (fl0['offbtime'],))
db_rows = conn1.fetchall()
print('\nRows to add from newer file: \t' + str(len(db_rows)))
# Insert rows on file0
for row in db_rows:
# At first row on file1, set offbtime, endst and downtime to last row on file0
if row == db_rows[0]:
print(' Fix shutdown values on row: ' + str(fl0['startup']))
fl0['downtime'] = row['btime'] - fl0['offbtime']
conn0.execute('update tuptime set offbtime =?, endst = 1, downtime =? '
'where rowid = (select max(rowid) from tuptime)', (fl0['offbtime'], fl0['downtime']))
if arg.verbose:
print('\toffbtime = ' + str(fl0['offbtime']))
print('\tendst = 1')
print('\tdowntime = ' + str(fl0['downtime']))
# Add registers to file0
print(' Adding startup row: ' + str(row['startup']))
conn0.execute('insert into tuptime values (?,?,?,?,?,?,?,?,?)',
(row['bootid'], row['btime'], row['uptime'], row['rntime'], row['slptime'], row['offbtime'], row['endst'], row['downtime'], row['kernel']))
if arg.verbose:
print('\tbootid = ' + str(row['bootid']))
print('\tbtime = ' + str(row['btime']))
print('\tuptime = ' + str(row['uptime']))
print('\trntime = ' + str(row['rntime']))
print('\tslptime = ' + str(row['slptime']))
print('\toffbtime = ' + str(row['offbtime']))
print('\tendst = ' + str(row['endst']))
print('\tdowntime = ' + str(row['downtime']))
print('\tkernel = ' + str(row['kernel']))
# At last row, empty offbtime, endst and downtime
if row == db_rows[-1]:
print(' Fix shutdown values on last row')
conn0.execute('update tuptime set offbtime = NULL, endst = 0, downtime = NULL where rowid = (select max(rowid) from tuptime)')
db_conn0.commit()
# Print raw rows
conn0.execute('select rowid as startup, * from tuptime')
db_rows = conn0.fetchall()
print('\nDestination rows after: \t' + str(len(db_rows)))
if arg.verbose > 1:
for row in db_rows:
print("\t", end='')
print(dict(row))
db_conn0.close()
db_conn1.close()
print('\nDone.')
if __name__ == "__main__":
main()
07070100000034000081A40000000000000000000000016693DA19000028A3000000000000000000000000000000000000002D00000000tuptime-5.2.4/misc/scripts/tuptime_modify.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
This script modify the "Startup Timestamp", "Shutdown Timestamp" and
"End Status" on Tuptime database preserving the other values around in sync.
Increase 60 secs the startup datetime on register number 1:
tuptime_modify.py -c startup -r 1 -s 60
Decrease 100 secs the startup datetime on register number 4 on other file:
tuptime_modify.py -c startup -r 4 -s -100 -f /tmp/test.db
Increase 30 secs the shutdown datetime on register number 12:
tuptime_modify.py -c shutdown -r 12 -s 60
Decrease 300 secs the shutdown datetime on register number 47 with verbose:
tuptime_modify.py -c shutdown -r 47 -s -300 -v
Swich end status value on register 54:
tuptime_modify.py -c endst -r 54
'''
import sys, argparse, locale, signal, logging, sqlite3, tempfile
from shutil import copyfile
DB_FILE = '/var/lib/tuptime/tuptime.db'
DATE_FORMAT = '%X %x'
__version__ = '1.0.1'
# Terminate when SIGPIPE signal is received
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
# Set locale to the users default settings (LANG env. var)
locale.setlocale(locale.LC_ALL, '')
def get_arguments():
"""Get arguments from command line"""
parser = argparse.ArgumentParser()
parser.add_argument(
'-c', '--change',
dest='change',
default=False,
action='store',
type=str,
required=True,
choices=['startup', 'shutdown', 'endst'],
help='change startup or shutdown datetime [<startup|shutdown|endst>]'
)
parser.add_argument(
'-f', '--filedb',
dest='db_file',
default=DB_FILE,
action='store',
help='database file (' + DB_FILE + ')',
metavar='FILE'
)
parser.add_argument(
'-n', '--nobackup',
dest='nobackup',
default=False,
action='store_true',
help='avoid create backup db file'
)
parser.add_argument(
'-r', '--register',
dest='register',
default=False,
action='store',
type=int,
required=True,
help='startup register to modify'
)
parser.add_argument(
'-s', '--seconds',
dest='seconds',
default=0,
action='store',
type=int,
help='seconds to add or remove (+/-)'
)
parser.add_argument(
'-v', '--verbose',
dest='verbose',
default=False,
action='store_true',
help='verbose output'
)
arg = parser.parse_args()
if arg.verbose:
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.info('Version: %s', (__version__))
if arg.change == 'endst' and arg.seconds:
parser.error('Operator \"seconds\" can\'t be combined with \"endst\"')
logging.info('Arguments: %s', vars(arg))
return arg
def backup_dbf(arg):
"""Backup db file before modify"""
try:
tmp_file = tempfile.NamedTemporaryFile(prefix="tuptime_", suffix=".db").name
copyfile(arg.db_file, tmp_file)
print('Backup file:\t' + tmp_file)
except Exception as exp:
logging.error('Can\'t create backup file. %s', exp)
sys.exit(1)
def fix_endst(arg, reg, conn, modt, orgt):
"""Swich end status register"""
# Swich between values 1 to 0 and 0 to 1
modt['endst'] = 1 - orgt['endst']
print('\t modified\tendst: ' + str(modt['endst']))
# Update values
conn.execute('update tuptime set endst =? where rowid =?', (modt['endst'], reg['target']))
def fix_shutdown(arg, reg, conn, modt, orgt):
"""Modify shutdown datetime register"""
# Last row have None values
if orgt['offbtime'] is None and orgt['downtime'] is None:
modt['offbtime'] = None
modt['downtime'] = None
else:
modt['offbtime'] = orgt['offbtime'] + arg.seconds
modt['downtime'] = orgt['downtime'] - arg.seconds
modt['uptime'] = orgt['uptime'] + arg.seconds
if orgt['rntime'] + arg.seconds > 0:
modt['rntime'] = orgt['rntime'] + arg.seconds
modt['slptime'] = orgt['slptime']
elif orgt['slptime'] + arg.seconds >= 0:
modt['rntime'] = orgt['rntime']
modt['slptime'] = orgt['slptime'] + arg.seconds
else:
modt['rntime'] = modt['uptime']
modt['slptime'] = 0
print('\t modified\tbtime: --n/a-- | uptime: ' + str(modt['uptime']) + ' | rntime: ' + str(modt['rntime']) + ' | slptime: ' + str(modt['slptime']) + ' | offbtime: ' + str(modt['offbtime']) + ' | downtime: ' + str(modt['downtime']))
# Limit check
if modt['downtime'] is None or modt['downtime'] < 0 or \
modt['offbtime'] is None or modt['offbtime'] < 0 or \
modt['uptime'] < 1 or modt['rntime'] < 1 or modt['slptime'] < 0:
logging.error('modified values can\'t be None or under limits')
sys.exit(1)
# Update values
conn.execute('update tuptime set uptime =? where rowid =?', (modt['uptime'], reg['target']))
conn.execute('update tuptime set rntime =? where rowid =?', (modt['rntime'], reg['target']))
conn.execute('update tuptime set slptime =? where rowid =?', (modt['slptime'], reg['target']))
conn.execute('update tuptime set downtime =? where rowid =?', (modt['downtime'], reg['target']))
conn.execute('update tuptime set offbtime =? where rowid =?', (modt['offbtime'], reg['target']))
def fix_startup(arg, reg, conn, modt, orgt, modp, orgp):
"""Modify startup datetime register"""
modt['btime'] = orgt['btime'] + arg.seconds
modt['uptime'] = orgt['uptime'] - arg.seconds
if orgt['rntime'] - arg.seconds > 0:
modt['rntime'] = orgt['rntime'] - arg.seconds
modt['slptime'] = orgt['slptime']
elif orgt['slptime'] - arg.seconds >= 0:
modt['rntime'] = orgt['rntime']
modt['slptime'] = orgt['slptime'] - arg.seconds
else:
modt['rntime'] = modt['uptime']
modt['slptime'] = 0
print('\t modified\tbtime: ' + str(modt['btime']) + ' | uptime: ' + str(modt['uptime']) + ' | rntime: ' + str(modt['rntime']) + ' | slptime: ' + str(modt['slptime']) + ' | offbtime: --n/a-- | downtime: --n/a-- ')
# Limit check
if modt['uptime'] < 1 or modt['rntime'] < 1 or modt['slptime'] < 0:
logging.error('modified values can\'t be under limits')
sys.exit(1)
# Get previous registers to modify except from first row
if reg['prev'] > 0:
# Get values from previous row
conn.execute('select downtime from tuptime where rowid =?', (reg['prev'],))
orgp['downtime'] = conn.fetchone()[0]
modp['downtime'] = orgp['downtime'] + arg.seconds
print('\tTarget row-1 \'' + str(reg['prev']) + '\':')
print('\t original\tdowntime: ' + str(orgp['downtime']))
print('\t modified\tdowntime: ' + str(modp['downtime']))
# Limit check
if modp['downtime'] < 0:
logging.error('downtime can\'t be lower than 0')
sys.exit(1)
conn.execute('update tuptime set downtime =? where rowid =?', (modp['downtime'], reg['prev']))
# Update values
conn.execute('update tuptime set btime =? where rowid =?', (modt['btime'], reg['target']))
conn.execute('update tuptime set uptime =? where rowid =?', (modt['uptime'], reg['target']))
conn.execute('update tuptime set rntime =? where rowid =?', (modt['rntime'], reg['target']))
conn.execute('update tuptime set slptime =? where rowid =?', (modt['slptime'], reg['target']))
def main():
arg = get_arguments()
orgt = {} # Original target value
orgp = {} # Original previous (target-1) value
modt = {} # Modified target value
modp = {} # Modified previous (target-1) value
reg = {'target': arg.register, 'prev': (arg.register - 1)} # Target register to modify and previous one
print('Target file:\t' + arg.db_file)
if not arg.nobackup:
backup_dbf(arg)
db_conn = sqlite3.connect(arg.db_file)
db_conn.row_factory = sqlite3.Row
db_conn.set_trace_callback(logging.debug)
conn = db_conn.cursor()
# Check if DB have the old format
columns = [i[1] for i in conn.execute('PRAGMA table_info(tuptime)')]
if 'rntime' and 'slptime' and 'bootid' not in columns:
logging.error('DB format outdated')
sys.exit(1)
# Print raw rows
if arg.verbose:
print('\nTarget registers before: ')
conn.execute('select rowid as startup, * from tuptime where rowid between ? and ?', (reg['prev'], reg['target']))
db_rows = conn.fetchall()
for row in db_rows:
print("\t", end='')
print(dict(row))
# Validate register to modify
conn.execute('select max(rowid) from tuptime')
max_register = conn.fetchone()[0]
if reg['target'] > max_register or reg['target'] < 1:
logging.error('Invalid register to modify. Out of range')
sys.exit(1)
# Get values from target row
conn.execute('select btime, uptime, rntime, slptime, offbtime, endst, downtime from tuptime where rowid =?', (reg['target'],))
orgt['btime'], orgt['uptime'], orgt['rntime'], orgt['slptime'], orgt['offbtime'], orgt['endst'], orgt['downtime'] = conn.fetchone()
print('\nValues:')
print('\tTarget row \'' + str(reg['target']) + '\':')
# Choose value to modify: endst / startup / shutdown
if arg.change == 'endst':
print('\t original\tendst: ' + str(orgt['endst']))
fix_endst(arg, reg, conn, modt, orgt)
else:
print('\t original\tbtime: ' + str(orgt['btime']) + ' | uptime: ' + str(orgt['uptime']) + ' | rntime: ' + str(orgt['rntime']) + ' | slptime: ' + str(orgt['slptime']) + ' | offbtime: ' + str(orgt['offbtime']) + ' | downtime: ' + str(orgt['downtime']))
if arg.change == 'startup':
fix_startup(arg, reg, conn, modt, orgt, modp, orgp)
if arg.change == 'shutdown':
fix_shutdown(arg, reg, conn, modt, orgt)
db_conn.commit()
# Print raw rows
if arg.verbose:
print('\nTarget registers after: ')
conn.execute('select rowid as startup, * from tuptime where rowid between ? and ?', (reg['prev'], reg['target']))
db_rows = conn.fetchall()
for row in db_rows:
print("\t", end='')
print(dict(row))
db_conn.close()
print('\nDone.')
if __name__ == "__main__":
main()
07070100000035000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001200000000tuptime-5.2.4/src07070100000036000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001900000000tuptime-5.2.4/src/cron.d07070100000037000081A40000000000000000000000016693DA19000000DA000000000000000000000000000000000000002100000000tuptime-5.2.4/src/cron.d/tuptime# /etc/cron.d/tuptime: crontab entry for tuptime update.
# NOTE: Decrease the execution time for increase accuracity.
*/5 * * * * _tuptime if [ -x /usr/bin/tuptime ]; then /usr/bin/tuptime -q > /dev/null; fi
07070100000038000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001900000000tuptime-5.2.4/src/init.d07070100000039000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000002000000000tuptime-5.2.4/src/init.d/debian0707010000003A000081ED0000000000000000000000016693DA190000080F000000000000000000000000000000000000002800000000tuptime-5.2.4/src/init.d/debian/tuptime#!/bin/sh
# tuptime - Report historical and statistical real time of the system, preserving it between restarts.
# Author Ricardo Fraile - 2020
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
### BEGIN INIT INFO
# Provides: tuptime
# Required-Start: $local_fs $time $remote_fs
# Required-Stop: $local_fs $time $remote_fs
# Should-Start:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start tuptime at boot time
# Description: Update tuptime.
### END INIT INFO
PATH=/sbin:/usr/sbin:/bin:/usr/bin
. /lib/init/vars.sh
. /lib/lsb/init-functions
USER='_tuptime'
BIN_NAME='tuptime'
PATH_BIN="/usr/bin/$BIN_NAME"
do_start () {
# Start service
log_action_begin_msg "Starting $BIN_NAME"
log_end_msg 0
su -s /bin/sh $USER -c "$PATH_BIN -q" > /dev/null
}
do_stop () {
# Stop service
log_action_begin_msg "Stopping $BIN_NAME"
log_end_msg 0
su -s /bin/sh $USER -c "$PATH_BIN -qg" > /dev/null
}
do_status () {
# Status service
su -s /bin/sh $USER -c "$PATH_BIN"
}
case "$1" in
start|"")
do_start
;;
restart|force-reload)
log_action_begin_msg "Restarting $BIN_NAME"
log_end_msg 0
do_stop
do_start
;;
stop)
do_stop
;;
status)
do_status
exit $?
;;
*)
echo "Usage: $BIN_NAME [start|stop|restart|status]" >&2
exit 3
;;
esac
0707010000003B000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000002100000000tuptime-5.2.4/src/init.d/openwrt0707010000003C000081A40000000000000000000000016693DA1900000112000000000000000000000000000000000000002900000000tuptime-5.2.4/src/init.d/openwrt/tuptime#!/bin/sh /etc/rc.common
START=99
STOP=60
start() {
echo 'Tuptime start'
ln -s /opt/tuptime/ /var/lib/tuptime 2> /dev/null
/usr/bin/python3 /usr/bin/tuptime -q
}
stop() {
echo 'Tuptime stop'
/usr/bin/python3 /usr/bin/tuptime -qg
}
0707010000003D000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000002000000000tuptime-5.2.4/src/init.d/redhat0707010000003E000081ED0000000000000000000000016693DA19000005E2000000000000000000000000000000000000002800000000tuptime-5.2.4/src/init.d/redhat/tuptime#!/bin/sh
#
# tuptime - Historical and statistical real time of the system.
#
# chkconfig: 12345 25 90
# description: Report historical and statistical real time \
# of the system, preserving it between restarts.
### BEGIN INIT INFO
# Provides: tuptime
# Required-Start: $local_fs $time
# Required-Stop: $local_fs $time
# Short-Description: start and stop tuptime
# Description: Report historical and statistical real time
# of the system, preserving it between restarts.
### END INIT INFO
# Source function library.
. /etc/rc.d/init.d/functions
USER='_tuptime'
BIN_NAME='tuptime'
PATH_BIN="/usr/bin/$BIN_NAME"
LOCKFILE="/var/lock/subsys/$BIN_NAME"
RETVAL=0
do_start () {
# Start service
echo -n $"Starting tuptime: "
daemon --user $USER $PATH_BIN -q
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch ${LOCKFILE}
return $RETVAL
}
do_stop () {
# Stop service
echo -n $"Stopping tuptime: "
daemon --user $USER $PATH_BIN -qg
$PATH_BIN -qg
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f ${LOCKFILE}
return $RETVAL
}
do_status () {
# Status service
$PATH_BIN
}
case "$1" in
start|"")
do_start
;;
restart)
do_stop
do_start
;;
stop)
do_stop
;;
status)
do_status
exit $?
;;
*)
echo "Usage: $BIN_NAME [start|stop|restart|status]" >&2
exit 3
;;
esac
0707010000003F000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001A00000000tuptime-5.2.4/src/launchd07070100000040000081A40000000000000000000000016693DA19000002FE000000000000000000000000000000000000003200000000tuptime-5.2.4/src/launchd/localhost.tuptime.plist<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>localhost.tuptime</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>-c</string>
<string>/usr/bin/tuptime -q</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>60</integer>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>LC_ALL</key>
<string>en_US.UTF-8</string>
<key>LANG</key>
<string>en_US.UTF-8</string>
</dict>
<key>StandardErrorPath</key>
<string>/tmp/tuptime.stderr.log</string>
</dict>
</plist>
07070100000041000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001600000000tuptime-5.2.4/src/man07070100000042000081A40000000000000000000000016693DA1900001448000000000000000000000000000000000000002000000000tuptime-5.2.4/src/man/tuptime.1'\" t
.TH TUPTIME 1 "Jan 2024" "5.2.4" "General Commands Manual"
.SH NAME
tuptime \- Report historical and statistical real time of the system, preserving it between restarts. Total uptime.
.SH SYNOPSIS
tuptime [\-h] [\-A STARTUP] [\-b] [\-c] [\-d DATETIME_FMT] [\-e DECIMALS] [\-E STARTUP] [\-f FILE] [\-g] [\-i] [\-k] [\-l] [\-n] [\-o TYPE] [\-p] [\-q] [\-r] [\-s] [\-S STARTUP] [\-t] [\-\-tat TIMESTAMP] [\-\-tsince TIMESTAMP] [\-\-tuntil TIMESTAMP] [\-U STARTUP] [\-v] [\-V]
.SH DESCRIPTION
.RS
.RE
Tuptime reports historical and statistical real time of
the system, preserving it between restarts. Indeed, it can:
.RS
- Count system startups
.RS
.RE
- Register first boot time (a.k.a. installation time)
.RS
.RE
- Count nicely and accidentally shutdowns
.RS
.RE
- Uptime and downtime percentage since first boot time
.RS
.RE
- Accumulated system uptime (running and sleeping), downtime and total
.RS
.RE
- Register used kernels and boot IDs
.RS
.RE
- Report current uptime
.RS
.RE
- Print formatted table or list with the system history
.RS
.RE
- Narrow reports since, until or at a given startup or timestamp
.RS
.RE
- Output in csv format
.SH OPTIONS
.SS ARGUMENTS
.TS
tab (@);
l lx.
\-h | \-\-help@T{
Show this help message and exit
T}
\-A | \-\-at STARTUP@T{
Limit to this startup number
T}
\-b | \-\-bootid@T{
Show boot identifier
T}
\-c | \-\-csv@T{
Output in csv format
T}
\-d | \-\-date DATETIME_FMT@T{
Datetime/timestamp format output
T}
\-e | \-\-dec DECIMALS@T{
Number of decimals in percentages
T}
\-E | \-\-exclude STARTUP@T{
Startup numbers to exclude
T}
\-f | \-\-file FILE@T{
Database file (file path)
T}
\-g | \-\-graceful@T{
Register a graceful shutdown
T}
\-i | \-\-invert@T{
Startup number in reverse count | swich between longest/shortest on default output
T}
\-k | \-\-kernel@T{
Show kernel version
T}
\-l | \-\-list@T{
Enumerate system life as list
T}
\-n | \-\-noup@T{
Avoid update values into DB
T}
\-o | \-\-order TYPE@T{
Order enumerate by [u|r|s|e|d|k] (u = uptime | r = runtime | s = sleep time | e = end status | d = downtime | k = kernel)
T}
\-p | \-\-power@T{
Show power states run + sleep
T}
\-q | \-\-quiet@T{
Update values into DB without output
T}
\-r | \-\-reverse@T{
Reverse order in listings
T}
\-s | \-\-seconds@T{
Output time in seconds and epoch
T}
\-S | \-\-since STARTUP@T{
Limit from this startup number
T}
\-t | \-\-table@T{
Enumerate system life as table
T}
\-\-tat TIMESTAMP@T{
Report system status at specific timestamp
T}
\-\-tsince TIMESTAMP@T{
Limit from this epoch timestamp
T}
\-\-tuntil TIMESTAMP@T{
Limit until this epoch timestamp
T}
\-U | \-\-until STARTUP@T{
Limit up until this startup number
T}
\-v | \-\-verbose@T{
Verbose output
T}
\-V | \-\-version@T{
Show version
T}
.TE
.SS ENVIRONMENT
.RE
TUPTIME_DBF
.RS
Set an alternative database file path. The argument -f, --filedb takes
precedence over this.
.TE
.SH DEFAULT OUTPUT
.RS
.RE
System startups:
.RS
Total number of system startups from since first timestamp available.
.RE
System shutdowns:
.RS
Total number of shutdowns done correctly or incorrectly.
.RE
System life:
.RS
Time counter since first startup timestamp available.
.RE
System uptime:
.RE
System downtime:
.RS
Percentage of time and time counter.
.RE
Longest uptime:
.RE
Longest downtime:
.RS
Time counter and date with the complete longest uptime/downtime register.
.RE
Average uptime:
.RE
Average downtime:
.RS
Average time counter.
.RE
Current uptime:
.RS
Actual time counter and datetime since registered boot timestamp.
.SH EXAMPLES
.TP
.BI tuptime
Default output.
.TP
.B tuptime -t
Enumerate system life as table.
.TP
.B tuptime -l
Enumerate system life as list.
.TP
.B tuptime -k
Add kernel information to the output.
.TP
.B tuptime --csv
Report in csv format.
.TP
.B tuptime -s
Change default human readable datetime/timestamp style and print times in
seconds and datetimes in epoch.
.TP
.B tuptime -d '%H:%M:%S %m-%d-%Y'
Change the datetime/timestamp format. By default the output use the
configured system locales.
.TP
.B tuptime --tsince -31557600
Report since one year ago.
.SH FILES
.TP
.I /etc/cron.d/tuptime
Scheduled cron file.
.TP
.I /etc/init.d/tuptime
Init file.
.TP
.I /lib/systemd/system/tuptime.service
Systemd service unit file. Register time values into database.
.TP
.I /usr/bin/tuptime
Main and only executable file.
.TP
.I /usr/share/doc/tuptime/
Directory with multiple documentation files.
.TP
.I /lib/systemd/system/tuptime-sync.timer
Systemd .timer unit for use instead of cron. Only executes tuptime-sync.service.
.TP
.I /lib/systemd/system/tuptime-sync.service
Systemd .service unit required by tuptime-sync.timer. Updates time values into database.
.TP
.I /usr/share/man/man1/tuptime.1
Manual page.
.SH SEE ALSO
.TP
.I /usr/share/doc/tuptime/tuptime-manual.txt.gz
Detailed documentation.
.TP
.I https://github.com/rfmoz/tuptime/
Official repository.
.SH "AUTHOR"
.PP
Ricardo Fraile <r@rfmoz.eu>
.SH "COPYRIGHT"
.PP
Copyright (C) 2024 by Ricardo F. All Rights Reserved.
This product is distributed in the hope that it will be useful, but
WITHOUT any warranty; without even the implied warranty of
MERCHANTABILITY or FITNESS for a particular purpose.
07070100000043000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001900000000tuptime-5.2.4/src/openrc07070100000044000081A40000000000000000000000016693DA1900000220000000000000000000000000000000000000002100000000tuptime-5.2.4/src/openrc/tuptime#!/sbin/openrc-run
name='tuptime'
command='/usr/bin/tuptime'
command_user='_tuptime'
command_args='-q'
depend() {
need localmount
after bootmisc
after clock
}
start() {
ebegin "Starting ${name}"
su ${command_user} -s /bin/sh -c "${command} -q"
eend $?
}
stop() {
ebegin "Stopping ${name}"
su ${command_user} -s /bin/sh -c "${command} -qg"
eend $?
}
status() {
ebegin "Status ${name}"
su ${command_user} -s /bin/sh -c "${command} -q"
eend $?
}
07070100000045000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001700000000tuptime-5.2.4/src/rc.d07070100000046000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001F00000000tuptime-5.2.4/src/rc.d/freebsd07070100000047000081ED0000000000000000000000016693DA1900000285000000000000000000000000000000000000002700000000tuptime-5.2.4/src/rc.d/freebsd/tuptime#!/bin/sh
#
# PROVIDE: tuptime
# REQUIRE: DAEMON
# KEYWORD: nojail shutdown
tuptime_enable=${tuptime_enable:-"NO"}
. /etc/rc.subr
name="tuptime"
rcvar=tuptime_enable
tuptime_user="_tuptime"
start_cmd="${name}_start"
stop_cmd="${name}_stop"
export PATH=$PATH:/usr/local/bin/
tuptime_start()
{
echo "Starting $name."
command_args="-q"
su -m ${tuptime_user} -c "$name $command_args" 2> /dev/null || $name $command_args 2> /dev/null
}
tuptime_stop()
{
echo "Stopping $name."
command_args="-qg"
su -m ${tuptime_user} -c "$name $command_args" 2> /dev/null || $name $command_args 2> /dev/null
}
load_rc_config $name
run_rc_command "$1"
07070100000048000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001F00000000tuptime-5.2.4/src/rc.d/openbsd07070100000049000081ED0000000000000000000000016693DA190000016A000000000000000000000000000000000000002700000000tuptime-5.2.4/src/rc.d/openbsd/tuptime#!/bin/ksh
daemon="/usr/local/bin/tuptime"
daemon_user="_tuptime"
. /etc/rc.d/rc.subr
rc_reload=NO
rc_check() {
# Fake daemon, not running in background
# Verify if started at boot
if [[ -e ${_RC_RUNFILE} ]]; then
return 0
else
return 1
fi
}
rc_start() {
rc_exec "${daemon} -q"
}
rc_stop() {
rc_exec "${daemon} -qg"
_rc_rm_runfile
}
rc_cmd $1
0707010000004A000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001A00000000tuptime-5.2.4/src/systemd0707010000004B000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000002500000000tuptime-5.2.4/src/systemd/sysusers.d0707010000004C000081A40000000000000000000000016693DA1900000125000000000000000000000000000000000000003200000000tuptime-5.2.4/src/systemd/sysusers.d/tuptime.confu _tuptime - "Tuptime execution user" /var/lib/tuptime
# Note:
# systemd-sysusers doesn't create home directories. Use in combination with
# StateDirectory=tuptime under [Service] in tuptime.service unit.
# https://lists.freedesktop.org/archives/systemd-devel/2018-February/040348.html
0707010000004D000081A40000000000000000000000016693DA1900000120000000000000000000000000000000000000002F00000000tuptime-5.2.4/src/systemd/tuptime-sync.service[Unit]
Description=Tuptime scheduled sync service
Documentation=man:tuptime(1) file:///usr/share/doc/tuptime/tuptime-manual.txt.gz
Requires=tuptime.service
After=tuptime.service
#ConditionPathExists=!/etc/cron.d/tuptime
[Service]
Type=oneshot
User=_tuptime
ExecStart=/usr/bin/tuptime -q
0707010000004E000081A40000000000000000000000016693DA1900000095000000000000000000000000000000000000002D00000000tuptime-5.2.4/src/systemd/tuptime-sync.timer[Unit]
Description=Tuptime scheduled sync timer
[Timer]
OnBootSec=1min
OnCalendar=*:0/5
Unit=tuptime-sync.service
[Install]
WantedBy=timers.target
0707010000004F000081A40000000000000000000000016693DA190000002A000000000000000000000000000000000000003000000000tuptime-5.2.4/src/systemd/tuptime-tmpfiles.confd /var/lib/tuptime 0755 _tuptime _tuptime
07070100000050000081A40000000000000000000000016693DA1900000156000000000000000000000000000000000000002A00000000tuptime-5.2.4/src/systemd/tuptime.service[Unit]
Description=Tuptime service
Documentation=man:tuptime(1) file:///usr/share/doc/tuptime/tuptime-manual.txt.gz
After=time-sync.target
Wants=time-sync.target
[Service]
Type=oneshot
User=_tuptime
RemainAfterExit=true
ExecStart=/usr/bin/tuptime -q
ExecStop=/usr/bin/tuptime -qg
StateDirectory=tuptime
[Install]
WantedBy=multi-user.target
07070100000051000081A40000000000000000000000016693DA190000BE30000000000000000000000000000000000000001A00000000tuptime-5.2.4/src/tuptime#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""tuptime - Report historical and statistical real time of the system,
preserving it between restarts"""
# Copyright (C) 2011-2024 - Ricardo F.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, os, argparse, locale, platform, signal, logging, sqlite3, time
from datetime import datetime
# On os_bsd(): import subprocess
DB_FILE = '/var/lib/tuptime/tuptime.db'
DATETIME_FMT = '%X %x'
__version__ = '5.2.4'
# Terminate gracefully when SIGPIPE signal is received.
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except Exception:
pass
# Set locale to the user’s default settings (LANG env. var)
try:
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass # Falling back to default locale.setlocale(locale.LC_ALL, 'C')
def get_arguments():
"""Get arguments from command line"""
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
parser.add_argument(
'-A', '--at',
dest='at',
default=None,
action='store',
metavar='STARTUP',
type=int,
help='limit to this startup number'
)
parser.add_argument(
'-b', '--bootid',
dest='bootid',
action='store_true',
default=False,
help='show boot identifier'
)
parser.add_argument(
'-c', '--csv',
dest='csv',
action='store_true',
default=False,
help='csv output'
)
parser.add_argument(
'-d', '--date',
dest='dtm_format',
metavar='DATETIME_FMT',
default=DATETIME_FMT,
action='store',
help='datetime/timestamp format output'
)
parser.add_argument(
'-e', '--dec',
dest='dec',
default=2,
metavar='DECIMALS',
action='store',
type=int,
help='number of decimals in percentages'
)
parser.add_argument(
'-E', '--exclude',
dest='exclude',
default=None,
action='store',
metavar='STARTUP',
help='startup numbers to exclude'
)
parser.add_argument(
'--decp',
dest='decp',
default=None,
action='store',
type=int,
help=argparse.SUPPRESS
)
parser.add_argument(
'-f', '--filedb',
dest='db_file',
default=DB_FILE,
action='store',
help='database file (' + DB_FILE + ')',
metavar='FILE'
)
parser.add_argument(
'-g', '--graceful',
dest='endst',
action='store_const',
default=int(0),
const=int(1),
help='register a graceful shutdown'
)
parser.add_argument(
'-i', '--invert',
dest='invert',
action='store_true',
default=False,
help='startup number in reverse count | swich between longest/shortest on default output'
)
parser.add_argument(
'-k', '--kernel',
dest='kernel',
action='store_true',
default=False,
help='show kernel version'
)
group.add_argument(
'-l', '--list',
dest='list',
default=False,
action='store_true',
help='enumerate system life as list'
)
parser.add_argument(
'-n', '--noup',
dest='update',
default=True,
action='store_false',
help='avoid update values into DB'
)
parser.add_argument(
'-o', '--order',
dest='order',
metavar='TYPE',
default=False,
action='store',
type=str,
choices=['u', 'r', 's', 'e', 'd', 'k'],
help='order enumerate by [u|r|s|e|d|k]'
)
parser.add_argument(
'-p', '--power',
dest='power',
default=False,
action='store_true',
help='show power states run + sleep'
)
parser.add_argument(
'--pctl',
dest='percentile',
default=None,
action='store',
type=int,
help=argparse.SUPPRESS
)
parser.add_argument(
'-q', '--quiet',
dest='quiet',
default=False,
action='store_true',
help='update values into DB without output'
)
parser.add_argument(
'-r', '--reverse',
dest='reverse',
default=False,
action='store_true',
help='reverse order in listings'
)
parser.add_argument(
'-s', '--seconds',
dest='seconds',
default=False,
action='store_true',
help='output time in seconds and epoch'
)
parser.add_argument(
'-S', '--since',
dest='since',
default=None,
action='store',
metavar='STARTUP',
type=int,
help='limit from this startup number'
)
group.add_argument(
'-t', '--table',
dest='table',
default=False,
action='store_true',
help='enumerate system life as table'
)
group.add_argument(
'--tat',
dest='tat',
metavar='TIMESTAMP',
default=None,
action='store',
type=int,
help='system status at epoch timestamp'
)
parser.add_argument(
'--tsince',
dest='ts',
metavar='TIMESTAMP',
default=None,
action='store',
type=int,
help='limit from this epoch timestamp'
)
parser.add_argument(
'--tuntil',
dest='tu',
metavar='TIMESTAMP',
default=None,
action='store',
type=int,
help='limit until this epoch timestamp'
)
parser.add_argument(
'-U', '--until',
dest='until',
default=None,
action='store',
metavar='STARTUP',
type=int,
help='limit up until this startup number'
)
parser.add_argument(
'-v', '--verbose',
dest='verbose',
default=False,
action='store_true',
help='verbose output'
)
parser.add_argument(
'-V', '--version',
action='version',
version='tuptime version ' + (__version__),
help='show version'
)
parser.add_argument(
'-x', '--silent',
dest='silent',
default=False,
action='store_true',
help=argparse.SUPPRESS
)
arg = parser.parse_args()
# Check enable verbose
if arg.verbose:
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.info('Version = %s', (__version__))
if (arg.power and (arg.ts or arg.tu or arg.tat)) or (arg.tat and (arg.ts or arg.tu)):
# - power states report accumulated time across an uptime range, it isn't possible to
# know if the state was running or sleeping between specific points inside it.
# - tat work within startups numbers, not in narrow ranges.
parser.error('Invalid argument combination')
# Wrap 'at' over since and until
if arg.at is not None:
arg.since = arg.until = arg.at
# Expand exclude range
if arg.exclude is not None:
try:
ex_extend = [ss.split('-') for ss in arg.exclude.split(',')]
ex_extend = [range(int(i[0]), int(i[1])+1) if len(i) == 2 else i for i in ex_extend]
arg.exclude = sorted({int(item) for sublist in ex_extend for item in sublist})
except:
logging.warning('Invalid exclude argument value. Not applied')
arg.exclude = None
if arg.decp:
arg.dec = arg.decp
logging.warning('Argument \'--decp\' is deprecated in favour of \'--dec\'')
if arg.percentile:
logging.warning('Argument \'--pctl\' deprecated without functionality')
if arg.silent:
arg.quiet = arg.silent
logging.warning('Argument \'-x\' is deprecated in favour of \'-q\'')
logging.info('Arguments = %s', vars(arg))
return arg
def get_os_values():
"""Get values from each type of operating system"""
sis = {'bootid': None, 'btime': None, 'uptime': None, 'rntime': None, 'slptime': None, 'kernel': None}
def os_bsd(sis):
"""Get values from BSD"""
logging.info('System = BSD')
import subprocess
try:
sis['btime'] = time.clock_gettime(time.CLOCK_REALTIME) - time.clock_gettime(time.CLOCK_MONOTONIC)
except Exception as exp:
logging.info('Old btime assignment. %s', exp)
sysctl_out = subprocess.run(['sysctl', '-n', 'kern.boottime'], stdout=subprocess.PIPE, text=True, check=True).stdout
# Some BSDs report the value assigned to 'sec', others do it directly
if 'sec' in sysctl_out: # FreeBSD, Darwin
sis['btime'] = sysctl_out.split(' sec = ')[1].split(',')[0]
else: # OpenBSD, NetBSD
sis['btime'] = sysctl_out
try:
# Time since some unspecified starting point. Contains sleep time on BSDs
sis['uptime'] = time.clock_gettime(time.CLOCK_MONOTONIC)
if sys.platform.startswith(('darwin')):
# OSX > 10.12 has only UPTIME_RAW. Avoid mix it with non _RAW counters, extract _RAW from MONOTONIC_ and remove it
sis['rntime'] = time.clock_gettime(time.CLOCK_UPTIME_RAW) + (sis['uptime'] - time.clock_gettime(time.CLOCK_MONOTONIC_RAW))
else:
# Time the system has been running. Not contains sleep time on BSDs
sis['rntime'] = time.clock_gettime(time.CLOCK_UPTIME)
except Exception as exp:
logging.info('Old uptime/rntime assignment. %s', exp)
logging.info('Power states disabled, values assigned from uptime')
sis['uptime'] = time.time() - sis['btime']
sis['rntime'] = sis['uptime']
try:
sysctl_out = subprocess.run(['sysctl', '-xn', 'kern.boot_id'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, check=False).stdout
if 'Dump' in sysctl_out:
sis['bootid'] = sysctl_out.split('Dump:')[-1].rstrip()
else:
raise ValueError
except Exception:
logging.info('BSD boot_id not assigned')
return sis
def os_linux(sis):
"""Get values from Linux"""
logging.info('System = Linux')
try:
sis['btime'] = time.clock_gettime(time.CLOCK_REALTIME) - time.clock_gettime(time.CLOCK_BOOTTIME)
except Exception as exp:
logging.info('Old btime assignment. %s', exp)
with open('/proc/stat', encoding='utf-8') as fl2:
for line in fl2:
if line.startswith('btime'): sis['btime'] = line.split()[1]
try: # uptime and rntime must be together to avoid time mismatch between them
# Time since some unspecified starting point. Contains sleep time on linux
sis['uptime'] = time.clock_gettime(time.CLOCK_BOOTTIME)
# Time since some unspecified starting point. Not contains sleep time on linux
sis['rntime'] = time.clock_gettime(time.CLOCK_MONOTONIC)
except Exception as exp:
logging.info('Old uptime/rntime assignment. %s', exp)
logging.info('Power states disabled, values assigned from uptime')
with open('/proc/uptime', encoding='utf-8') as fl1:
sis['uptime'] = fl1.readline().split()[0]
sis['rntime'] = sis['uptime']
try:
with open('/proc/sys/kernel/random/boot_id', encoding='utf-8') as fl3:
sis['bootid'] = fl3.readline().split()[0]
except Exception:
logging.info('Linux boot_id not assigned')
try:
# Way to indicate a synchronized clock by SystemD, see filename notes on systemd/src/time-wait-sync/time-wait-sync.c
# Check if SystemD is used, but file not exists. Could be mandatory, outstripping control_drift()
if not os.path.exists('/run/systemd/timesync/synchronized') and os.path.exists('/run/systemd'):
logging.info('Timesync state file not found = /run/systemd/timesync/synchronized')
#logging.error('Clock no synchronized')
#sys.exit(1)
except Exception as exp:
logging.info('Timesync check fails. %s', exp)
return sis
# Linux
if sys.platform.startswith('linux'):
sis = os_linux(sis)
# BSD and related
elif sys.platform.startswith(('freebsd', 'darwin', 'dragonfly', 'openbsd', 'netbsd', 'sunos')):
sis = os_bsd(sis)
# elif:
# other_os()
else:
logging.error('System = %s not supported', sys.platform)
sys.exit(1)
# Check right allocation of core variables before continue
for key, value in sis.items():
if key in ('btime', 'uptime', 'rntime') and value is None:
logging.error('"%s" value unallocate from system. Execution aborted', key)
sys.exit(1)
if key in ('uptime', 'rntime') and float(value) < 0:
logging.error('"%s" value "%s" below zero. Execution aborted', key, value)
sys.exit(1)
# Set number OS values as integer
for key in ('btime', 'uptime', 'rntime'):
sis[key] = int(round(float(sis[key])))
# Avoid mismatch with elapsed time between getting counters and/or rounded values,
# with less than 1 seconds, values are equal
if (sis['uptime'] - 1) <= sis['rntime'] <= (sis['uptime'] + 1):
sis['rntime'] = sis['uptime']
# Get sleep time from runtime
sis['slptime'] = sis['uptime'] - sis['rntime']
# Set text OS values
sis['bootid'] = str(sis['bootid'])
sis['kernel'] = str(platform.platform())
logging.info('Python = %s', platform.python_version())
try:
logging.info('Current locale = %s', locale.getlocale())
except Exception:
logging.info('Current locale = None')
logging.info('Sys values = %s', sis)
logging.info('Execution uid,gid = %s,%s', os.getuid(), os.getgid())
# Avoid executing when OS clock is too out of phase
if sis['btime'] < 946684800: # 01/01/2000 00:00
logging.error('Epoch boot time value is too old \'%s\'. Check system clock sync', sis['btime'])
logging.error('Tuptime execution aborted')
sys.exit(1)
return sis
def gain_db(sis, arg):
"""Assure DB state and get DB connection"""
# If db_file keeps default value, check for DB environment variable
if arg.db_file == DB_FILE:
if os.environ.get('TUPTIME_DBF'):
arg.db_file = os.environ.get('TUPTIME_DBF')
logging.info('DB environ var = %s', arg.db_file)
# Test path
arg.db_file = os.path.abspath(arg.db_file) # Get absolute or relative path
try:
if os.makedirs(os.path.dirname(arg.db_file), exist_ok=True):
logging.info('Making path = %s', os.path.dirname(arg.db_file))
except Exception as exp_path:
logging.error('Check DB path "%s": %s', os.path.dirname(arg.db_file), exp_path)
sys.exit(1)
# Test and create DB with the initial values
try:
if os.path.isfile(arg.db_file):
logging.info('DB file exists = %s (uid=%s,gid=%s)', arg.db_file, os.stat(arg.db_file).st_uid, os.stat(arg.db_file).st_gid)
else:
logging.info('Making DB file = %s', arg.db_file)
db_conn = sqlite3.connect(arg.db_file)
db_conn.set_trace_callback(logging.debug)
conn = db_conn.cursor()
conn.execute('BEGIN deferred')
conn.execute('create table tuptime'
' (bootid text, btime integer, uptime integer, rntime integer, slptime integer,'
' offbtime integer, endst integer, downtime integer, kernel text)')
conn.execute('insert into tuptime values (?,?,?,?,?,?,?,?,?) ',
(sis['bootid'], sis['btime'], sis['uptime'], sis['rntime'],
sis['slptime'], None, arg.endst, None, sis['kernel']))
conn.execute('PRAGMA user_version = {}'.format(__version__.partition('.')[0]))
db_conn.commit()
db_conn.close()
except Exception as exp_file:
logging.error('Check DB file "%s": %s', arg.db_file, exp_file)
sys.exit(1)
# Get DB connection and begin transaction
try:
logging.info('Getting DB connection')
db_conn = sqlite3.connect(arg.db_file)
db_conn.row_factory = sqlite3.Row
db_conn.set_trace_callback(logging.debug)
conn = db_conn.cursor()
conn.execute('BEGIN deferred')
except Exception as exp_conn:
logging.error('DB connection failed: %s', exp_conn)
sys.exit(1)
# Check if DB has the old format
user_version = conn.execute('PRAGMA user_version').fetchone()[0]
if arg.verbose:
logging.info('DB user_version: %s', user_version)
if user_version < 5:
logging.warning('DB format outdated')
upgrade_db(db_conn, conn, arg)
return db_conn, conn
def upgrade_db(db_conn, conn, arg):
"""Upgrade DB to current format"""
if not os.access(arg.db_file, os.W_OK):
logging.error('"%s" file not writable by execution user', arg.db_file)
sys.exit(1)
logging.warning('Upgrading DB file = %s', arg.db_file)
try:
columns = [i[1] for i in conn.execute('PRAGMA table_info(tuptime)')]
if 'rntime' not in columns or 'slptime' not in columns: # new in tuptime v4
logging.warning('Upgrading DB with power states')
conn.execute('create table if not exists tuptimeNew'
' (btime integer, uptime integer, rntime integer, slptime integer,'
' offbtime integer, endst integer, downtime integer, kernel text)')
conn.execute('update tuptime set uptime = cast(round(uptime) as int)')
conn.execute('update tuptime set offbtime = cast(round(offbtime) as int)')
conn.execute('update tuptime set downtime = cast(round(downtime) as int)')
conn.execute('insert into tuptimeNew'
' (btime, uptime, offbtime, endst, downtime, kernel)'
' SELECT btime, uptime, offbtime, endst, downtime, kernel'
' FROM tuptime')
conn.execute('update tuptimeNew set rntime = uptime')
conn.execute('update tuptimeNew set slptime = 0')
conn.execute('drop table tuptime')
conn.execute('alter table tuptimeNew RENAME TO tuptime')
conn.execute('PRAGMA user_version = 4')
db_conn.commit()
if 'bootid' not in columns: # new in tuptime v5
logging.warning('Upgrading DB with boot ID')
conn.execute('create table if not exists tuptimeNew'
' (bootid text, btime integer, uptime integer, rntime integer, slptime integer,'
' offbtime integer, endst integer, downtime integer, kernel text)')
conn.execute('insert into tuptimeNew'
' (btime, uptime, rntime, slptime, offbtime, endst, downtime, kernel)'
' SELECT btime, uptime, rntime, slptime, offbtime, endst, downtime, kernel'
' FROM tuptime')
conn.execute('update tuptimeNew set bootid = "None"')
conn.execute('update tuptimeNew set kernel = "None" where kernel = ""')
conn.execute('drop table tuptime')
conn.execute('alter table tuptimeNew RENAME TO tuptime')
conn.execute('PRAGMA user_version = 5')
db_conn.commit()
logging.warning('Set DB user_version')
conn.execute('PRAGMA user_version = 5')
db_conn.commit()
except Exception as exp_db:
logging.error('Upgrading DB format failed. "%s"', exp_db)
sys.exit(1)
logging.warning('Upgraded')
def control_drift(lastdb, sis):
"""Check time drift due inconsistencies with system clock"""
offset = sis['btime'] - lastdb['btime']
logging.info('Drift over btime = %s', offset)
# offset > 0 fixed now over current uptime value
# offset < 0 fixed later over next downtime value
if offset:
#logging.info('System timestamp = %s', sis['btime'] + sis['uptime'])
#btime value registered on DB is a startup reference, keep it
sis['btime'] = lastdb['btime']
if offset > 0:
sis['uptime'] = sis['uptime'] + offset
sis['rntime'] = sis['rntime'] + offset
# After drift values, fixed timestamp must be equal to system timestamp
#logging.info('Fixed timestamp = %s', sis['btime'] + sis['uptime'])
logging.info("Drifted sys values = {'btime': %s, 'uptime': %s, 'rntime': %s}", sis['btime'], sis['uptime'], sis['rntime'])
else:
logging.info("Drifted sys values = {'btime': %s}", sis['btime'])
return sis
def time_conv(secs):
"""Convert seconds to human readable style"""
dtm = {'yr': 0, 'd': 0, 'h': 0, 'm': 0, 's': 0}
line = ''
# Get human values from seconds
dtm['m'], dtm['s'] = divmod(secs, 60)
dtm['h'], dtm['m'] = divmod(dtm['m'], 60)
dtm['d'], dtm['h'] = divmod(dtm['h'], 24)
dtm['yr'], dtm['d'] = divmod(dtm['d'], 365)
# Build datetime sentence with this order
for key in ('yr', 'd', 'h', 'm', 's'):
# Avoid print empty values at the beginning, except seconds
if (dtm[key] == 0) and (line == '') and (key != 's'):
continue
else:
line += str(dtm[key]) + key + ' '
# Return without last space char
return str(line[:-1])
def trim_rows(db_rows, sis, last_st, arg):
"""Report rows since or until a given startup number or timestamp
Conventions:
- Keep startup number, boot ID and kernel
- Empty values are False
"""
def tuntil(db_rows, arg):
for row in (*db_rows,): # Parse rows trying to look for the rightmost (older) value
if arg.tu > row['offbtime'] and arg.tu <= (row['offbtime'] + row['downtime']):
row['downtime'] = arg.tu - row['offbtime']
elif arg.tu > row['btime'] and arg.tu <= (row['btime'] + row['uptime']):
row['uptime'] = arg.tu - row['btime']
for key in ('rntime', 'slptime', 'offbtime', 'endst', 'downtime'):
row[key] = False
elif arg.tu <= row['btime']:
db_rows.remove(row)
else:
continue
return db_rows
def tsince(db_rows, arg):
for row in (*db_rows,): # Parse rows trying to look for the leftmost (newer) value
if arg.ts <= row['btime']:
continue
elif arg.ts > row['btime'] and arg.ts < (row['btime'] + row['uptime']):
row['uptime'] = row['btime'] + row['uptime'] - arg.ts
for key in ('btime', 'rntime', 'slptime'):
row[key] = False
elif arg.ts == row['offbtime']:
for key in ('btime', 'uptime', 'rntime', 'slptime'):
row[key] = False
elif arg.ts > row['offbtime'] and arg.ts < (row['offbtime'] + row['downtime']):
row['downtime'] = row['offbtime'] + row['downtime'] - arg.ts
for key in ('btime', 'uptime', 'rntime', 'slptime', 'offbtime', 'endst'):
row[key] = False
else:
db_rows.remove(row)
return db_rows
# Filter based on argument
if arg.until is not None:
if arg.until <= 0: # Negative value start from bottom
arg.until = last_st + arg.until
db_rows = [row for row in db_rows if arg.until >= row['startup']]
if arg.since is not None:
if arg.since <= 0: # Negative value start from bottom
arg.since = last_st + arg.since
db_rows = [row for row in db_rows if arg.since <= row['startup']]
if arg.tu is not None:
if arg.tu < 0: # Negative value decrease actual timestamp
arg.tu = sis['btime'] + sis['uptime'] + arg.tu
db_rows = tuntil(db_rows, arg)
if arg.ts is not None:
if arg.ts < 0: # Negative value decrease actual timestamp
arg.ts = sis['btime'] + sis['uptime'] + arg.ts
db_rows = tsince(db_rows, arg)
if arg.exclude is not None:
db_rows = [row for row in db_rows if row['startup'] not in arg.exclude]
return db_rows, arg
def reorder(db_rows, arg, last_st):
"""Order and/or revert and/or invert output"""
if db_rows:
if arg.order:
match_value = {'u': 'uptime', 'r': 'rntime', 's': 'slptime', 'e': 'endst', 'd': 'downtime', 'k': 'kernel'}
db_rows = sorted(db_rows, key=lambda x: (x[match_value[arg.order]]))
if arg.reverse:
db_rows = list(reversed(db_rows))
if arg.invert:
for ind, _ in enumerate(db_rows):
db_rows[ind]['startup'] = db_rows[ind]['startup'] - last_st
return db_rows
def format_output(db_rows, arg):
"""Set the right output format"""
for row in db_rows:
for key in ('bootid', 'kernel'):
if row[key] is False:
row[key] = ''
if row['uptime'] is False:
row['uptime'] = row['rntime'] = row['slptime'] = ''
else:
if not arg.seconds:
for key in ('uptime', 'rntime', 'slptime'):
row[key] = time_conv(row[key])
if row['endst'] is False:
row['endst'] = ''
else:
if row['offbtime'] is False or row['downtime'] is False:
row['endst'] = ''
else:
if row['endst'] == 1:
row['endst'] = 'OK'
elif row['endst'] == 0:
row['endst'] = 'BAD'
for key in ('btime', 'offbtime', 'downtime'):
if row[key] is False:
row[key] = ''
else:
if not arg.seconds:
if key == 'downtime':
row[key] = time_conv(row[key])
else:
row[key] = datetime.fromtimestamp(row[key]).strftime(arg.dtm_format)
return db_rows
def print_list(db_rows, arg):
"""Print values as list"""
if arg.csv: # Set content/spaces between values
sp0, sp5 = '"', ''
sp1 = sp2 = '","'
else:
sp0, sp1, sp2, sp5 = '', ' ', ': ', ' '
for row_dict in format_output(db_rows, arg):
print(sp0 + 'Startup' + sp2 + sp5 + str(row_dict['startup']), end='')
if row_dict['btime']:
print(sp1 + 'at' + sp1 + str(row_dict['btime']), end='')
print(sp0)
if arg.bootid and row_dict['bootid']:
print(sp0 + 'Boot ID' + sp2 + sp5 + str(row_dict['bootid']) + sp0)
if row_dict['uptime']:
print(sp0 + 'Uptime' + sp2 + (sp5 * 2) + str(row_dict['uptime']) + sp0)
if arg.power:
print(sp0 + 'Running' + sp2 + sp5 + str(row_dict['rntime']) + sp0)
print(sp0 + 'Sleeping' + sp2 + str(row_dict['slptime']) + sp0)
if row_dict['offbtime']:
print(sp0 + 'Shutdown' + sp2 + str(row_dict['endst']) + sp1 + 'at' + sp1 + str(row_dict['offbtime']) + sp0)
if row_dict['downtime']:
print(sp0 + 'Downtime' + sp2 + str(row_dict['downtime']) + sp0)
if arg.kernel:
print(sp0 + 'Kernel' + sp2 + (sp5 * 2) + str(row_dict['kernel']) + sp0)
if not arg.csv: print('')
def print_table(db_rows, arg):
"""Print values as a table"""
tops = {'startup': 'No.', 'bootid': 'Boot ID', 'btime': 'Startup T.', 'uptime': 'Uptime', 'rntime': 'Running', 'slptime': 'Sleeping',
'offbtime': 'Shutdown T.', 'endst': 'End', 'downtime': 'Downtime', 'kernel': 'Kernel'}
side_spaces = 3
# Remove unused optional values
if not arg.bootid:
tops.pop('bootid')
if not arg.power:
tops.pop('rntime')
tops.pop('slptime')
if not arg.kernel:
tops.pop('kernel')
# Assign remaining values for print the header
tbl = [tuple(tops.values())]
# Add empty brake up line if csv is not used
if not arg.csv:
tbl.append(tuple(' ') * len(tbl[0]))
# Assign table values for print
for row_dict in format_output(db_rows, arg):
rowd = tuple(row_dict[i] for i in tops)
tbl.append(rowd)
# Print table values
if arg.csv:
for row in tbl:
for key, value in enumerate(row):
sys.stdout.write('"' + str(value) + '"')
if (key + 1) != len(row):
sys.stdout.write(',')
print('')
else:
# Get index position of elements left aligned
align_left = [tbl[0].index(i) for i in ('End', 'Kernel') if i in tbl[0]]
# Get the maximum width of the given column index
colpad = [max([len(str(row[i])) for row in tbl]) for i in range(len(tbl[0]))]
# Print cols by row
for row in tbl:
# Convert the entire row to strings
row = [str(cell) for cell in row]
# First in raw and next ones with side spaces
sys.stdout.write(row[0].rjust(colpad[0]))
for i in range(1, len(row)):
if i in align_left:
col = (side_spaces * ' ') + row[i].ljust(colpad[i])
else:
col = row[i].rjust(colpad[i] + side_spaces)
sys.stdout.write(col)
print('')
def print_tat(db_rows, sis, last_st, arg):
"""Report system status at specific timestamp"""
# Negative value decrease actual timestamp
if arg.tat < 0:
arg.tat = sis['btime'] + sis['uptime'] + arg.tat
report = {'at': arg.tat, 'status': None}
for row in db_rows:
for key in ('startup', 'bootid', 'kernel'):
report[key] = row[key]
# Report UP if tat fall into btime + uptime range
if (arg.tat >= row['btime']) and (arg.tat < (row['btime'] + row['uptime'])):
report['status'] = 'UP'
report['time'] = arg.tat - row['btime']
report['time_fwd'] = row['uptime'] - report['time']
report['time_total'] = row['uptime']
break
# Report DOWN if tat fall into offbtime + downtime range
elif (arg.tat >= row['offbtime']) and (arg.tat < (row['offbtime'] + row['downtime'])):
report['time'] = arg.tat - row['offbtime']
report['time_fwd'] = row['downtime'] - report['time']
if row['endst'] == 1:
report['status'] = 'DOWN-OK'
elif row['endst'] == 0:
report['status'] = 'DOWN-BAD'
report['time_total'] = row['downtime']
break
# If status keep their default value, no match, clean all other variables. Also, cover unexpected division by zero
if report['status'] is None or report['time_total'] == 0:
report['startup'] = report['time'] = report['time_fwd'] = 0
report['bootid'] = report['kernel'] = 'None'
perctg_1 = perctg_2 = 0.0
else:
perctg_1 = round(report['time'] * 100 / report['time_total'], arg.dec)
perctg_2 = round(report['time_fwd'] * 100 / report['time_total'], arg.dec)
if arg.invert:
report['startup'] = report['startup'] - last_st
if not arg.seconds:
report['at'] = datetime.fromtimestamp(report['at']).strftime(arg.dtm_format)
report['time'] = time_conv(report['time'])
report['time_fwd'] = time_conv(report['time_fwd'])
if arg.csv: # Set content/spaces between values
sp0, sp5 = '"', ''
sp2 = sp3 = '","'
else:
sp0, sp2 = '', ':\t\t'
sp5, sp3 = ' ', ' '
print(sp0 + 'System status' + sp2 + str(report['status']) + sp3 + 'at' + sp3 + str(report['at']) + sp3 + 'on' + sp3 + str(report['startup']) + sp0)
if arg.bootid:
print(sp0 + (sp5 * 3) + '...boot ID' + sp2 + str(report['bootid']) + sp0)
if arg.kernel:
print(sp0 + (sp5 * 4) + '...kernel' + sp2 + str(report['kernel']) + sp0)
print(sp0 + (sp5 * 3) + 'elapsed in' + sp2 + str(perctg_1) + '%' + sp3 + '=' + sp3 + str(report['time']) + sp0)
print(sp0 + (sp5 * 1) + 'remaining in' + sp2 + str(perctg_2) + '%' + sp3 + '=' + sp3 + str(report['time_fwd']) + sp0)
def print_default(db_rows, sis, arg):
"""Print values with default output"""
def parse_rows(db_rows, updown, cnt, lmt):
"""Loop along all DB rows"""
for row in db_rows:
# Sum counters
if row['btime'] is not False:
updown['startups'] += 1
if row['offbtime'] is not False:
if row['endst'] == 0:
updown['bad'] += 1
elif row['endst'] == 1:
updown['ok'] += 1
updown['shutdowns'] += 1
# Get lists with all values for later sum and count
for key in cnt:
if row[key] is not False:
cnt[key].append(row[key])
# Get limits for uptime and downtime values
if row['uptime']:
if lmt['max-up']['bootid'] is None or lmt['max-up']['uptime'] <= row['uptime']:
lmt['max-up'] = row.copy()
if lmt['min-up']['bootid'] is None or lmt['min-up']['uptime'] >= row['uptime']:
lmt['min-up'] = row.copy()
if row['downtime']:
if lmt['max-down']['bootid'] is None or lmt['max-down']['downtime'] <= row['downtime']:
lmt['max-down'] = row.copy()
if lmt['min-down']['bootid'] is None or lmt['min-down']['downtime'] >= row['downtime']:
lmt['min-down'] = row.copy()
return updown, cnt, lmt
# Set default values
updown = {'startups': 0, 'shutdowns': 0, 'ok': 0, 'bad': 0}
tstamp = {'min': arg.ts, 'max': arg.tu} # Args as init values
cnt = {'bootid': [], 'uptime': [], 'rntime': [], 'slptime': [], 'downtime': [], 'kernel': []}
range_trim = False
def1 = {'uptime': 0, 'rntime': 0, 'slptime': 0, 'downtime': 0}
def2 = {**def1, 'bootid': None, 'btime': False, 'offbtime': False, 'kernel': None, 'startup': None}
cal = {'tot': def1.copy(), 'ave': def1.copy()}
lmt = {'max-up': def2.copy(), 'max-down': def2.copy(), 'min-up': def2.copy(), 'min-down': def2.copy()}
rate = {k: float(v) for k, v in def1.items()} # Float values of def1
# Get values from all DB rows
updown, cnt, lmt = parse_rows(db_rows, updown, cnt, lmt)
# Max timestamp - until datetime
# Get rightmost (older) value from last row if arg.tu is not used
if db_rows and tstamp['max'] is None:
if db_rows[-1]['offbtime'] is not False:
tstamp['max'] = db_rows[-1]['offbtime'] + db_rows[-1]['downtime']
elif db_rows[-1]['btime'] is not False:
tstamp['max'] = db_rows[-1]['btime'] + db_rows[-1]['uptime']
# Min timestamp - since datetime
# Get leftmost (newer) value, always btime, from first row value if arg.ts is not used
if db_rows and tstamp['min'] is None:
tstamp['min'] = db_rows[0]['btime']
# Check if range is trimmed
if db_rows and arg.exclude:
if db_rows[-1]['startup'] - db_rows[0]['startup'] != len(db_rows) - 1:
range_trim = True
# Get totals and system life
for key in cal['tot']:
cal['tot'][key] += sum(cnt[key])
sys_life = cal['tot']['uptime'] + cal['tot']['downtime']
# Get rates and average uptime / downtime
if sys_life > 0:
for key in rate:
rate[key] = round((cal['tot'][key] * 100) / sys_life, arg.dec)
if cnt['uptime']:
for key in ('uptime', 'rntime', 'slptime'):
cal['ave'][key] = int(round(cal['tot'][key] / len(cnt['uptime'])))
if cnt['downtime']:
cal['ave']['downtime'] = int(round(cal['tot']['downtime'] / len(cnt['downtime'])))
# Output style: Apply human printable values or keep seconds
if not arg.seconds:
sys_life = time_conv(sys_life)
for key, val in tstamp.items():
if val is not None:
tstamp[key] = datetime.fromtimestamp(val).strftime(arg.dtm_format)
for key in ('uptime', 'rntime', 'slptime', 'downtime'):
if key != 'downtime' and sis[key] is not None:
sis[key] = time_conv(sis[key])
for dval in (cal, lmt):
for val in dval.values():
if val[key] is not False:
val[key] = time_conv(val[key])
for key in ('btime', 'offbtime'):
if key != 'offbtime' and sis[key] is not None:
sis[key] = datetime.fromtimestamp(sis[key]).strftime(arg.dtm_format)
for val in lmt.values():
if val[key] is not False:
val[key] = datetime.fromtimestamp(val[key]).strftime(arg.dtm_format)
# Prepare values for print
if arg.csv: # Set content/spaces between values
sp0, sp5 = '"', ''
sp1 = sp4 = sp3 = '","'
else:
sp0, sp1, sp3 = '', ': \t', ' '
sp4 = sp5 = ' '
uptime = {'average': str(cal['ave']['uptime']), 'long': str(lmt['max-up']['uptime']), 'short': str(lmt['min-up']['uptime']),
'sys_time': str(cal['tot']['uptime']), 'sys_rate': str(rate['uptime']) + '%', 'current': str(sis['uptime'])}
downtime = {'average': str(cal['ave']['downtime']), 'long': str(lmt['max-down']['downtime']), 'short': str(lmt['min-down']['downtime']),
'sys_time': str(cal['tot']['downtime']), 'sys_rate': str(rate['downtime']) + '%'}
if arg.power: # Add power values if needed
uptime['average'] += sp4 + '(rn: ' + str(cal['ave']['rntime']) + ' + slp: ' + str(cal['ave']['slptime']) + ')'
uptime['long'] += sp4 + '(rn: ' + str(lmt['max-up']['rntime']) + ' + slp: ' + str(lmt['max-up']['slptime']) + ')'
uptime['short'] += sp4 + '(rn: ' + str(lmt['min-up']['rntime']) + ' + slp: ' + str(lmt['min-up']['slptime']) + ')'
uptime['sys_time'] += sp4 + '(rn: ' + str(cal['tot']['rntime']) + ' + slp: ' + str(cal['tot']['slptime']) + ')'
uptime['sys_rate'] += sp4 + '(rn: ' + str(rate['rntime']) + '% + slp: ' + str(rate['slptime']) + '%)'
uptime['current'] += sp4 + '(rn: ' + str(sis['rntime']) + ' + slp: ' + str(sis['slptime']) + ')'
if arg.invert: # Set longest or shortest values for uptime/downtime
bfp = {'name': 'Shortest', 'key': 'short', 'up': lmt['min-up'], 'down': lmt['min-down']}
else:
bfp = {'name': 'Longest', 'key': 'long', 'up': lmt['max-up'], 'down': lmt['max-down']}
# System block print
print(sp0 + 'System startups' + sp1 + str(updown['startups']) + sp3 + 'since' + sp3 + str(tstamp['min']), end='')
if arg.tu or arg.until:
print(sp3 + 'until' + sp3 + str(tstamp['max']), end='')
if range_trim:
print(sp3 + 'trimmed', end='')
print(sp0)
print(sp0 + 'System shutdowns' + sp1 + str(updown['ok']) + sp4 + 'ok' + sp3 + '+' + sp3 + str(updown['bad']) + sp4 + 'bad' + sp0)
print(sp0 + 'System life' + sp1 + (sp5 * 8) + str(sys_life) + sp0)
if arg.bootid:
print(sp0 + 'System boot IDs' + sp1 + str(len(set(cnt['bootid']))) + sp0)
if arg.kernel:
print(sp0 + 'System kernels' + sp1 + str(len(set(cnt['kernel']))) + sp0)
if not arg.csv: print('')
# Uptime block print
if bfp['up']['btime'] is not False:
print(sp0 + bfp['name'] + ' uptime' + sp1 + uptime[bfp['key']] + sp3 + 'from' + sp3 + str(bfp['up']['btime']) + sp0)
else:
print(sp0 + bfp['name'] + ' uptime' + sp1 + uptime[bfp['key']] + sp0)
if arg.bootid:
print(sp0 + (sp5 * 4) + '...boot ID' + sp1 + str(bfp['up']['bootid']) + sp0)
if arg.kernel:
print(sp0 + (sp5 * 5) + '...kernel' + sp1 + str(bfp['up']['kernel']) + sp0)
print(sp0 + 'Average uptime' + sp1 + uptime['average'] + sp0)
print(sp0 + 'System uptime' + sp1 + (sp5 * 8) + uptime['sys_rate'] + sp3 + '=' + sp3 + uptime['sys_time'] + sp0)
if not arg.csv: print('')
# Downtime block print
if bfp['down']['offbtime'] is not False:
print(sp0 + bfp['name'] + ' downtime' + sp1 + downtime[bfp['key']] + sp3 + 'from' + sp3 + str(bfp['down']['offbtime']) + sp0)
else:
print(sp0 + bfp['name'] + ' downtime' + sp1 + downtime[bfp['key']] + sp0)
if arg.bootid:
print(sp0 + (sp5 * 6) + '...boot ID' + sp1 + str(bfp['down']['bootid']) + sp0)
if arg.kernel:
print(sp0 + (sp5 * 7) + '...kernel' + sp1 + str(bfp['down']['kernel']) + sp0)
print(sp0 + 'Average downtime' + sp1 + downtime['average'] + sp0)
print(sp0 + 'System downtime' + sp1 + downtime['sys_rate'] + sp3 + '=' + sp3 + downtime['sys_time'] + sp0)
# Current block print
if arg.update:
if not arg.csv: print('')
print(sp0 + 'Current uptime' + sp1 + uptime['current'] + sp3 + 'since' + sp3 + str(sis['btime']) + sp0)
if arg.bootid:
print(sp0 + (sp5 * 4) + '...boot ID' + sp1 + str(sis['bootid']) + sp0)
if arg.kernel:
print(sp0 + (sp5 * 5) + '...kernel' + sp1 + str(sis['kernel']) + sp0)
def output_hub(db_rows, sis, arg):
"""Manage values for print"""
last_st = db_rows[-1]['startup']
if len(db_rows) != last_st:
logging.info('Real startups are not equal to enumerate startups. Deleted rows in DB')
if arg.endst:
if arg.endst != db_rows[-1]['endst']:
# Graceful register must be performed at shutdown by the init manager, with _tuptime user
# or a privileged one. No one needs to do in other moment. A normal user can't write into DB
print('Graceful shutdown in DB = mismatch')
sys.exit(1)
print('Graceful shutdown in DB = ok')
return
if arg.update:
# If the user can only read DB, the select query over DB return outdated numbers in last row
# because the DB was not updated previously. The following snippet update them in memory
# If the user wrote into DB, the values are the same
for key in ('uptime', 'rntime', 'slptime', 'kernel'):
db_rows[-1][key] = sis[key]
db_rows[-1]['endst'] = arg.endst
logging.info('Refresh last row values = %s', db_rows[-1])
# Convert last line None sqlite registers to False
for key, value in db_rows[-1].items():
if value is None:
db_rows[-1][key] = False
# Get narrow range of rows if it applies
if not (None is arg.exclude is arg.until is arg.since is arg.tu is arg.ts):
db_rows, arg = trim_rows(db_rows, sis, last_st, arg)
# Print values with the chosen output
if arg.list:
print_list(reorder(db_rows, arg, last_st), arg)
elif arg.table:
print_table(reorder(db_rows, arg, last_st), arg)
elif arg.tat is not None:
print_tat(db_rows, sis, last_st, arg)
else:
print_default(db_rows, sis, arg)
def check_new_boot(lastdb, sis):
"""Test if system has new boot"""
# How tuptime does it:
#
# If boot id exists (only on Linux and FreeBSD), checking if its value has changed
#
# If not exists, checking if the value resultant from previous btime plus previous uptime (both
# saved into DB) is lower than current btime with at least 1 second of diference.
#
# Checking boot id is the most secure way to detect a new boot. Working with time values is not 100% relialible.
# In some particular cases the btime value from /proc/stat or from the system clock functions may change.
# When tuptime doesn't register a new boot, only an update of the records, it tries to fix the drift.
#
# To avoid lost an uptime record, please be sure that the system has time sync enabled, the init/systemd
# script and the cron task works as expected.
if lastdb['bootid'] != 'None' and sis['bootid'] != 'None':
if lastdb['bootid'] != sis['bootid']:
logging.info('System restarted = True from bootid')
return True
else:
logging.info('System restarted = False from bootid')
return False
elif lastdb['buptime'] < sis['btime']:
logging.info('System restarted = True from btime')
return True
else:
logging.info('System restarted = False')
return False
def main():
"""Main entry point, core logic"""
arg = get_arguments()
sis = get_os_values()
db_conn, conn = gain_db(sis, arg)
conn.execute('select rowid, bootid, btime, uptime, endst from tuptime where rowid = (select max(rowid) from tuptime)')
lastdb = dict(zip(['rowid', 'bootid', 'btime', 'uptime', 'endst'], conn.fetchone()))
lastdb['buptime'] = lastdb['btime'] + lastdb['uptime']
logging.info('Last DB values = %s', lastdb)
# Check if system was restarted
if arg.update and check_new_boot(lastdb, sis):
if lastdb['buptime'] > sis['btime']: # Assure btime. Never lower than shutdown
sis['btime'] = lastdb['buptime']
lastdb['downtime'] = sis['btime'] - lastdb['buptime']
try:
# Save downtimes for previous boot
conn.execute('update tuptime set offbtime =?, downtime =? where rowid =? ',
(lastdb['buptime'], lastdb['downtime'], lastdb['rowid']))
# Create a new boot register
conn.execute('insert into tuptime values (?,?,?,?,?,?,?,?,?)',
(sis['bootid'], sis['btime'], sis['uptime'], sis['rntime'],
sis['slptime'], None, arg.endst, None, sis['kernel']))
db_conn.commit()
logging.info('DB info = insert ok')
except Exception as exp:
# If you see this error, maybe the systemd script isn't executed at startup
# or the DB file (DB_FILE) has wrong permissions
logging.error('Detected a new system startup but the values have not been saved into DB')
logging.error('Tuptime execution user failed to write into DB file: %s', arg.db_file)
logging.error('%s', exp)
sys.exit(1)
elif arg.update:
# Adjust time drift. Check only when system wasn't restarted
sis = control_drift(lastdb, sis)
# If a graceful shutdown was just registered before, let 5 seconds to next update to avoid being overlapped
# with regular schedule execution (it can happen at shutdown)
if lastdb['endst'] and (lastdb['uptime'] + 5 > sis['uptime']) and not arg.endst:
logging.info('DB info = graceful pass')
else:
try:
# Update current boot records
conn.execute('update tuptime set uptime =?, rntime =?, slptime =?, endst =?, kernel =? where rowid =?',
(sis['uptime'], sis['rntime'], sis['slptime'], arg.endst, sis['kernel'], lastdb['rowid']))
db_conn.commit()
logging.info('DB info = update ok')
except sqlite3.OperationalError as exc:
if arg.endst:
logging.warning('DB warning = skip register a graceful shutdown, %s', exc)
else:
logging.info('DB info = skip update values, %s', exc)
else:
sis = control_drift(lastdb, sis)
logging.info('DB info = skip by arg.update')
if arg.quiet:
db_conn.close()
logging.info('Quiet mode')
else:
# Get all rows to determine print values. Convert from sqlite row object to dict to allow item allocation
conn.execute('select rowid as startup, * from tuptime')
db_rows = [dict(row) for row in conn.fetchall()]
db_conn.close()
output_hub(db_rows, sis, arg)
if __name__ == "__main__":
main()
07070100000052000081A40000000000000000000000016693DA1900001A16000000000000000000000000000000000000002100000000tuptime-5.2.4/tuptime-install.sh#!/bin/bash
set -e
#
# Tuptime installation linux script
#
# Usage:
# bash tuptime-install.sh Default master install
# bash tuptime-install.sh -d Install using dev branch
#
VERSION=1.9.2
# Execution user
EXUSR='_tuptime'
# Destination dir for executable file
D_BIN='/usr/bin'
# Swich dev branch
DEV=0
# Check bash execution
if [ -z "$BASH" ]; then
echo "--- ERROR - execute only with BASH ---"
exit 1
fi
# Check root execution
if [ "$(id -u)" != "0" ]; then
echo "Please run this script as root"
exit 1
fi
# Test arguments
while getopts ":d" opt; do
case $opt in
d)
DEV=1
;;
\?)
echo "Invalid option: -$OPTARG"
exit 1
;;
esac
done
# Test if it is a linux system
if [ $(uname -s) != "Linux" ]; then
echo "Sorry, only for Linux systems"
exit 1
fi
# Test required commands
check_command() {
if ! command -v "$1" &> /dev/null; then
echo "ERROR: "$1" command not found"
echo "Please install it"
exit 1
fi
}
check_command "curl"
check_command "tar"
check_command "python3"
# Test python version
PYTHON_VERSION=$(python3 --version | awk '{print $2}')
if [[ "$(cut -d'.' -f1 <<<"$PYTHON_VERSION")" -lt 3 ]]; then
echo "ERROR: Python 3 or later is required"
echo "Please upgrade your Python installation"
exit 1
else
# Test if all modules needed are available
REQUIRED_PYTHON_MODULES=("sys" "os" "argparse" "locale" "platform" "signal" "logging" "sqlite3" "datetime")
for module in "${REQUIRED_PYTHON_MODULES[@]}"; do
if ! python3 -c "import $module" &> /dev/null; then
echo "ERROR: Required Python module '$module' is not available."
exit 1
fi
done
fi
# Set SystemD path
if [ -d /usr/lib/systemd/system/ ]; then
SYSDPATH='/usr/lib/systemd/system/'
else
SYSDPATH='/lib/systemd/system/'
fi
# Set Selinux swich
if getenforce 2> /dev/null | grep -q 'Enforcing'; then
echo "Selinux enabled in Enforcing"
SELX=1
else
SELX=0
fi
# Temporary dir to download sources
F_TMP1=$(mktemp -d)
echo ""
echo "++ Tuptime installation script v.$VERSION ++"
echo ""
echo "+ Getting source tar file"
if [ ${DEV} -eq 1 ]; then
echo " ...using dev branch"
tar xz --strip 1 -C "${F_TMP1}" -f <(curl -sL https://github.com/rfmoz/tuptime/archive/dev.tar.gz)
else
tar xz --strip 1 -C "${F_TMP1}" -f <(curl -sL https://github.com/rfmoz/tuptime/archive/master.tar.gz)
fi
echo ' [OK]'
echo "+ Copying files"
install -m 755 "${F_TMP1}"/src/tuptime "${D_BIN}"/tuptime
((SELX)) && restorecon -vF "${D_BIN}"/tuptime
echo ' [OK]'
echo "+ Creating Tuptime execution user '_tuptime'"
if systemd-sysusers --version > /dev/null 2>&1; then
echo " ...using systemd-sysusers"
install -m 644 "${F_TMP1}"/src/systemd/sysusers.d/tuptime.conf /usr/lib/sysusers.d/
((SELX)) && restorecon -vF /usr/lib/sysusers.d/tuptime.conf
systemd-sysusers /usr/lib/sysusers.d/tuptime.conf
echo ' [OK]'
elif useradd -h > /dev/null 2>&1; then
echo " ...using useradd"
useradd --system --no-create-home --home-dir '/var/lib/tuptime' \
--shell '/bin/false' --comment 'Tuptime execution user' "${EXUSR}" && echo ' [OK]'
elif adduser -h > /dev/null 2>&1; then
echo " ...using adduser"
adduser -S -H -h '/var/lib/tuptime' -s '/bin/false' "${EXUSR}" && echo ' [OK]'
else
echo "#######################################"
echo " WARNING - _tuptime user not available"
echo "#######################################"
echo ' [BAD]'
fi
echo "+ Creating Tuptime db"
tuptime -q
echo ' [OK]'
echo "+ Setting Tuptime db ownership"
chown -R "${EXUSR}":"${EXUSR}" /var/lib/tuptime || chown -R "${EXUSR}" /var/lib/tuptime
chmod 755 /var/lib/tuptime
echo ' [OK]'
echo "+ Executing Tuptime with '_tuptime' user for testing"
su -s /bin/sh "${EXUSR}" -c "tuptime -q"
echo ' [OK]'
# Install init
PID1=$(grep 'Name' /proc/1/status | cut -f2)
if [ "${PID1}" = 'systemd' ]; then
echo "+ Copying Systemd file"
install -m 644 "${F_TMP1}"/src/systemd/tuptime.service "${SYSDPATH}"
((SELX)) && restorecon -vF "${SYSDPATH}"tuptime.service
systemctl daemon-reload
systemctl enable tuptime.service
systemctl start tuptime.service
echo ' [OK]'
elif [ "${PID1}" = 'init' ] && [ -f /etc/rc.d/init.d/functions ]; then
echo "+ Copying SysV init RedHat file"
install -m 755 "${F_TMP1}"/src/init.d/redhat/tuptime /etc/init.d/tuptime
((SELX)) && restorecon -vF /etc/init.d/tuptime
chkconfig --add tuptime
chkconfig tuptime on
echo ' [OK]'
elif [ "${PID1}" = 'init' ] && [ -f /lib/lsb/init-functions ]; then
echo "+ Copying SysV init Debian file"
install -m 755 "${F_TMP1}"/src/init.d/debian/tuptime /etc/init.d/tuptime
((SELX)) && restorecon -vF /etc/init.d/tuptime
update-rc.d tuptime defaults
echo ' [OK]'
elif [ "${PID1}" = 'init' ] && [ -f /etc/rc.conf ]; then
echo "+ Copying OpenRC file for init"
install -m 755 "${F_TMP1}"/src/openrc/tuptime /etc/init.d/
((SELX)) && restorecon -vF /etc/init.d/tuptime
rc-update add tuptime default
rc-service tuptime start
echo ' [OK]'
elif [ "${PID1}" = 'openrc-init' ]; then
echo "+ Copying OpenRC file for openrc-init"
install -m 755 "${F_TMP1}"/src/openrc/tuptime /etc/init.d/
((SELX)) && restorecon -vF /etc/init.d/tuptime
rc-update add tuptime default
rc-service tuptime start
echo ' [OK]'
elif [ "${PID1}" = 'runit' ] && [ -f /etc/rc.local ] && [ -f /etc/rc.shutdown ]; then
echo "+ Runit startup and shutdown execution"
echo 'tuptime -q' >> /etc/rc.local
echo 'tuptime -qg' >> /etc/rc.shutdown
else
echo "#########################################"
echo " WARNING - Any init file for your system"
echo "#########################################"
echo ' [BAD]'
fi
# Install cron
if [ -d "${SYSDPATH}" ]; then
echo "+ Copying tuptime-sync.timer and .service"
install -m 644 "${F_TMP1}"/src/systemd/tuptime-sync.* "${SYSDPATH}"
((SELX)) && restorecon -vF "${SYSDPATH}"tuptime-sync.*
systemctl enable tuptime-sync.timer
systemctl start tuptime-sync.timer
echo ' [OK]'
elif [ -d /etc/cron.d/ ]; then
echo "+ Copying Cron file"
install -m 644 "${F_TMP1}"/src/cron.d/tuptime /etc/cron.d/tuptime
((SELX)) && restorecon -vF /etc/cron.d/tuptime
echo ' [OK]'
elif [ -d /etc/cron.hourly/ ]; then
echo "+ Cron hourly execution"
printf '#!/bin/sh \n tuptime -q' > /etc/cron.hourly/tuptime
chmod 744 /etc/cron.hourly/tuptime
echo ' [OK]'
elif [ -d /etc/periodic/15min/ ]; then
echo "+ Periodic execution"
printf '#!/bin/sh \n tuptime -q' > /etc/periodic/15min/tuptime
chmod 744 /etc/periodic/15min/tuptime
echo ' [OK]'
else
echo "#########################################"
echo " WARNING - Any cron file for your system"
echo "#########################################"
echo ' [BAD]'
fi
echo "+ Finished, all steps done."
echo ""
tuptime
07070100000053000081A40000000000000000000000016693DA190000674A000000000000000000000000000000000000002100000000tuptime-5.2.4/tuptime-manual.txt ----------------------
Tuptime Manual
----------------------
version 5.2.4
Ricardo F.
01/May/2024
============
| Abstract |
============
Tuptime reports historical and statistical real time of the system, preserving it between restarts.
Indeed, it can:
- Count system startups
- Register first boot time (since installation time)
- Count nicely and accidentally shutdowns
- Uptime and downtime percentage since first boot time
- Accumulated system uptime (running and sleeping), downtime and total
- Register used kernels and boot IDs
- Report current uptime
- Print formatted table or list with the system history
- Narrow reports since, until or at a given startup or timestamp
- Output in csv format
=================
| Prerequisites |
=================
Operating systems:
- Linux
- BSD
Software:
- Python 3.x
- Python modules (most included in python core by default):
sys, os, argparse, locale, platform, signal, logging, sqlite3, datetime
If a distro is used, all is met with:
- In Linux: 'python' package >= 3.0
- In FreeBSD: 'python3' and 'py36-sqlite3'
Also, execution via Docker is available, see note below.
================
| Installation |
================
For Linux systems:
+ From distribution repository:
Debian - https://packages.debian.org/tuptime
Ubuntu - https://packages.ubuntu.com/tuptime
# apt install tuptime
Fedora, EPEL - https://src.fedoraproject.org/rpms/tuptime
# dnf install tuptime
# systemctl enable tuptime.service && systemctl start tuptime.service
# systemctl enable tuptime-sync.timer && systemctl start tuptime-sync.timer
+ Using bash script:
Download and execute the installation script:
# bash < <(curl -Ls https://git.io/tuptime-install.sh)
+ Manual install:
Clone repository and copy executable file:
# install -m 755 tuptime/src/tuptime /usr/bin/tuptime
Add _tuptime user with sysusers or useradd, choose one:
With sysusers:
# install -m 644 tuptime/src/systemd/tuptime.sysusers /usr/lib/sysusers.d/tuptime.conf
# systemd-sysusers /usr/lib/sysusers.d/tuptime.conf
With useradd:
# useradd --system --no-create-home --home-dir '/var/lib/tuptime' \
--shell '/bin/false' --comment 'Tuptime execution user' _tuptime
Execute tuptime with a privilege user for create DB path:
# tuptime
Change owner of the DB path and file:
# chown -R _tuptime:_tuptime /var/lib/tuptime
Add sync execution with cron or timer, choose one:
With cron file:
# install -m 644 tuptime/src/cron.d/tuptime /etc/cron.d/tuptime
With systemd timer:
# install -m 644 tuptime/src/systemd/tuptime-sync.* /lib/systemd/system/
# systemctl enable tuptime-sync.timer && systemctl start tuptime-sync.timer
Copy systemd service file and enable it:
# install -m 644 tuptime/src/systemd/tuptime.service /lib/systemd/system/tuptime.service
# systemctl enable tuptime.service && systemctl start tuptime.service
Or if it is an old RedHat system or derivate, copy the init file:
# install -m 755 tuptime/src/init.d/redhat/tuptime /etc/init.d/tuptime
# chkconfig --add tuptime && chkconfig tuptime on
Or if it is and old Debian system or derivate, copy the init file:
# install -m 755 tuptime/src/init.d/debian/tuptime /etc/init.d/tuptime
# update-rc.d tuptime defaults
# /etc/init.d/tuptime start
That's all, enjoy it.
For BSDs systems:
+ On FreeBSD and derivates using packages or ports:
# pkg install tuptime
(or)
# /usr/ports/sysutils/tuptime
Add the suggested cron entry and enable it on rc:
# sysrc tuptime_enable=YES
# service tuptime start
+ Manual install on FreeBSD, OpenBSD, NetBSD and derivates:
Please, read misc/bsd-manual-install.txt
Via Docker:
Tuptime could be executed inside a container via Docker, but it still
requires the Systemd integration on the monitored host.
Please, read misc/docker/docker-notes.txt
Partial support for OSX (Darwin) system:
Tuptime is compatible with OSX, but the integration with LaunchD is not
complete. Please, read misc/osx-notes.txt
Note about Python 2.7:
Tuptime <= 3.5.0 can be executed with Python 2.7.
Set the shebang line to '#!/usr/bin/env python' to get it.
===========================
| Command line parameters |
===========================
These are the command line options, no configuration file is used:
-h, --help show this help message and exit
-A STARTUP, --at STARTUP
limit to this startup number
-b, --bootid show boot identifier
-c, --csv csv output
-d DATETIME_FMT, --date DATETIME_FMT
datetime/timestamp format output
-e DECIMALS, --dec DECIMALS
number of decimals in percentages
-E, --exclude STARTUP
startup numbers to exclude
-f FILE, --filedb FILE
database file (/var/lib/tuptime/tuptime.db)
-g, --graceful register a graceful shutdown
-i, --invert startup number in reverse count | swich between
longest/shortest on default output
-k, --kernel show kernel version
-l, --list enumerate system life as list
-n, --noup avoid update values into DB
-o TYPE, --order TYPE
order enumerate by [u|r|s|e|d|k]
-p, --power show power states run + sleep
-q, --quiet update values into DB without output
-r, --reverse reverse order in listings
-s, --seconds output time in seconds and epoch
-S STARTUP, --since STARTUP
limit from this startup number
-t, --table enumerate system life as table
--tat TIMESTAMP system status at epoch timestamp
--tsince TIMESTAMP limit from this epoch timestamp
--tuntil TIMESTAMP limit until this epoch timestamp
-U STARTUP, --until STARTUP
limit up until this startup number
-v, --verbose verbose output
-V, --version show version
-A <STARTUP>, --at <STARTUP>
Limit values only to this startup number. Take it from (-t) table or
(-l) list reports. Negative values are allowed.
Examples:
tuptime -A 34
tuptime --at 22
tuptime -l -A -1 # List last register
-b, --bootid
Add boot identifier to the output.
Examples:
tuptime -b
tuptime -t --bootid
-h, --help
Print a quick reference of the command line parameters.
Examples:
tuptime -h
tuptime --help
-c, --csv
Report in csv format.
Examples:
tuptime --csv
tuptime --csv -t
-d <DATETIME_FMT>, --date=<DATETIME_FMT>
Change the datetime/timestamp format.
By default the output use the configured system locales.
The following is a list of all the format codes that the 1989 C standard
requires, and these work on all platforms with a standard C implementation.
%a Weekday as locale’s abbreviated name.
%A Weekday as locale’s full name.
%w Weekday as a decimal number, where 0 is Sunday and 6 is
Saturday.
%d Day of the month as a zero-padded decimal number.
%b Month as locale’s abbreviated name.
%B Month as locale’s full name.
%m Month as a zero-padded decimal number.
%y Year without century as a zero-padded decimal number.
%Y Year with century as a decimal number.
%H Hour (24-hour clock) as a zero-padded decimal number.
%I Hour (12-hour clock) as a zero-padded decimal number.
%p Locale’s equivalent of either AM or PM.
%M Minute as a zero-padded decimal number.
%S Second as a zero-padded decimal number.
%f Microsecond as a decimal number, zero-padded on the left.
%z UTC offset in the form +HHMM or -HHMM (empty string if the
the object is naive).
%Z Time zone name (empty string if the object is naive).
%j Day of the year as a zero-padded decimal number.
%U Week number of the year (Sunday as the first day of the week)
as a zero padded decimal number. All days in a new year
preceding the first Sunday are considered to be in week 0.
%W Week number of the year (Monday as the first day of the week)
as a decimal number. All days in a new year preceding the
first Monday are considered to be in week 0.
%c Locale’s appropriate date and time representation.
%x Locale’s appropriate date representation.
%X Locale’s appropriate time representation.
%% A literal '%' character.
To see the full set of format codes supported on your platform, consult the
strftime(3) documentation.
Examples:
tuptime -d '%X %x' # (locale by default)
tuptime -d '%H:%M:%S %m-%d-%Y' # MDY style
tuptime -d '%H:%M:%S %d-%b-%Y' # DMY style
-e DECIMALS, --dec DECIMALS
Change the decimal length in percentages. The number is rounded to this value.
Examples:
tuptime --dec 9
tuptime --power --dec 9
-E <STARTUP>, --exclude <STARTUP>
Avoid take into account the startup numbers defined. Multiple values must be
separated by comma ',' and ranges with dash '-'. Default output prints
'trimmed' if the excluded numbers are in middle of the range.
Examples:
tuptime -E 9
tuptime -E '9-20'
tuptime --exclude '2,10-20,50'
-f <FILE>, --filedb=<FILE>
Define an alternative database file. Default is located in
'/var/lib/tuptime/tuptime.db'. It takes precedence over environmental variable
'TUPTIME_DBF'.
Examples:
tuptime -f /var/lib/tuptime/tuptime.db # (Default)
tuptime -f /tmp/test1.db
tuptime --filedb /tmp/test2.db
-g, --graceful
Register the time in DB as a graceful shutdown. This option is the way that
tuptime have for know if is a good or a bad shutdown. This is used in the
init.d and systemd files at the shutdown process.
Examples:
tuptime -g
tuptime --graceful
-i, --invert
Print startup number in reverse count starting since last boot when it's
used in conjunction with (-t), (-l), or (--tat) options. Equal than the output of
'journalctl --list-boots'.
Change Longest to Shortest values on default output report.
Change Total Uptime to Downtime on (-m) option.
Examples:
tuptime -t -i
tuptime -tib
tuptime -l --invert
tuptime -i
-k, --kernel
Add kernel information to the output.
Examples:
tuptime -k
tuptime -lk
tuptime --kernel
tuptime -t --kernel
-l, --list
Enumerate system life as a list.
Examples:
tuptime -l
tuptime --list
-n, --noup
Avoid update values in the DB with the current btime, uptime, etc. Useful
when reporting modified DB files.
Examples:
tuptime -n
tuptime --noup
-o [u|r|s|e|d|k], --order=[u|r|s|e|d|k]
Order enumerate output from table or list by:
<u> uptime
<r> runtime
<s> sleep time
<e> end status
<d> downtime
<k> kernel
Therefore, only works in conjunction with (-t) or (-l) options.
Examples:
tuptime -t -o e
tuptime -l -o k
tuptime -t --order u
tuptime -l --order d
-p, --power
Print the running and sleeping accumulated time for the correspondent uptime slot.
Note that only Python >= 3.6 can register this values.
Examples:
tuptime -p
tuptime --power
tuptime -tp
tuptime -l --power
-q, --quiet
Update values into database without compute and print output values.
Examples:
tuptime -q
tuptime --quiet
-r, --reverse
Reverse order for table or list output.
Examples:
tuptime -r
tuptime -lur
tuptime -teo --reverse
tuptime -l --reverse
-s, --seconds
Change default human readable datetime/timestamp style and print times in seconds
and datetimes in epoch.
Examples:
tuptime -s
tuptime --seconds
-S <STARTUP>, --since <STARTUP>
Limit values only from this startup number. Take it from (-t) table or
(-l) list reports. Negative values are allowed.
Examples:
tuptime -S 34
tuptime --since 22
tuptime -l -S -10
-t, --table
Enumerate system life as a table.
Examples:
tuptime -t
tuptime --table
--tat
Report system status at specific timestamp, register number enclosed and time
elapsed and remaining in that matched status.
Examples:
tuptime --tat 1555280149
tuptime --tat `date -d "1 day ago" +%s`
tuptime -cs --tat 1555281311
--tsince <TIMESTAMP>
Limit report from the given epoch timestamp. Negative values are allowed.
Options (-S) and (-U) have precedence over this one.
Examples:
tuptime --tsince 1454998872
tuptime --tsince `date -d "20-JAN-18" +%s`
tuptime --tsince -31536000 # Since one year ago
--tuntil <TIMESTAMP>
Limit report until the given epoch timestamp. Negative values are allowed.
Options (-S) and (-U) have precedence over this one.
Examples:
tuptime --tuntil 1454999619
tuptime --tuntil `date -d "2018-01-20 16:21:42" +%s`
tuptime --tuntil -2592000 # Until one month ago
-U <STARTUP>, --until <STARTUP>
Limit values only up until this startup number. Take it from (-t) table or
(-l) list reports. Negative values are allowed.
Examples:
tuptime -U 45
tuptime --until 11
tuptime -l -U -10
-v, --verbose
Print information about the internals of tuptime. It's good for debugging
how it gets the variables.
Examples:
tuptime -v
tuptime --verbose
-V, --version:
Print version number and exit.
Examples:
tuptime -V
tuptime --version
=========================
| Environment variables |
=========================
List of supported environmental variables:
TUPTIME_DBF
Set an alternative database file path. The argument -f, --filedb takes
precedence over this.
Example for ksh / sh / bash:
export TUPTIME_DBF="/opt/tuptime.db"
Example for csh / tcsh:
setenv TUPTIME_DBF /opt/tuptime.db
Example for systemd unit:
Environment="TUPTIME_DBF='/opt/tuptime.db'"
======================
| Schedule execution |
======================
It's important to have a schedule execution. If the init or systemd scripts
are installed as it needs, the program only update values at startup and
shutdown, but if the system fails, hangs or whatever, the uptime time will
be lost. Be sure that the cron entry or, in other case, the systemd
tuptime-sync.[timer|service] units are installed as expected.
Why is it needed the use of an other .service file with the name tuptime-sync?
The default tuptime.service file uses the option 'RemainAfterExit=true' and
nowadays the systemd timer can't restart an already running service, so, this
is the best workaround.
By default, both have a 5 minutes scheduled execution. Feel free to lower
that value if your requirements are narrow.
==================
| Default output |
==================
System startups:
Total number of system startups registered since first timestamp available.
System shutdowns:
Total number of shutdowns done correctly or incorrectly.
System life:
Time counter since first startup timestamp available.
System uptime:
System downtime:
Percentage of time and time counter.
Longest uptime:
Longest downtime:
Time counter and date with the longest/shortest uptime register.
Average uptime:
Average downtime:
Average time counter.
Current uptime:
Actual time counter and datetime since registered boot timestamp.
======================================
| DB format changes between versions |
======================================
From 2.x to 3.x:
From 2.x to 3.0, reorder columns, added offbtime, endst and downtime. From 3.0 to 3.1,
added kernel column. The migration scripts are loated at:
https://github.com/rfmoz/tuptime/blob/master/misc/cripts/db-tuptime-migrate-2.0-to-3.0.sh
https://github.com/rfmoz/tuptime/blob/master/misc/cripts/db-tuptime-migrate-3.0-to-3.1.sh
From 3.x to 4.x:
Added rntime and slptime registers, also uptime, rntime, slptime, downtime are now integers.
The migrations is done automatically from Tuptime. Also, the migration script
is located at:
https://github.com/rfmoz/tuptime/blob/master/misc/cripts/db-tuptime-migrate-3.1-to-4.0.sh
From 4.x to 5.x:
Added bootid register. The migrations is done automatically from Tuptime.
Also, the migration script is located at:
https://github.com/rfmoz/tuptime/blob/master/misc/cripts/db-tuptime-migrate-4.0-to-5.0.sh
==================
| Database Specs |
==================
Tuptime use a sqlite3 database located in '/var/lib/tuptime/tuptime.db' with
the following format:
tuptime (bootid text,
btime integer,
uptime integer,
rntime integer,
slptime integer,
offbtime integer,
endst integer,
downtime integer,
kernel text)
bootid Unique boot identifier (if it exists)
btime Startup timestamp in epoch
uptime Uptime seconds
rntime Running time seconds
slptime Sleeping time seconds
offbtime Shutdown timestamp in epoch
endst Type of shutdown [1 ok | 0 bad]
downtime Downtime seconds
kernel Name of the kernel
The number of startup sequence is extracted from 'rowid', the signed integer
key that uniquely identifies the row within its table in sqlite.
The bootid value is available since Linux 2.6.20 and FreeBSD 11.0.
For reset all the values, simply delete the database file.
It is possible to query and modify it directly, but it's better to use any of
the scripts located under 'misc/scripts' for modify, join or check it.
If a complete row is deleted and the database is not recreate completely (vacuum),
rowid keep the real information about startup number. Tuptime will notify about
'Deleted rows in DB' if the verbose mode is enabled.
============================
| About sync date and time |
============================
Substantial time jumps within a running system may trigger unespected behavior.
Take care of the hardware clock RTC (if the system have), or how the operating
system initializes their clock at startup.
If the system starts with a incorrect datetime and some external time sync is
used, like ntpd or timesyncd, a few time after boot the btime reported by the
system may change (it is a live value computed by now() - uptime). This behaviour
is also related with adjustments performed by adjtime(3), systems running inside
virtualized environments, servers with high load or with high disk I/O, wrong
computation of jiffies / HZ and the problema of lost ticks.
A slightly drift is compensate over uptime/downtime values. Large drift can
produce unreal registers and output. For example, Raspberry Pi doesn't have any
clock and initializes from 1 January 1970. A downtime of 0, when it's evident
that it was physically impossible, it's a common indicator than the clock had a
large sync jump.
As Tuptime have time dependency, its Systemd service unit pulls time-sync.target
and, therefore, is ordered to start before it. Note that it doesn't assure that
the clock synchronization is achieved, only that it needs the start of the
synchronization service and the date isn't going to be before last shutdown. On
last releases of Systemd, there is a new unit that delays reaching time-sync.target
until stay on sync 'systemd-time-wait-sync', but it isn't enable by default nowadays.
On Linux systems running Systemd, when the system clock has been synchronized, the
file /run/systemd/timesync/synchronized is created. Tuptime check it and print a
verbose line when it doesn't exists 'INFO:Timesync state file not found'.
A few recommendations / workarounds are:
Enable 'systemd-time-wait-sync'. But take care because it can delay the execution
more than expected and a startup can be lost. Note that if it couldn't reach the
synchronization before shutdown, Tuptime isn't going to be executed on that startup.
Check open issues on Systemd's GitHub related to this scenario.
Use 'systemd-timesyncd' if it is available (timedatectl set-ntp true), it can also
keep the time in systems without hardware clock. Disable any other time sync service
like 'ntp', 'chrony', 'openntpd' to avoid issues, most related with the use in
conjunction with 'systemd-time-wait-sync'.
Use any time sync service like 'ntp', 'chrony' or 'openntpd' and disable
'systemd-timesyncd'. Add the time sync service requirement on 'tuptime.service' at
the end of 'After=' and 'Wants=' lines.
Force a time sync in the 'tuptime.unit' file. Add under '[Service]' definition the
line 'ExecStartPre=/usr/bin/ntpdate pool.ntp.org'
If the systems is a single-board computer, like Raspberry Pi or Beaglebone, use a
RTC module and configure it properly.
Without Systemd, use a ntp client and delay the execution of tuptime until the time
is synced. Also, 'ntp-wait' can be added to the init script and the cron line
preceeding the tuptime execution.
Try and failure is the best way to find the right solution for the environment in
which the system is running.
The drift value that Tuptime have reference is reported in verbose mode 'tuptime -v'
in the line 'INFO:Drift over btime'. Values around +-1 are common and can be
considered as normal.
Systemd timer issues:
https://github.com/systemd/systemd/issues/14061
https://github.com/systemd/systemd/issues/8683
https://github.com/systemd/systemd/issues/5097
Good reads:
https://tools.ietf.org/html/rfc1589
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=119971
http://man7.org/linux/man-pages/man2/clock_gettime.2.html
https://unix.stackexchange.com/questions/118631/how-can-i-measure-and-prevent-clock-drift
Timekeeping in VMware Virtual Machines (Document)
https://venam.nixers.net/blog/unix/2020/05/02/time-on-unix.html
https://papers.freebsd.org/2002/phk-timecounters.files/timecounter.pdf
========================
| Date and Epoch notes |
========================
The options --tsince, --tuntil and --tat use epoch timestamp format, as quick
reference here are some notes:
Seconds equivalences:
1 year 31536000 seconds
1 month 2592000 seconds
1 week 604800 seconds
1 day 86400 seconds
1 hour 3600 seconds
Convert human datetime to epoch:
date -d "JAN-18-2018" +%s
date -d "20-JAN-18" +%s
date -d "2018-01-20 16:21:42" +%s
date -d "2018-01-20 16:21:42 GMT+2" +%s
date -d "3 week ago" +%s
date -d "1 year ago" +%s
date -d "now" +%s
Convert epoch to human datetime:
date -d "@1516748400"
Preceding conversions uses GNU date, on FreeBSD install "coreutils" and call
them with "gdate".
Examples of using it directly:
tuptime --tsince `date -d "1 day ago" +%s`
tuptime --tuntil `date -d "1 day ago" +%s`
tuptime --tsince `date -d "1 week ago" +%s` --tuntil `date -d "1 day ago" +%s`
tuptime --tat `date -d "1 day ago" +%s`
More info about datetime input formats:
man date
'https://www.gnu.org/software/coreutils/manual/html_node/
Date-input-formats.html#Date-input-formats'
===========
| Caveats |
===========
Tuptime can be executed with 'root' user, but it's recommendable the use of an
unprivileged user, like '_tuptime' as the installation section points to avoid
security problems.
Tuptime tries to save values on DB in every execution. If the user doesn't have
permissions over the file, an informational verbose message is generated. This
situation is handled for print values, but not when register a graceful shutdown.
That's why a user who can write in the database file must execute tuptime, usually
from the init manager, to update values at startup and shutdown, at least.
Tuptime register a new startup time (btime) reported by the system at first
execution after boot, likewise the new startup number and bootid. Also the
previous startup offbtime and downtime are also written. None of them changes
after that. Uptime may shift to accommodate drift and kernel name could be
replaced if live patching is used.
Tuptime is backed by a SQLite database. It is highly resilient but fails can
occur in any software and hardware level, producing a database corruption. Here
is an excellent documentation about it https://www.sqlite.org/howtocorrupt.html
If a backup of the DB file is needed in other place, take a look to Litestream.
This is a streaming replication tool for SQLite databases, it works fine with
Tuptime. https://litestream.io/
The uptime reference is linked with the wall clock time elapsed since boot, not
the running effective time. Both the running (effective time) and hibernate
power states fall within the applicable uptime slot.
If the system have configured a time zone with daylight saving time (DST),
to avoid problems when the time change, the real time clock (RTC) should have
set to universal time coordinated (UTC), not local time. Check it with
'timedatectl' command. Reports involving DST changes are completely right.
The system current local time zone is the reference for all timestamps printed.
https://tldp.org/HOWTO/Clock-2.html
If the Linux system execute a full force shutdown or reboot, like for example
"systemctl --force --force reboot", as it doesn't follow the steps to produce
a clean shutdown, the "End status" remains in bad state.
The name of the tool is based on the contraction of the words Total Uptime.
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!471 blocks