Compare commits
17 Commits
ea6d5e6042
...
34967db7ee
Author | SHA1 | Date |
---|---|---|
Nathan Dwarshuis | 34967db7ee | |
Nathan Dwarshuis | 738d365231 | |
Nathan Dwarshuis | a5183f4109 | |
Nathan Dwarshuis | 882ac46259 | |
Nathan Dwarshuis | 96734cd328 | |
Nathan Dwarshuis | d82ad44f58 | |
Nathan Dwarshuis | 500d8b634e | |
Nathan Dwarshuis | 187a148631 | |
Nathan Dwarshuis | cc01482477 | |
Nathan Dwarshuis | 1ab23d039b | |
Nathan Dwarshuis | b5920374a7 | |
Nathan Dwarshuis | 564796b81d | |
Nathan Dwarshuis | da5b4d3f2b | |
Nathan Dwarshuis | 98cc789d78 | |
Nathan Dwarshuis | da9a6b0c46 | |
Nathan Dwarshuis | e7d5b63c38 | |
Nathan Dwarshuis | b797e46c04 |
|
@ -1,3 +1,22 @@
|
||||||
|
{- Types to define a conky configuration.
|
||||||
|
|
||||||
|
Key terms:
|
||||||
|
- widget: the components of a module (dials, bars, plots, etc)
|
||||||
|
- module: a monitor for some aspect of the system to be displayed
|
||||||
|
- column: a vertical arrangement of modules
|
||||||
|
- panel: a horizontal arrangement of columns
|
||||||
|
- layout: a horizontal arrangement of panels
|
||||||
|
- geometry: the 'dimensions' of a widget
|
||||||
|
- pattern: the color/gradient to be used in widgets
|
||||||
|
|
||||||
|
NOTE: geometry is defined on a global level and at the module level. The global
|
||||||
|
level is defined in the theme type (see below) and applies to all widgets
|
||||||
|
equally. Some dimensions are not defined globally and are instead delegated to
|
||||||
|
each module to define themselves (these are not overrides so they must be
|
||||||
|
specified, although most modules have sensible defaults hardcoded)
|
||||||
|
|
||||||
|
See the 'fallback' config for an example of how to use these types.
|
||||||
|
-}
|
||||||
let Vector2 = \(a : Type) -> { x : a, y : a }
|
let Vector2 = \(a : Type) -> { x : a, y : a }
|
||||||
|
|
||||||
let Point = Vector2 Natural
|
let Point = Vector2 Natural
|
||||||
|
@ -6,43 +25,94 @@ let Margin = Vector2 Natural
|
||||||
|
|
||||||
let FSPath = { name : Text, path : Text }
|
let FSPath = { name : Text, path : Text }
|
||||||
|
|
||||||
let TextGeo = { Type = { text_spacing : Natural }, default.text_spacing = 20 }
|
let TextGeo =
|
||||||
|
{-
|
||||||
|
Defines text dimensions for multiline visuals
|
||||||
|
|
||||||
let SepGeo = { Type = { sep_spacing : Natural }, default.sep_spacing = 20 }
|
text_spacing: gap between lines of text
|
||||||
|
-}
|
||||||
|
{ Type = { text_spacing : Natural }, default.text_spacing = 20 }
|
||||||
|
|
||||||
|
let SepGeo =
|
||||||
|
{-
|
||||||
|
Defines separator dimensions
|
||||||
|
|
||||||
|
sep_spacing: gap between the separator and either above or below
|
||||||
|
-}
|
||||||
|
{ Type = { sep_spacing : Natural }, default.sep_spacing = 20 }
|
||||||
|
|
||||||
let PlotGeo =
|
let PlotGeo =
|
||||||
|
{-
|
||||||
|
Defines plot dimensions
|
||||||
|
|
||||||
|
sec_break: gap between the plot label and the plot itself
|
||||||
|
height: height of the plot (without the label)
|
||||||
|
ticks_y: number of ticks on the y axis
|
||||||
|
-}
|
||||||
{ Type = { sec_break : Natural, height : Natural, ticks_y : Natural }
|
{ Type = { sec_break : Natural, height : Natural, ticks_y : Natural }
|
||||||
, default = { sec_break = 20, height = 56, ticks_y = 4 }
|
, default = { sec_break = 20, height = 56, ticks_y = 4 }
|
||||||
}
|
}
|
||||||
|
|
||||||
let PlotGeo_ = { Type = { plot : PlotGeo.Type }, default.plot = PlotGeo::{=} }
|
let PlotGeo_ = { Type = { plot : PlotGeo.Type }, default.plot = PlotGeo::{=} }
|
||||||
|
|
||||||
let TableGeo = { Type = { sec_break : Natural }, default.sec_break = 20 }
|
let TableGeo =
|
||||||
|
{-
|
||||||
|
Defines table dimensions
|
||||||
|
|
||||||
|
sec_break: spacing between header and the first table row
|
||||||
|
-}
|
||||||
|
{ Type = { sec_break : Natural }, default.sec_break = 20 }
|
||||||
|
|
||||||
let TableGeo_ =
|
let TableGeo_ =
|
||||||
{ Type = { table : TableGeo.Type }, default.table = TableGeo::{=} }
|
{ Type = { table : TableGeo.Type }, default.table = TableGeo::{=} }
|
||||||
|
|
||||||
let FSGeo =
|
let FSGeo =
|
||||||
|
{-
|
||||||
|
Defines Filesystem module dimensions
|
||||||
|
|
||||||
|
bar_spacing: spacing between percent usage bars
|
||||||
|
bar_pad: spacing between usage bars and the left edge of the panel
|
||||||
|
-}
|
||||||
{ Type = { bar_spacing : Natural, bar_pad : Natural } //\\ SepGeo.Type
|
{ Type = { bar_spacing : Natural, bar_pad : Natural } //\\ SepGeo.Type
|
||||||
, default = { bar_spacing = 20, bar_pad = 100 } /\ SepGeo::{=}
|
, default = { bar_spacing = 20, bar_pad = 100 } /\ SepGeo::{=}
|
||||||
}
|
}
|
||||||
|
|
||||||
let GfxGeo =
|
let GfxGeo =
|
||||||
|
{-
|
||||||
|
Defines Graphics module dimensions
|
||||||
|
|
||||||
|
See PlotGeo, TextGeo, and SepGeo for options included here
|
||||||
|
-}
|
||||||
{ Type = SepGeo.Type //\\ PlotGeo_.Type //\\ TextGeo.Type
|
{ Type = SepGeo.Type //\\ PlotGeo_.Type //\\ TextGeo.Type
|
||||||
, default = SepGeo::{=} /\ PlotGeo_::{=} /\ TextGeo::{=}
|
, default = SepGeo::{=} /\ PlotGeo_::{=} /\ TextGeo::{=}
|
||||||
}
|
}
|
||||||
|
|
||||||
let MemGeo =
|
let MemGeo =
|
||||||
|
{-
|
||||||
|
Defines Memory module dimensions
|
||||||
|
|
||||||
|
See PlotGeo, TextGeo, and TableGeo for options included here
|
||||||
|
-}
|
||||||
{ Type = TextGeo.Type //\\ PlotGeo_.Type //\\ TableGeo_.Type
|
{ Type = TextGeo.Type //\\ PlotGeo_.Type //\\ TableGeo_.Type
|
||||||
, default = TextGeo::{=} /\ PlotGeo_::{=} /\ TableGeo_::{=}
|
, default = TextGeo::{=} /\ PlotGeo_::{=} /\ TableGeo_::{=}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ProcGeo =
|
let ProcGeo =
|
||||||
|
{-
|
||||||
|
Defines Processor module dimensions
|
||||||
|
|
||||||
|
See GfxGeo and TableGeo for options included here
|
||||||
|
-}
|
||||||
{ Type = GfxGeo.Type //\\ TableGeo_.Type
|
{ Type = GfxGeo.Type //\\ TableGeo_.Type
|
||||||
, default = GfxGeo::{=} /\ TableGeo_::{=}
|
, default = GfxGeo::{=} /\ TableGeo_::{=}
|
||||||
}
|
}
|
||||||
|
|
||||||
let PwrGeo =
|
let PwrGeo =
|
||||||
|
{-
|
||||||
|
Defines Processor module dimensions
|
||||||
|
|
||||||
|
See TextGeo and PlotGeo for options included here
|
||||||
|
-}
|
||||||
{ Type = TextGeo.Type //\\ PlotGeo_.Type
|
{ Type = TextGeo.Type //\\ PlotGeo_.Type
|
||||||
, default = TextGeo::{=} /\ PlotGeo_::{=}
|
, default = TextGeo::{=} /\ PlotGeo_::{=}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +121,14 @@ let AllGeo =
|
||||||
{ TextGeo, PlotGeo, TableGeo, FSGeo, GfxGeo, MemGeo, ProcGeo, PwrGeo }
|
{ TextGeo, PlotGeo, TableGeo, FSGeo, GfxGeo, MemGeo, ProcGeo, PwrGeo }
|
||||||
|
|
||||||
let FileSystem =
|
let FileSystem =
|
||||||
|
{-
|
||||||
|
Defines Filesystem module configuration
|
||||||
|
|
||||||
|
show_smart: show SMART daemon indicator
|
||||||
|
show_seafile: show seafile daemon indicator
|
||||||
|
fs_paths: list of filesystem paths to monitor for percent usage
|
||||||
|
geometry: dimensional data for this module
|
||||||
|
-}
|
||||||
{ Type =
|
{ Type =
|
||||||
{ show_smart : Bool
|
{ show_smart : Bool
|
||||||
, show_seafile : Bool
|
, show_seafile : Bool
|
||||||
|
@ -60,7 +138,35 @@ let FileSystem =
|
||||||
, default.geometry = FSGeo::{=}
|
, default.geometry = FSGeo::{=}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let GPUPower =
|
||||||
|
{-
|
||||||
|
Controls how the GPU will be queries so it can go to sleep when 'idle'.
|
||||||
|
|
||||||
|
freq_limit: only query the GPU every 'cycle_seconds' when frequency is below
|
||||||
|
this value; this should be a sensible value representing and
|
||||||
|
'idle' gpu state. Set to 0 to disable this check entirely.
|
||||||
|
cycle_seconds: the number of seconds to query the GPU when below the idle
|
||||||
|
frequency limit; note that this must be long enough to let
|
||||||
|
the GPU go to sleep when it actually is ready to sleep, but
|
||||||
|
not so long that the next query will miss too much activity
|
||||||
|
in the case the GPU doesn't shut off
|
||||||
|
-}
|
||||||
|
{ Type = { freq_limit : Natural, cycle_seconds : Natural }
|
||||||
|
, default = { freq_limit = 250, cycle_seconds = 10 }
|
||||||
|
}
|
||||||
|
|
||||||
let Graphics =
|
let Graphics =
|
||||||
|
{-
|
||||||
|
Defines Graphics module configuration
|
||||||
|
|
||||||
|
dev_power: sysfs path to graphics card power directory
|
||||||
|
show_temp: show temperature in celsius
|
||||||
|
show_clock: show clock speed
|
||||||
|
show_gpu_util: show percent utilization
|
||||||
|
show_mem_util: show percent memory utilized
|
||||||
|
show_vid_util: show percent video utilized
|
||||||
|
geometry: dimensional configuration for this module
|
||||||
|
-}
|
||||||
{ Type =
|
{ Type =
|
||||||
{ dev_power : Text
|
{ dev_power : Text
|
||||||
, show_temp : Bool
|
, show_temp : Bool
|
||||||
|
@ -69,11 +175,21 @@ let Graphics =
|
||||||
, show_mem_util : Bool
|
, show_mem_util : Bool
|
||||||
, show_vid_util : Bool
|
, show_vid_util : Bool
|
||||||
, geometry : GfxGeo.Type
|
, geometry : GfxGeo.Type
|
||||||
|
, power : GPUPower.Type
|
||||||
}
|
}
|
||||||
, default.geometry = GfxGeo::{=}
|
, default = { geometry = GfxGeo::{=}, power = GPUPower::{=} }
|
||||||
}
|
}
|
||||||
|
|
||||||
let Memory =
|
let Memory =
|
||||||
|
{-
|
||||||
|
Defines Memory module configuration
|
||||||
|
|
||||||
|
show_stats: show total memory gauge, cache, buffers, shared, and slab
|
||||||
|
show_plot: show memory utilization plot
|
||||||
|
show_swap: show swap utilization gauge (only if 'show_stats' is true)
|
||||||
|
table_rows: top processes by memory to display in table (max 10)
|
||||||
|
geometry: dimensional configuration for this module
|
||||||
|
-}
|
||||||
{ Type =
|
{ Type =
|
||||||
{ show_stats : Bool
|
{ show_stats : Bool
|
||||||
, show_plot : Bool
|
, show_plot : Bool
|
||||||
|
@ -85,12 +201,35 @@ let Memory =
|
||||||
}
|
}
|
||||||
|
|
||||||
let Network =
|
let Network =
|
||||||
|
{-
|
||||||
|
Defines Network module configuration
|
||||||
|
|
||||||
|
geometry: dimensional configuration for this module
|
||||||
|
-}
|
||||||
{ Type = { geometry : PlotGeo_.Type }, default.geometry = PlotGeo_::{=} }
|
{ Type = { geometry : PlotGeo_.Type }, default.geometry = PlotGeo_::{=} }
|
||||||
|
|
||||||
|
let CoreGroup =
|
||||||
|
{-
|
||||||
|
Defines a processor group for the Processor module
|
||||||
|
|
||||||
|
threads: the number of threads for each core in this group (usually 1 or 2)
|
||||||
|
rows: the number of rows over which to display the cores in this group
|
||||||
|
padding: the spacing to the left and right of the gauges in this group
|
||||||
|
-}
|
||||||
|
{ threads : Natural, rows : Natural, padding : Natural }
|
||||||
|
|
||||||
let Processor =
|
let Processor =
|
||||||
|
{-
|
||||||
|
Defines Network module configuration
|
||||||
|
|
||||||
|
core_groups: a list of core groups to display
|
||||||
|
show_stats: show frequency for each core group and HWP status
|
||||||
|
show_plot: show percent utilization plot
|
||||||
|
table_rows: top processes by cpu percent to display in table (max 10)
|
||||||
|
geometry: dimensional configuration for this module
|
||||||
|
-}
|
||||||
{ Type =
|
{ Type =
|
||||||
{ core_rows : Natural
|
{ core_groups : List CoreGroup
|
||||||
, core_padding : Natural
|
|
||||||
, show_stats : Bool
|
, show_stats : Bool
|
||||||
, show_plot : Bool
|
, show_plot : Bool
|
||||||
, table_rows : Natural
|
, table_rows : Natural
|
||||||
|
@ -99,23 +238,53 @@ let Processor =
|
||||||
, default.geometry = ProcGeo::{=}
|
, default.geometry = ProcGeo::{=}
|
||||||
}
|
}
|
||||||
|
|
||||||
let RaplSpec = { name : Text, address : Text }
|
|
||||||
|
|
||||||
let Pacman =
|
let Pacman =
|
||||||
|
{-
|
||||||
|
Defines Pacman module configuration
|
||||||
|
|
||||||
|
geometry: dimensional configuration for this module
|
||||||
|
-}
|
||||||
{ Type = { geometry : TextGeo.Type }, default.geometry = TextGeo::{=} }
|
{ Type = { geometry : TextGeo.Type }, default.geometry = TextGeo::{=} }
|
||||||
|
|
||||||
|
let RaplSpec =
|
||||||
|
{-
|
||||||
|
Defines a RAPL endpoint to display in the Power module
|
||||||
|
|
||||||
|
name: nice name to display above plot
|
||||||
|
address: sysfs address to the directory containing the 'energy_uj' file
|
||||||
|
-}
|
||||||
|
{ name : Text, address : Text }
|
||||||
|
|
||||||
let Power =
|
let Power =
|
||||||
|
{-
|
||||||
|
Defines Power module configuration
|
||||||
|
|
||||||
|
battery: sysfs path to the battery to display
|
||||||
|
rapl_specs: list of RAPL endpoints to display
|
||||||
|
geometry: dimensional configuration for this module
|
||||||
|
-}
|
||||||
{ Type =
|
{ Type =
|
||||||
{ battery : Text, rapl_specs : List RaplSpec, geometry : PwrGeo.Type }
|
{ battery : Text, rapl_specs : List RaplSpec, geometry : PwrGeo.Type }
|
||||||
, default.geometry = PwrGeo::{=}
|
, default.geometry = PwrGeo::{=}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ReadWrite =
|
let ReadWrite =
|
||||||
|
{-
|
||||||
|
Defines ReadWrite module configuration
|
||||||
|
|
||||||
|
geometry: dimensional configuration for this module
|
||||||
|
-}
|
||||||
{ Type = { devices : List Text, geometry : PlotGeo_.Type }
|
{ Type = { devices : List Text, geometry : PlotGeo_.Type }
|
||||||
, default.geometry = PlotGeo_::{=}
|
, default.geometry = PlotGeo_::{=}
|
||||||
}
|
}
|
||||||
|
|
||||||
let System = Pacman
|
let System =
|
||||||
|
{-
|
||||||
|
Defines System module configuration
|
||||||
|
|
||||||
|
geometry: dimensional configuration for this module
|
||||||
|
-}
|
||||||
|
Pacman
|
||||||
|
|
||||||
let AllModules =
|
let AllModules =
|
||||||
{ FileSystem
|
{ FileSystem
|
||||||
|
@ -130,6 +299,8 @@ let AllModules =
|
||||||
}
|
}
|
||||||
|
|
||||||
let ModType =
|
let ModType =
|
||||||
|
{- Wrapper type for each module
|
||||||
|
-}
|
||||||
< filesystem : FileSystem.Type
|
< filesystem : FileSystem.Type
|
||||||
| graphics : Graphics.Type
|
| graphics : Graphics.Type
|
||||||
| memory : Memory.Type
|
| memory : Memory.Type
|
||||||
|
@ -141,21 +312,61 @@ let ModType =
|
||||||
| system : System.Type
|
| system : System.Type
|
||||||
>
|
>
|
||||||
|
|
||||||
let Annotated = \(a : Type) -> { type : Text, data : a }
|
let Annotated =
|
||||||
|
{- Helper to ensure yaml is formatted in a parsable manner for union types
|
||||||
|
|
||||||
let Block = < Pad : Natural | Mod : Annotated ModType >
|
The intent is to make a block like:
|
||||||
|
|
||||||
let Column_ = { blocks : List Block, width : Natural }
|
parent:
|
||||||
|
type: <constructor_name>
|
||||||
|
data: <stuff_in_the_union_type>
|
||||||
|
-}
|
||||||
|
\(a : Type) -> { type : Text, data : a }
|
||||||
|
|
||||||
let Column = < CPad : Natural | CCol : Column_ >
|
let Block =
|
||||||
|
{- Either vertical padding or a module -}
|
||||||
|
< Pad : Natural | Mod : Annotated ModType >
|
||||||
|
|
||||||
let Panel_ = { columns : List Column, margins : Margin }
|
let Column_ =
|
||||||
|
{- A column of modules to display
|
||||||
|
|
||||||
let Panel = < PPad : Natural | PPanel : Panel_ >
|
blocks: a list of blocks (either padding or a module)
|
||||||
|
width: the width of the column (and consequently everything in said column)
|
||||||
|
-}
|
||||||
|
{ blocks : List Block, width : Natural }
|
||||||
|
|
||||||
let Layout = { anchor : Point, panels : List Panel }
|
let Column =
|
||||||
|
{- Either a column with modules or padding between columns -}
|
||||||
|
< CPad : Natural | CCol : Column_ >
|
||||||
|
|
||||||
|
let Panel_ =
|
||||||
|
{- A panel definition.
|
||||||
|
|
||||||
|
columns: a list of columns to show (must be at least one)
|
||||||
|
margins: space between the panel border and the columns
|
||||||
|
-}
|
||||||
|
{ columns : List Column, margins : Margin }
|
||||||
|
|
||||||
|
let Panel =
|
||||||
|
{- Wrapper type for either a panel or horizontal padding between panels -}
|
||||||
|
< PPad : Natural | PPanel : Panel_ >
|
||||||
|
|
||||||
|
let Layout =
|
||||||
|
{- The layout of the display
|
||||||
|
|
||||||
|
'anchor' is the coordinate of the upper-left corner, and 'panels' contains
|
||||||
|
the specifications of each panel (ie modules to show).
|
||||||
|
-}
|
||||||
|
{ anchor : Point, panels : List Panel }
|
||||||
|
|
||||||
let Sizes =
|
let Sizes =
|
||||||
|
{- The font sizes to use for various widgets
|
||||||
|
|
||||||
|
plot_label: the text above the plot
|
||||||
|
table: the text in a table (headers and rows)
|
||||||
|
header: the text of each module header
|
||||||
|
normal: all other text (excluding plot axes)
|
||||||
|
-}
|
||||||
{ Type =
|
{ Type =
|
||||||
{ normal : Natural
|
{ normal : Natural
|
||||||
, plot_label : Natural
|
, plot_label : Natural
|
||||||
|
@ -166,16 +377,29 @@ let Sizes =
|
||||||
}
|
}
|
||||||
|
|
||||||
let Font =
|
let Font =
|
||||||
|
{- A complete font specification -}
|
||||||
{ Type = { family : Text, sizes : Sizes.Type }
|
{ Type = { family : Text, sizes : Sizes.Type }
|
||||||
, default = { family = "Neuropolitical", sizes = Sizes::{=} }
|
, default = { family = "Neuropolitical", sizes = Sizes::{=} }
|
||||||
}
|
}
|
||||||
|
|
||||||
let PlotGeometry =
|
let PlotGeometry =
|
||||||
|
{- Global dimensions for a plot
|
||||||
|
|
||||||
|
seconds: number of seconds to display on the x axis
|
||||||
|
ticks_x: the number of ticks to display on the x axis
|
||||||
|
-}
|
||||||
{ Type = { seconds : Natural, ticks_x : Natural }
|
{ Type = { seconds : Natural, ticks_x : Natural }
|
||||||
, default = { seconds = 90, ticks_x = 9 }
|
, default = { seconds = 90, ticks_x = 9 }
|
||||||
}
|
}
|
||||||
|
|
||||||
let TableGeometry =
|
let TableGeometry =
|
||||||
|
{- Global dimensions for a table
|
||||||
|
|
||||||
|
name_chars: the max number of characters a cell can have before being truncated
|
||||||
|
padding: the margins between the border and the interior text
|
||||||
|
header_padding: the distance between the header and the first row of text
|
||||||
|
row_spacing: the distance between each row
|
||||||
|
-}
|
||||||
{ Type =
|
{ Type =
|
||||||
{ name_chars : Natural
|
{ name_chars : Natural
|
||||||
, padding : Margin
|
, padding : Margin
|
||||||
|
@ -191,11 +415,17 @@ let TableGeometry =
|
||||||
}
|
}
|
||||||
|
|
||||||
let HeaderGeometry =
|
let HeaderGeometry =
|
||||||
|
{- Global dimensions for the header of a module
|
||||||
|
|
||||||
|
underline_offset: distance between bottom edge of text and the line below
|
||||||
|
padding: the distance between the underline and the first widget in the module
|
||||||
|
-}
|
||||||
{ Type = { underline_offset : Natural, padding : Natural }
|
{ Type = { underline_offset : Natural, padding : Natural }
|
||||||
, default = { underline_offset = 26, padding = 19 }
|
, default = { underline_offset = 26, padding = 19 }
|
||||||
}
|
}
|
||||||
|
|
||||||
let Geometry =
|
let Geometry =
|
||||||
|
{- Global config for dimensions of various widgets -}
|
||||||
{ Type =
|
{ Type =
|
||||||
{ plot : PlotGeometry.Type
|
{ plot : PlotGeometry.Type
|
||||||
, table : TableGeometry.Type
|
, table : TableGeometry.Type
|
||||||
|
@ -208,28 +438,72 @@ let Geometry =
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let StopRGB = { color : Natural, stop : Double }
|
let Color =
|
||||||
|
{- Alias for a solid color represented as a single hex number -}
|
||||||
|
Natural
|
||||||
|
|
||||||
let StopRGBA = { color : Natural, stop : Double, alpha : Double }
|
let Alpha =
|
||||||
|
{- Alias for alpha channel as a fraction between 0 and 1 -}
|
||||||
|
Double
|
||||||
|
|
||||||
let ColorAlpha = { color : Natural, alpha : Double }
|
let Stop =
|
||||||
|
{- Alias for the position of a color in a gradient (between 0 and 1) -}
|
||||||
|
Double
|
||||||
|
|
||||||
|
let StopRGB =
|
||||||
|
{- A solid color with a position in a gradiant -}
|
||||||
|
{ color : Color, stop : Stop }
|
||||||
|
|
||||||
|
let StopRGBA =
|
||||||
|
{- A transparent color with a position in a gradiant -}
|
||||||
|
{ color : Color, stop : Stop, alpha : Alpha }
|
||||||
|
|
||||||
|
let ColorAlpha =
|
||||||
|
{- A solid transparent color -}
|
||||||
|
{ color : Color, alpha : Alpha }
|
||||||
|
|
||||||
let Pattern =
|
let Pattern =
|
||||||
< RGB : Natural
|
{- Wrapper for different pattern types -}
|
||||||
|
< RGB : Color
|
||||||
| RGBA : ColorAlpha
|
| RGBA : ColorAlpha
|
||||||
| GradientRGB : List StopRGB
|
| GradientRGB : List StopRGB
|
||||||
| GradientRGBA : List StopRGBA
|
| GradientRGBA : List StopRGBA
|
||||||
>
|
>
|
||||||
|
|
||||||
let annotatePattern =
|
let annotatePattern =
|
||||||
|
{- Helper function to ensure patterns are parsable in yaml
|
||||||
|
|
||||||
|
This will create entries in the yaml config like:
|
||||||
|
|
||||||
|
parent:
|
||||||
|
type: <pattern_type>
|
||||||
|
date:
|
||||||
|
<field1>: ...
|
||||||
|
<field2>: ...
|
||||||
|
-}
|
||||||
\(a : Pattern) ->
|
\(a : Pattern) ->
|
||||||
{ type = showConstructor a, data = a } : Annotated Pattern
|
{ type = showConstructor a, data = a } : Annotated Pattern
|
||||||
|
|
||||||
let mod = \(a : ModType) -> Block.Mod { type = showConstructor a, data = a }
|
let mod =
|
||||||
|
{- Helper function to ensure modules are parsable in yaml
|
||||||
|
|
||||||
|
This will create entries in the yaml config like:
|
||||||
|
|
||||||
|
parent:
|
||||||
|
type: <module name>
|
||||||
|
date:
|
||||||
|
<field1>: ...
|
||||||
|
<field2>: ...
|
||||||
|
-}
|
||||||
|
\(a : ModType) -> Block.Mod { type = showConstructor a, data = a }
|
||||||
|
|
||||||
let APattern = Annotated Pattern
|
let APattern = Annotated Pattern
|
||||||
|
|
||||||
let symGradient =
|
let symGradient =
|
||||||
|
{- Make a symmetric gradient between two colors.
|
||||||
|
|
||||||
|
c0 will be the color of either edge and c1 will be the color in the center
|
||||||
|
-}
|
||||||
\(c0 : Natural) ->
|
\(c0 : Natural) ->
|
||||||
\(c1 : Natural) ->
|
\(c1 : Natural) ->
|
||||||
annotatePattern
|
annotatePattern
|
||||||
|
@ -241,6 +515,8 @@ let symGradient =
|
||||||
)
|
)
|
||||||
|
|
||||||
let Patterns =
|
let Patterns =
|
||||||
|
{- All patterns for a given theme
|
||||||
|
-}
|
||||||
{ Type =
|
{ Type =
|
||||||
{ header : APattern
|
{ header : APattern
|
||||||
, panel : { bg : APattern }
|
, panel : { bg : APattern }
|
||||||
|
@ -296,6 +572,14 @@ let Patterns =
|
||||||
}
|
}
|
||||||
|
|
||||||
let Theme =
|
let Theme =
|
||||||
|
{-
|
||||||
|
Defines the theme for displaying the window
|
||||||
|
|
||||||
|
font: the font to use
|
||||||
|
geometry: the global dimensions to use for each panel and widget (for
|
||||||
|
everything not define by individual modules)
|
||||||
|
patterns: colors and gradient definitions
|
||||||
|
-}
|
||||||
{ Type =
|
{ Type =
|
||||||
{ font : Font.Type
|
{ font : Font.Type
|
||||||
, geometry : Geometry.Type
|
, geometry : Geometry.Type
|
||||||
|
@ -305,19 +589,26 @@ let Theme =
|
||||||
{ font = Font::{=}, geometry = Geometry::{=}, patterns = Patterns::{=} }
|
{ font = Font::{=}, geometry = Geometry::{=}, patterns = Patterns::{=} }
|
||||||
}
|
}
|
||||||
|
|
||||||
let Bootstrap = { update_interval : Natural, dimensions : Point }
|
let Bootstrap =
|
||||||
|
{- Defines minimal options to start conky prior to calling any lua code.
|
||||||
|
-}
|
||||||
|
{ update_interval : Natural, dimensions : Point }
|
||||||
|
|
||||||
let Config = { bootstrap : Bootstrap, theme : Theme.Type, layout : Layout }
|
let Config =
|
||||||
|
{- Global config type -}
|
||||||
|
{ bootstrap : Bootstrap, theme : Theme.Type, layout : Layout }
|
||||||
|
|
||||||
let toConfig =
|
let toConfig =
|
||||||
\(i : Natural) ->
|
{- Helper function to generate a config -}
|
||||||
\(x : Natural) ->
|
\(update_interval : Natural) ->
|
||||||
\(y : Natural) ->
|
\(width : Natural) ->
|
||||||
\(t : Theme.Type) ->
|
\(height : Natural) ->
|
||||||
\(l : Layout) ->
|
\(theme : Theme.Type) ->
|
||||||
{ bootstrap = { update_interval = i, dimensions = { x, y } }
|
\(layout : Layout) ->
|
||||||
, theme = t
|
{ bootstrap =
|
||||||
, layout = l
|
{ update_interval, dimensions = { x = width, y = height } }
|
||||||
|
, theme
|
||||||
|
, layout
|
||||||
}
|
}
|
||||||
: Config
|
: Config
|
||||||
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
bootstrap:
|
|
||||||
update_interval: 1
|
|
||||||
dimensions: [1920, 1080]
|
|
||||||
modules:
|
|
||||||
memory:
|
|
||||||
show_stats: false
|
|
||||||
show_swap: false
|
|
||||||
show_plot: true
|
|
||||||
table_rows: 3
|
|
||||||
processor:
|
|
||||||
core_rows: 0
|
|
||||||
core_padding: 0
|
|
||||||
show_stats: false
|
|
||||||
show_plot: true
|
|
||||||
table_rows: 3
|
|
||||||
|
|
||||||
layout:
|
|
||||||
anchor: [12, 11]
|
|
||||||
panels:
|
|
||||||
- columns:
|
|
||||||
- {blocks: [network, 10, memory, 10, processor], width: 436}
|
|
||||||
margins: [20, 10]
|
|
||||||
|
|
||||||
theme:
|
|
||||||
font:
|
|
||||||
family: Neuropolitical
|
|
||||||
sizes:
|
|
||||||
normal: 13
|
|
||||||
plot_label: 8
|
|
||||||
table: 11
|
|
||||||
header: 15
|
|
||||||
geometry:
|
|
||||||
plot:
|
|
||||||
seconds: 90
|
|
||||||
ticks: [9, 4]
|
|
||||||
height: 56
|
|
||||||
spacing: 20
|
|
||||||
table:
|
|
||||||
name_chars: 8
|
|
||||||
padding: [6, 15]
|
|
||||||
header_padding: 20
|
|
||||||
row_spacing: 13
|
|
||||||
header:
|
|
||||||
underline_offset: 26
|
|
||||||
padding: 19
|
|
||||||
patterns:
|
|
||||||
header: 0xefefef
|
|
||||||
panel:
|
|
||||||
bg: {color: 0x121212, alpha: 0.7}
|
|
||||||
text:
|
|
||||||
active: 0xbfe1ff
|
|
||||||
inactive: 0xc8c8c8
|
|
||||||
critical: 0xff8282
|
|
||||||
border: 0x888888
|
|
||||||
plot:
|
|
||||||
grid: 0x666666
|
|
||||||
outline: 0x777777
|
|
||||||
data:
|
|
||||||
border:
|
|
||||||
gradient:
|
|
||||||
- {stop: 0, color: 0x003f7c}
|
|
||||||
- {stop: 1, color: 0x1e90ff}
|
|
||||||
fill:
|
|
||||||
gradient_alpha:
|
|
||||||
- {stop: 0.2, color: 0x316ece, alpha: 0.5}
|
|
||||||
- {stop: 1, color: 0x8cc7ff, alpha: 1.0}
|
|
||||||
indicator:
|
|
||||||
bg:
|
|
||||||
gradient:
|
|
||||||
- {stop: 0, color: 0x565656}
|
|
||||||
- {stop: 0.5, color: 0xbfbfbf}
|
|
||||||
- {stop: 1, color: 0x565656}
|
|
||||||
fg:
|
|
||||||
active:
|
|
||||||
gradient:
|
|
||||||
- {stop: 0, color: 0x316BA6}
|
|
||||||
- {stop: 0.5, color: 0x99CEFF}
|
|
||||||
- {stop: 1, color: 0x316BA6}
|
|
||||||
critical:
|
|
||||||
gradient:
|
|
||||||
- {stop: 0, color: 0xFF3333}
|
|
||||||
- {stop: 0.5, color: 0xFFB8B8}
|
|
||||||
- {stop: 1, color: 0xFF3333}
|
|
|
@ -1,375 +0,0 @@
|
||||||
$schema: "http://json-schema.org/draft-07/schema#"
|
|
||||||
description: over-engineered conky schema
|
|
||||||
required: [modules, layout]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
|
|
||||||
bootstrap:
|
|
||||||
required: [update_interval, dimensions]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
update_interval:
|
|
||||||
description: the update interval (seconds)
|
|
||||||
type: number
|
|
||||||
dimensions:
|
|
||||||
description: the max width/height of the conky window
|
|
||||||
type: array
|
|
||||||
minItems: 2
|
|
||||||
maxItems: 2
|
|
||||||
items:
|
|
||||||
type: integer
|
|
||||||
minimum: 1
|
|
||||||
|
|
||||||
# NOTE none of these are required
|
|
||||||
modules:
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
filesystem:
|
|
||||||
required: [show_smart, fs_paths]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
show_smart:
|
|
||||||
description: show the smart deamon indicator
|
|
||||||
type: boolean
|
|
||||||
fs_paths:
|
|
||||||
description: the filesystem paths for which usage should be shown
|
|
||||||
type: array
|
|
||||||
minItems: 1
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
required: [name, path]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
path:
|
|
||||||
type: string
|
|
||||||
|
|
||||||
graphics:
|
|
||||||
required: [show_temp, show_clock, show_gpu_util, show_mem_util, show_vid_util]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
dev_power:
|
|
||||||
description: the sysfs path to the graphics card power indicator
|
|
||||||
type: string
|
|
||||||
show_temp:
|
|
||||||
description: show the GPU temp
|
|
||||||
type: boolean
|
|
||||||
show_clock:
|
|
||||||
description: show the GPU clock speeds
|
|
||||||
type: boolean
|
|
||||||
show_gpu_util:
|
|
||||||
description: show the GPU utilization plot
|
|
||||||
type: boolean
|
|
||||||
show_mem_util:
|
|
||||||
description: show the GPU memory utilization plot
|
|
||||||
type: boolean
|
|
||||||
show_vid_util:
|
|
||||||
description: show the GPU video utilization plot
|
|
||||||
type: boolean
|
|
||||||
|
|
||||||
memory:
|
|
||||||
required: [show_stats, show_plot, table_rows]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
show_stats:
|
|
||||||
description: show memory stats/dial
|
|
||||||
type: boolean
|
|
||||||
show_swap:
|
|
||||||
description: show swap dial
|
|
||||||
type: boolean
|
|
||||||
show_plot:
|
|
||||||
description: show the RAM utilization plot
|
|
||||||
type: boolean
|
|
||||||
table_rows: &table
|
|
||||||
descrition: the number of rows in the table (0 for no table)
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
|
|
||||||
power:
|
|
||||||
required: [battery, rapl_specs]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
battery:
|
|
||||||
description: the battery device to use (or blank if none)
|
|
||||||
type: string
|
|
||||||
rapl_specs:
|
|
||||||
description: the Intel RAPL specs for which plots should be made
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
required: [name, address]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
address:
|
|
||||||
type: string
|
|
||||||
|
|
||||||
processor:
|
|
||||||
required: [core_rows, show_stats, show_plot, table_rows]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
core_rows:
|
|
||||||
description: the number of rows over which to show discrete cores
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
core_padding:
|
|
||||||
description: horizontal padding to apply to the core layout
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
show_stats:
|
|
||||||
description: show frequency/HWP stats
|
|
||||||
type: boolean
|
|
||||||
show_plot:
|
|
||||||
description: show CPU utilization plot
|
|
||||||
type: boolean
|
|
||||||
table_rows: *table
|
|
||||||
|
|
||||||
readwrite:
|
|
||||||
required: [devices]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
devices:
|
|
||||||
description: the devices to include in I/O summations
|
|
||||||
type: array
|
|
||||||
minItems: 1
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
|
|
||||||
layout:
|
|
||||||
required: [anchor, panels]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
anchor:
|
|
||||||
description: the coordinates of the upper-left corner to anchor the app
|
|
||||||
type: array
|
|
||||||
minItems: 2
|
|
||||||
maxItems: 2
|
|
||||||
items:
|
|
||||||
type: integer
|
|
||||||
panels:
|
|
||||||
description: either a panel (object) or padding between panels (int)
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
anyOf:
|
|
||||||
- type: integer
|
|
||||||
minimum: 0
|
|
||||||
- type: object
|
|
||||||
description: layout for a single panel
|
|
||||||
required: [columns, margins]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
margins:
|
|
||||||
type: array
|
|
||||||
minItems: 2
|
|
||||||
maxItems: 2
|
|
||||||
items:
|
|
||||||
type: integer
|
|
||||||
columns:
|
|
||||||
description: |
|
|
||||||
either the columns in this panel (object) or padding
|
|
||||||
between columns (int)
|
|
||||||
type: array
|
|
||||||
minItems: 1
|
|
||||||
items:
|
|
||||||
anyOf:
|
|
||||||
- type: integer
|
|
||||||
minimum: 0
|
|
||||||
- type: object
|
|
||||||
required: [blocks, width]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
width:
|
|
||||||
description: the width of all modules in this column
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
blocks:
|
|
||||||
description: |
|
|
||||||
either a module name (string) or padding (int)
|
|
||||||
type: array
|
|
||||||
minItems: 1
|
|
||||||
items:
|
|
||||||
anyOf:
|
|
||||||
- type: integer
|
|
||||||
minimum: 0
|
|
||||||
- type: string
|
|
||||||
pattern:
|
|
||||||
"^system|graphics|processor|readwrite|\
|
|
||||||
network|pacman|filesystem|power|memory$"
|
|
||||||
|
|
||||||
theme:
|
|
||||||
required: [font, geometry, patterns]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
font:
|
|
||||||
required: [family, sizes]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
family:
|
|
||||||
type: string
|
|
||||||
sizes:
|
|
||||||
required: [normal, plot_label, table, header]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
normal: &font_size
|
|
||||||
type: integer
|
|
||||||
minimum: 5
|
|
||||||
plot_label: *font_size
|
|
||||||
table: *font_size
|
|
||||||
header: *font_size
|
|
||||||
|
|
||||||
geometry:
|
|
||||||
required: [plot, table]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
plot:
|
|
||||||
required: [seconds, ticks, height, spacing]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
spacing:
|
|
||||||
description: the spacing between the label and the plot
|
|
||||||
type: integer
|
|
||||||
minimum: 10
|
|
||||||
height:
|
|
||||||
description: the height of the plot
|
|
||||||
type: integer
|
|
||||||
minimum: 10
|
|
||||||
seconds:
|
|
||||||
description: the number of seconds on each timeseries plot
|
|
||||||
type: integer
|
|
||||||
minimum: 30
|
|
||||||
ticks:
|
|
||||||
description: the number of ticks on the x/y axes
|
|
||||||
type: array
|
|
||||||
minItems: 2
|
|
||||||
maxItems: 2
|
|
||||||
items:
|
|
||||||
type: integer
|
|
||||||
minimum: 2
|
|
||||||
table:
|
|
||||||
required: [name_chars, padding, header_padding]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
name_chars:
|
|
||||||
description: |
|
|
||||||
the length to which the name column should be trimmed (if any)
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
padding:
|
|
||||||
description: the x/y padding around the table
|
|
||||||
type: array
|
|
||||||
minItems: 2
|
|
||||||
maxItems: 2
|
|
||||||
items:
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
header_padding:
|
|
||||||
description: the padding beneath the column headers
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
row_spacing:
|
|
||||||
description: the distance between the center of each row
|
|
||||||
type: integer
|
|
||||||
minimum: 10
|
|
||||||
|
|
||||||
header:
|
|
||||||
required: [underline_offset, padding]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
underline_offset:
|
|
||||||
description: the offset of the underline (from top of header)
|
|
||||||
type: integer
|
|
||||||
minimum: 10
|
|
||||||
padding:
|
|
||||||
description: the padding beneath the underline
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
|
|
||||||
patterns:
|
|
||||||
required: [header, panel, text, border, plot, indicator]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
header: &pattern
|
|
||||||
oneOf:
|
|
||||||
- type: integer
|
|
||||||
- type: object
|
|
||||||
oneOf:
|
|
||||||
- required: [color, alpha]
|
|
||||||
- required: [gradient]
|
|
||||||
- required: [gradient_alpha]
|
|
||||||
properties:
|
|
||||||
color:
|
|
||||||
type: integer
|
|
||||||
maximum: 0xffffff
|
|
||||||
alpha: &alpha
|
|
||||||
type: number
|
|
||||||
minimum: 0
|
|
||||||
maximum: 1
|
|
||||||
gradient:
|
|
||||||
type: array
|
|
||||||
minItems: 2
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
required: [stop, color]
|
|
||||||
additionalProperties: false
|
|
||||||
properties: &gradient
|
|
||||||
stop:
|
|
||||||
type: number
|
|
||||||
minimum: 0
|
|
||||||
maximum: 1
|
|
||||||
color:
|
|
||||||
type: integer
|
|
||||||
maximum: 0xffffff
|
|
||||||
|
|
||||||
gradient_alpha:
|
|
||||||
type: array
|
|
||||||
minItems: 2
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
required: [stop, color, alpha]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
<<: *gradient
|
|
||||||
alpha: *alpha
|
|
||||||
|
|
||||||
panel:
|
|
||||||
required: [bg]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
bg: *pattern
|
|
||||||
|
|
||||||
text:
|
|
||||||
required: [active, inactive, critical]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
active: *pattern
|
|
||||||
inactive: *pattern
|
|
||||||
critical: *pattern
|
|
||||||
|
|
||||||
border: *pattern
|
|
||||||
|
|
||||||
plot:
|
|
||||||
required: [grid, outline, data]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
grid: *pattern
|
|
||||||
outline: *pattern
|
|
||||||
data:
|
|
||||||
required: [border, fill]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
border: *pattern
|
|
||||||
fill: *pattern
|
|
||||||
|
|
||||||
indicator:
|
|
||||||
required: [bg, fg]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
bg: *pattern
|
|
||||||
fg:
|
|
||||||
required: [active, critical]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
active: *pattern
|
|
||||||
critical: *pattern
|
|
|
@ -54,6 +54,7 @@ package.cpath = conky_dir..'lib/lib/lua/5.4/?.so;'
|
||||||
|
|
||||||
local yaml = require 'lyaml'
|
local yaml = require 'lyaml'
|
||||||
local i_o = require 'i_o'
|
local i_o = require 'i_o'
|
||||||
|
local validate = require 'validate'
|
||||||
|
|
||||||
local config_path = '/tmp/conky.yml'
|
local config_path = '/tmp/conky.yml'
|
||||||
|
|
||||||
|
@ -68,7 +69,9 @@ local find_valid_config = function(paths)
|
||||||
local rc = try_read_config(path)
|
local rc = try_read_config(path)
|
||||||
if rc == 0 then
|
if rc == 0 then
|
||||||
i_o.infof('Using config at %s', path)
|
i_o.infof('Using config at %s', path)
|
||||||
return yaml.load(i_o.read_file(config_path))
|
local config = yaml.load(i_o.read_file(config_path))
|
||||||
|
validate.validate_config(config)
|
||||||
|
return config
|
||||||
else
|
else
|
||||||
i_o.warnf('could not read %s; trying next', path)
|
i_o.warnf('could not read %s; trying next', path)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
# small script to log battery usage in txt file for conky to parse
|
||||||
|
# lovingly stolen from battery-stats by petter reinholdtsen
|
||||||
|
|
||||||
|
DST_FILE="$XDG_CACHE_HOME/batmon.log"
|
||||||
|
SYS_BAT_PATH="/sys/class/power_supply/BAT0"
|
||||||
|
BAT_CAP_PATH="$SYS_BAT_PATH/charge_now"
|
||||||
|
BAT_CAP_FULL_PATH="$SYS_BAT_PATH/charge_full"
|
||||||
|
MAX_ENTRIES=1440 # number of minutes in day
|
||||||
|
|
||||||
|
read_bat_percent() {
|
||||||
|
timestamp=$(date +%s)
|
||||||
|
charge_now=$(cat "$BAT_CAP_PATH")
|
||||||
|
charge_full=$(cat "$BAT_CAP_FULL_PATH")
|
||||||
|
percent=$(echo "100 * $charge_now / $charge_full" | bc)
|
||||||
|
echo $timestamp $percent >> "$DST_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_bat_percent
|
||||||
|
# truncate to most recent entries by max_entries
|
||||||
|
tail -n $MAX_ENTRIES "$DST_FILE" > /tmp/batmon.tmp.log && mv /tmp/batmon.tmp.log "$DST_FILE"
|
|
@ -439,6 +439,14 @@ return function(config)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
M.make_blank_dial = function(x, y, radius, thickness, threshold)
|
||||||
|
return dial.make(
|
||||||
|
geom.make_arc(x, y, radius, DIAL_THETA0, DIAL_THETA1),
|
||||||
|
arc.config(style.line(thickness, CAP_BUTT), patterns.indicator.bg),
|
||||||
|
threshold_indicator(threshold)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
M.make_dial = function(x, y, radius, thickness, threshold, _format, pre_function)
|
M.make_dial = function(x, y, radius, thickness, threshold, _format, pre_function)
|
||||||
return {
|
return {
|
||||||
dial = dial.make(
|
dial = dial.make(
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
local pure = require 'pure'
|
local pure = require 'pure'
|
||||||
local i_o = require 'i_o'
|
local i_o = require 'i_o'
|
||||||
|
|
||||||
return function(update_freq, config, common, width, point)
|
return function(update_freq, config, common, width, point)
|
||||||
local NA = 'N/A'
|
local NA = 'N/A'
|
||||||
local NVIDIA_EXE = 'nvidia-settings'
|
local NVIDIA_EXE = 'nvidia-smi'
|
||||||
|
|
||||||
local geo = config.geometry
|
local geo = config.geometry
|
||||||
local sep_spacing = geo.sep_spacing
|
local sep_spacing = geo.sep_spacing
|
||||||
|
@ -20,55 +20,97 @@ return function(update_freq, config, common, width, point)
|
||||||
|
|
||||||
i_o.assert_exe_exists(NVIDIA_EXE)
|
i_o.assert_exe_exists(NVIDIA_EXE)
|
||||||
|
|
||||||
-- vars to process the nv settings glob
|
local mk_query_cmd = function(props)
|
||||||
--
|
return __string_format(
|
||||||
-- glob will be of the form:
|
'%s --query-gpu=%s --format=csv,noheader,nounits',
|
||||||
-- <used_mem>
|
NVIDIA_EXE,
|
||||||
-- <total_mem>
|
pure.collapse(props, ',')
|
||||||
-- <temp>
|
)
|
||||||
-- <gpu_freq>,<mem_freq>
|
end
|
||||||
-- graphics=<gpu_util>, memory=<mem_util>, video=<vid_util>, PCIe=<pci_util>
|
|
||||||
local NV_QUERY = NVIDIA_EXE..
|
|
||||||
' -t'..
|
|
||||||
' -q UsedDedicatedGPUmemory'..
|
|
||||||
' -q TotalDedicatedGPUmemory'..
|
|
||||||
' -q ThermalSensorReading'..
|
|
||||||
' -q [gpu:0]/GPUCurrentClockFreqs'..
|
|
||||||
' -q [gpu:0]/GPUutilization'..
|
|
||||||
' 2>/dev/null'
|
|
||||||
|
|
||||||
local NV_REGEX = '(%d+)\n'..
|
-- TODO also use encoder for video util?
|
||||||
'(%d+)\n'..
|
-- TODO add video clock speed?
|
||||||
'(%d+)\n'..
|
local query_stats = mk_query_cmd(
|
||||||
'(%d+),(%d+)\n'..
|
{
|
||||||
'graphics=(%d+), memory=%d+, video=(%d+), PCIe=%d+\n'
|
'memory.used',
|
||||||
|
'temperature.gpu',
|
||||||
|
'clocks.gr',
|
||||||
|
'clocks.mem',
|
||||||
|
'utilization.gpu',
|
||||||
|
'utilization.decoder'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
local NV_REGEX = '(%d+), (%d+), (%d+), (%d+), (%d+), (%d+)'
|
||||||
|
|
||||||
local mod_state = {
|
local mod_state = {
|
||||||
error = false,
|
error = false,
|
||||||
used_memory = 0,
|
|
||||||
total_memory = 0,
|
total_memory = 0,
|
||||||
|
gpu_frequency = 0,
|
||||||
|
memory_frequency = 0,
|
||||||
|
used_memory = 0,
|
||||||
temp_reading = 0,
|
temp_reading = 0,
|
||||||
gpu_utilization = 0,
|
gpu_utilization = 0,
|
||||||
vid_utilization = 0
|
vid_utilization = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local sleep_token = 0
|
||||||
|
local sleep_limit = config.power.cycle_seconds
|
||||||
|
local gpu_idle_freq_limit = config.power.freq_limit
|
||||||
|
|
||||||
|
-- TODO ensure this file exists
|
||||||
|
local runtime_status_file = config.dev_power..'/runtime_status'
|
||||||
|
|
||||||
|
local want_nvidia_query = config.show_temp or config.show_clock
|
||||||
|
or config.gpu_util or config.mem_util or config.vid_util
|
||||||
|
|
||||||
local update_state = function()
|
local update_state = function()
|
||||||
if i_o.read_file(config.dev_power, nil, '*l') == 'on' then
|
local is_active = i_o.read_file(runtime_status_file, nil, '*l') == 'active'
|
||||||
local nvidia_settings_glob = i_o.execute_cmd(NV_QUERY)
|
-- this will make the nvidia-smi query fire only so often when the clock
|
||||||
|
-- is below a certain threshold. This is necessary to get the GPU to
|
||||||
|
-- suspend when nothing is 'using' it, at the cost of lowering the
|
||||||
|
-- response time for when it eventually is used again. Maybe won't
|
||||||
|
-- matter that much since the jobs that use the GPU tend to be long
|
||||||
|
-- anyways, so a few seconds won't hurt. Furthermore, there are ways to
|
||||||
|
-- wake this up manually by detecting certain processes the likely will
|
||||||
|
-- use the GPU (ffmpeg and friends) or detecting processes that are
|
||||||
|
-- holding /dev/nvidia* files (which isn't foolproof but it will capture
|
||||||
|
-- most events)
|
||||||
|
if is_active and
|
||||||
|
mod_state.gpu_frequency > 0 and
|
||||||
|
mod_state.gpu_frequency < gpu_idle_freq_limit then
|
||||||
|
if sleep_token < sleep_limit - 1 then
|
||||||
|
sleep_token = sleep_token + 1
|
||||||
|
else
|
||||||
|
sleep_token = 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
sleep_token = 0
|
||||||
|
end
|
||||||
|
if is_active and want_nvidia_query and sleep_token == 0 then
|
||||||
|
local nvidia_settings_glob = i_o.execute_cmd(query_stats)
|
||||||
if nvidia_settings_glob == nil then
|
if nvidia_settings_glob == nil then
|
||||||
mod_state.error = 'Error'
|
mod_state.error = 'Error'
|
||||||
else
|
else
|
||||||
mod_state.used_memory,
|
mod_state.used_memory,
|
||||||
mod_state.total_memory,
|
|
||||||
mod_state.temp_reading,
|
mod_state.temp_reading,
|
||||||
mod_state.gpu_frequency,
|
mod_state.gpu_frequency,
|
||||||
mod_state.memory_frequency,
|
mod_state.memory_frequency,
|
||||||
mod_state.gpu_utilization,
|
mod_state.gpu_utilization,
|
||||||
mod_state.vid_utilization
|
mod_state.vid_utilization
|
||||||
= __string_match(nvidia_settings_glob, NV_REGEX)
|
= __string_match(nvidia_settings_glob, NV_REGEX)
|
||||||
|
mod_state.gpu_frequency = __tonumber(mod_state.gpu_frequency)
|
||||||
mod_state.error = false
|
mod_state.error = false
|
||||||
end
|
end
|
||||||
|
-- query total memory if we need it (do this here so we don't turn on
|
||||||
|
-- the gpu every time we start conky)
|
||||||
|
if mod_state.total_memory == 0 and config.show_mem_util then
|
||||||
|
mod_state.total_memory = __tonumber(i_o.execute_cmd(mk_query_cmd({'memory.total'})))
|
||||||
|
end
|
||||||
|
elseif is_active then
|
||||||
|
mod_state.error = false
|
||||||
else
|
else
|
||||||
|
mod_state.gpu_frequency = 0
|
||||||
mod_state.error = 'Off'
|
mod_state.error = 'Off'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,6 +26,8 @@ return function(update_freq, config, common, width, point)
|
||||||
|
|
||||||
local mk_rate_plot = function(label, address, y)
|
local mk_rate_plot = function(label, address, y)
|
||||||
local read_joules = sys.intel_powercap_reader(address)
|
local read_joules = sys.intel_powercap_reader(address)
|
||||||
|
local read_joules0 = function() return read_joules() or 0 end
|
||||||
|
local read_joulesNA = function() return read_joules() or 'N/A' end
|
||||||
local obj = common.make_rate_timeseries(
|
local obj = common.make_rate_timeseries(
|
||||||
point.x,
|
point.x,
|
||||||
y,
|
y,
|
||||||
|
@ -38,12 +40,12 @@ return function(update_freq, config, common, width, point)
|
||||||
label,
|
label,
|
||||||
0,
|
0,
|
||||||
update_freq,
|
update_freq,
|
||||||
read_joules()
|
read_joules0()
|
||||||
)
|
)
|
||||||
return common.mk_acc(
|
return common.mk_acc(
|
||||||
width,
|
width,
|
||||||
plot_height + plot_sec_break,
|
plot_height + plot_sec_break,
|
||||||
function(_) common.update_rate_timeseries(obj, read_joules()) end,
|
function(_) common.update_rate_timeseries(obj, read_joulesNA()) end,
|
||||||
mk_static(obj),
|
mk_static(obj),
|
||||||
mk_dynamic(obj)
|
mk_dynamic(obj)
|
||||||
)
|
)
|
||||||
|
@ -65,8 +67,11 @@ return function(update_freq, config, common, width, point)
|
||||||
local mk_bat = function(y)
|
local mk_bat = function(y)
|
||||||
local _read_battery_power = sys.battery_power_reader(config.battery)
|
local _read_battery_power = sys.battery_power_reader(config.battery)
|
||||||
|
|
||||||
|
-- TODO this is actually telling the plot to say it is on AC when the
|
||||||
|
-- battery status can't be read (in which case it should say ERROR or
|
||||||
|
-- something)
|
||||||
local read_battery_power = function(is_using_ac)
|
local read_battery_power = function(is_using_ac)
|
||||||
return is_using_ac and 0 or _read_battery_power()
|
return is_using_ac and 0 or (_read_battery_power() or 0)
|
||||||
end
|
end
|
||||||
local read_bat_status = sys.battery_status_reader(config.battery)
|
local read_bat_status = sys.battery_status_reader(config.battery)
|
||||||
local obj = common.make_tagged_scaled_timeseries(
|
local obj = common.make_tagged_scaled_timeseries(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
local dial = require 'dial'
|
||||||
local compound_dial = require 'compound_dial'
|
local compound_dial = require 'compound_dial'
|
||||||
local text_table = require 'text_table'
|
local text_table = require 'text_table'
|
||||||
local i_o = require 'i_o'
|
local i_o = require 'i_o'
|
||||||
|
@ -5,6 +6,7 @@ local cpu = require 'sys'
|
||||||
local pure = require 'pure'
|
local pure = require 'pure'
|
||||||
|
|
||||||
local __math_floor = math.floor
|
local __math_floor = math.floor
|
||||||
|
local __string_format = string.format
|
||||||
|
|
||||||
return function(update_freq, main_state, config, common, width, point)
|
return function(update_freq, main_state, config, common, width, point)
|
||||||
local dial_inner_radius = 30
|
local dial_inner_radius = 30
|
||||||
|
@ -21,44 +23,51 @@ return function(update_freq, main_state, config, common, width, point)
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- processor state
|
-- processor state
|
||||||
|
|
||||||
local mod_state = cpu.read_cpu_loads(cpu.init_cpu_loads())
|
local topology = cpu.get_core_topology()
|
||||||
|
local mod_state = cpu.read_cpu_loads(cpu.init_cpu_loads(topology))
|
||||||
|
local ncpus = cpu.get_cpu_number(topology)
|
||||||
|
|
||||||
local update_state = function()
|
local update_state = function()
|
||||||
mod_state = cpu.read_cpu_loads(mod_state)
|
mod_state = cpu.read_cpu_loads(mod_state)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- test that the core groups we want are actually displayable
|
||||||
|
for _, g in pairs(config.core_groups) do
|
||||||
|
i_o.assertf(
|
||||||
|
topology[g.threads] ~= nil,
|
||||||
|
'processor: no group with %s threads', 1
|
||||||
|
)
|
||||||
|
local ncores = #topology[g.threads]
|
||||||
|
i_o.assertf(
|
||||||
|
math.fmod(ncores, g.rows) == 0,
|
||||||
|
'processor: could not evenly divide %s cores into %s rows', ncores, g.rows
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- cores (loads and temps)
|
-- cores (loads and temps)
|
||||||
|
|
||||||
local ncpus = cpu.get_cpu_number()
|
local create_core = function(core_cols, y, nthreads, padding, c)
|
||||||
local ncores = cpu.get_core_number()
|
|
||||||
local nthreads = ncpus / ncores
|
|
||||||
|
|
||||||
local show_cores = false
|
|
||||||
|
|
||||||
if config.core_rows > 0 then
|
|
||||||
if math.fmod(ncores, config.core_rows) == 0 then
|
|
||||||
show_cores = true
|
|
||||||
else
|
|
||||||
i_o.warnf(
|
|
||||||
'could not evenly distribute %i cores over %i rows; disabling',
|
|
||||||
ncores,
|
|
||||||
config.core_rows
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local create_core = function(core_cols, y, c)
|
|
||||||
local dial_x = point.x +
|
local dial_x = point.x +
|
||||||
(core_cols == 1
|
(core_cols == 1
|
||||||
and (width / 2)
|
and (width / 2)
|
||||||
or (config.core_padding + dial_outer_radius +
|
or (padding + dial_outer_radius +
|
||||||
(width - 2 * (dial_outer_radius + config.core_padding))
|
(width - 2 * (dial_outer_radius + padding))
|
||||||
* math.fmod(c - 1, core_cols) / (core_cols - 1)))
|
* math.fmod(c - 1, core_cols) / (core_cols - 1)))
|
||||||
local dial_y = y + dial_outer_radius +
|
local dial_y = y + dial_outer_radius +
|
||||||
(2 * dial_outer_radius + dial_y_spacing)
|
(2 * dial_outer_radius + dial_y_spacing)
|
||||||
* math.floor((c - 1) / core_cols)
|
* math.floor((c - 1) / core_cols)
|
||||||
return {
|
local loads
|
||||||
|
if nthreads == 1 then
|
||||||
|
local single_thickness = dial_outer_radius - dial_inner_radius
|
||||||
|
loads = common.make_blank_dial(
|
||||||
|
dial_x,
|
||||||
|
dial_y,
|
||||||
|
dial_outer_radius - single_thickness / 2,
|
||||||
|
single_thickness,
|
||||||
|
80
|
||||||
|
)
|
||||||
|
else
|
||||||
loads = common.make_compound_dial(
|
loads = common.make_compound_dial(
|
||||||
dial_x,
|
dial_x,
|
||||||
dial_y,
|
dial_y,
|
||||||
|
@ -67,7 +76,10 @@ return function(update_freq, main_state, config, common, width, point)
|
||||||
dial_thickness,
|
dial_thickness,
|
||||||
80,
|
80,
|
||||||
nthreads
|
nthreads
|
||||||
),
|
)
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
loads = loads,
|
||||||
coretemp = common.make_text_circle(
|
coretemp = common.make_text_circle(
|
||||||
dial_x,
|
dial_x,
|
||||||
dial_y,
|
dial_y,
|
||||||
|
@ -79,44 +91,62 @@ return function(update_freq, main_state, config, common, width, point)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local mk_cores = function(y)
|
local read_load = function(core_topology, phy_core_id, thread_id)
|
||||||
local core_cols = ncores / config.core_rows
|
local i = core_topology[phy_core_id].cpus[thread_id].lgl_cpu_id
|
||||||
local cores = pure.map_n(pure.partial(create_core, core_cols, y), ncores)
|
return mod_state[i].percent_active * 100
|
||||||
local coretemp_paths = cpu.get_coretemp_paths()
|
end
|
||||||
if #coretemp_paths ~= ncores then
|
|
||||||
i_o.warnf('could not find all coretemp paths')
|
local get_load_functions = function(cores, nthreads, core_topology)
|
||||||
end
|
if nthreads == 1 then
|
||||||
local update_coretemps = function()
|
local update = function(c)
|
||||||
for conky_core_idx, path in pairs(coretemp_paths) do
|
dial.set(cores[c].loads, read_load(core_topology, c, 1))
|
||||||
local temp = __math_floor(0.001 * i_o.read_file(path, nil, '*n'))
|
|
||||||
common.text_circle_set(cores[conky_core_idx].coretemp, temp)
|
|
||||||
end
|
end
|
||||||
|
return update, dial.draw_static, dial.draw_dynamic
|
||||||
|
else
|
||||||
|
local update = function(c)
|
||||||
|
for t = 1, nthreads do
|
||||||
|
compound_dial.set(cores[c].loads, t, read_load(core_topology, c, t))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return update, compound_dial.draw_static, compound_dial.draw_dynamic
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local mk_core_group = function(group_config, y)
|
||||||
|
local nthreads = group_config.threads
|
||||||
|
local core_topology = topology[nthreads]
|
||||||
|
local ncores = #core_topology
|
||||||
|
local core_cols = ncores / group_config.rows
|
||||||
|
local _create_core = pure.partial(
|
||||||
|
create_core, core_cols, y, nthreads, group_config.padding
|
||||||
|
)
|
||||||
|
local cores = pure.map_n(_create_core, ncores)
|
||||||
|
local update_loads, draw_static_loads, draw_dynamic_loads =
|
||||||
|
get_load_functions(cores, nthreads, core_topology)
|
||||||
local update = function()
|
local update = function()
|
||||||
for _, load_data in pairs(mod_state) do
|
for c = 1, ncores do
|
||||||
compound_dial.set(
|
local temp = __math_floor(
|
||||||
cores[load_data.conky_core_idx].loads,
|
0.001 * i_o.read_file(core_topology[c].coretemp_path, nil, '*n')
|
||||||
load_data.conky_thread_id,
|
|
||||||
load_data.percent_active * 100
|
|
||||||
)
|
)
|
||||||
|
common.text_circle_set(cores[c].coretemp, temp)
|
||||||
|
update_loads(c)
|
||||||
end
|
end
|
||||||
update_coretemps()
|
|
||||||
end
|
end
|
||||||
local static = function(cr)
|
local static = function(cr)
|
||||||
for i = 1, #cores do
|
for i = 1, ncores do
|
||||||
common.text_circle_draw_static(cores[i].coretemp, cr)
|
common.text_circle_draw_static(cores[i].coretemp, cr)
|
||||||
compound_dial.draw_static(cores[i].loads, cr)
|
draw_static_loads(cores[i].loads, cr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local dynamic = function(cr)
|
local dynamic = function(cr)
|
||||||
for i = 1, #cores do
|
for i = 1, ncores do
|
||||||
common.text_circle_draw_dynamic(cores[i].coretemp, cr)
|
common.text_circle_draw_dynamic(cores[i].coretemp, cr)
|
||||||
compound_dial.draw_dynamic(cores[i].loads, cr)
|
draw_dynamic_loads(cores[i].loads, cr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return common.mk_acc(
|
return common.mk_acc(
|
||||||
width,
|
width,
|
||||||
(dial_outer_radius * 2 + dial_y_spacing) * config.core_rows
|
(dial_outer_radius * 2 + dial_y_spacing) * group_config.rows
|
||||||
- dial_y_spacing,
|
- dial_y_spacing,
|
||||||
update,
|
update,
|
||||||
static,
|
static,
|
||||||
|
@ -128,13 +158,24 @@ return function(update_freq, main_state, config, common, width, point)
|
||||||
-- HWP status
|
-- HWP status
|
||||||
|
|
||||||
local mk_hwp_freq = function(y)
|
local mk_hwp_freq = function(y)
|
||||||
local hwp_paths = cpu.get_hwp_paths()
|
local hwp_paths = cpu.get_hwp_paths(topology)
|
||||||
|
local freq_labels
|
||||||
|
local cpu_group_map = cpu.topology_to_cpu_map(topology)
|
||||||
|
local ngroups = pure.length(topology)
|
||||||
|
local format_label = function(group_id)
|
||||||
|
return __string_format('Ave Freq (%i)', group_id)
|
||||||
|
end
|
||||||
|
if ngroups == 1 then
|
||||||
|
freq_labels = {'Ave Freq'}
|
||||||
|
else
|
||||||
|
freq_labels = pure.map_n(format_label, ngroups)
|
||||||
|
end
|
||||||
local cpu_status = common.make_text_rows(
|
local cpu_status = common.make_text_rows(
|
||||||
point.x,
|
point.x,
|
||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
text_spacing,
|
text_spacing,
|
||||||
{'HWP Preference', 'Ave Freq'}
|
pure.concat({'HWP Preference'}, freq_labels)
|
||||||
)
|
)
|
||||||
local update = function()
|
local update = function()
|
||||||
-- For some reason this call is slow (querying anything with pstate in
|
-- For some reason this call is slow (querying anything with pstate in
|
||||||
|
@ -143,13 +184,18 @@ return function(update_freq, main_state, config, common, width, point)
|
||||||
if main_state.trigger10 == 0 then
|
if main_state.trigger10 == 0 then
|
||||||
common.text_rows_set(cpu_status, 1, cpu.read_hwp(hwp_paths))
|
common.text_rows_set(cpu_status, 1, cpu.read_hwp(hwp_paths))
|
||||||
end
|
end
|
||||||
common.text_rows_set(cpu_status, 2, cpu.read_freq())
|
local ave_freqs = cpu.read_ave_freqs(topology, cpu_group_map)
|
||||||
|
local i = 2
|
||||||
|
for group_id, _ in pairs(topology) do
|
||||||
|
common.text_rows_set(cpu_status, i, ave_freqs[group_id])
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local static = pure.partial(common.text_rows_draw_static, cpu_status)
|
local static = pure.partial(common.text_rows_draw_static, cpu_status)
|
||||||
local dynamic = pure.partial(common.text_rows_draw_dynamic, cpu_status)
|
local dynamic = pure.partial(common.text_rows_draw_dynamic, cpu_status)
|
||||||
return common.mk_acc(
|
return common.mk_acc(
|
||||||
width,
|
width,
|
||||||
text_spacing,
|
text_spacing * ngroups,
|
||||||
update,
|
update,
|
||||||
static,
|
static,
|
||||||
dynamic
|
dynamic
|
||||||
|
@ -172,7 +218,7 @@ return function(update_freq, main_state, config, common, width, point)
|
||||||
)
|
)
|
||||||
local update = function()
|
local update = function()
|
||||||
local s = 0
|
local s = 0
|
||||||
for i = 1, #mod_state do
|
for i = 1, ncpus do
|
||||||
s = s + mod_state[i].percent_active
|
s = s + mod_state[i].percent_active
|
||||||
end
|
end
|
||||||
common.tagged_percent_timeseries_set(total_load, s / ncpus * 100)
|
common.tagged_percent_timeseries_set(total_load, s / ncpus * 100)
|
||||||
|
@ -225,15 +271,19 @@ return function(update_freq, main_state, config, common, width, point)
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- main functions
|
-- main functions
|
||||||
|
|
||||||
|
local core_group_section = function (g)
|
||||||
|
return {pure.partial(mk_core_group, g), true, text_spacing}
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
header = 'PROCESSOR',
|
header = 'PROCESSOR',
|
||||||
point = point,
|
point = point,
|
||||||
width = width,
|
width = width,
|
||||||
set_state = update_state,
|
set_state = update_state,
|
||||||
top = {
|
top = pure.concat(
|
||||||
{mk_cores, show_cores, text_spacing},
|
pure.map(core_group_section, config.core_groups),
|
||||||
{mk_hwp_freq, config.show_stats, sep_spacing},
|
{{mk_hwp_freq, config.show_stats, sep_spacing}}
|
||||||
},
|
),
|
||||||
common.mk_section(
|
common.mk_section(
|
||||||
sep_spacing,
|
sep_spacing,
|
||||||
{mk_load_plot, config.show_plot, geo.table.sec_break},
|
{mk_load_plot, config.show_plot, geo.table.sec_break},
|
||||||
|
|
86
src/pure.lua
86
src/pure.lua
|
@ -4,6 +4,7 @@ local err = require 'err'
|
||||||
|
|
||||||
local __math_floor = math.floor
|
local __math_floor = math.floor
|
||||||
local __table_insert = table.insert
|
local __table_insert = table.insert
|
||||||
|
local __string_format = string.format
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- zippy functions
|
-- zippy functions
|
||||||
|
@ -50,9 +51,52 @@ M.reduce = function(f, init, seq)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
M.member = function(x, seq)
|
||||||
|
return M.reduce(
|
||||||
|
function(acc, next) return acc or next == x end,
|
||||||
|
false,
|
||||||
|
M.values(seq)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.collapse = function(seq, delim)
|
||||||
|
if #seq == 0 then
|
||||||
|
return ''
|
||||||
|
elseif #seq == 1 then
|
||||||
|
return seq[1]
|
||||||
|
else
|
||||||
|
local init, rest = M.head(seq)
|
||||||
|
return M.reduce(
|
||||||
|
function(acc, next) return __string_format('%s%s%s', acc, delim, next) end,
|
||||||
|
init,
|
||||||
|
rest
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- mappy functions
|
-- mappy functions
|
||||||
|
|
||||||
|
M.keys = function(seq)
|
||||||
|
local r = {}
|
||||||
|
local n = 1
|
||||||
|
for k, _ in pairs(seq) do
|
||||||
|
r[n] = k
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.values = function(seq)
|
||||||
|
local r = {}
|
||||||
|
local n = 1
|
||||||
|
for _, v in pairs(seq) do
|
||||||
|
r[n] = v
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
M.map = function(f, seq, ...)
|
M.map = function(f, seq, ...)
|
||||||
local r = {}
|
local r = {}
|
||||||
for i = 1, #seq do
|
for i = 1, #seq do
|
||||||
|
@ -136,6 +180,22 @@ end
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- random list things
|
-- random list things
|
||||||
|
|
||||||
|
M.length = function(tbl)
|
||||||
|
local n = 0
|
||||||
|
for _, _ in pairs(tbl) do
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
M.head = function(seq)
|
||||||
|
local r = {}
|
||||||
|
for i = 2, #seq do
|
||||||
|
r[i - 1] = seq[i]
|
||||||
|
end
|
||||||
|
return seq[1], r
|
||||||
|
end
|
||||||
|
|
||||||
-- a stupid but composable function
|
-- a stupid but composable function
|
||||||
M.get = function(key, tbl)
|
M.get = function(key, tbl)
|
||||||
return tbl[key]
|
return tbl[key]
|
||||||
|
@ -189,6 +249,32 @@ M.flatten = function(xs)
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
|
M.group_with = function(keyfun, valfun, seq)
|
||||||
|
local f = function(acc, next)
|
||||||
|
local k = keyfun(next)
|
||||||
|
local v = valfun(next)
|
||||||
|
if acc[k] == nil then
|
||||||
|
acc[k] = {v}
|
||||||
|
else
|
||||||
|
acc[k][#acc[k] + 1] = v
|
||||||
|
end
|
||||||
|
return acc
|
||||||
|
end
|
||||||
|
return M.reduce(f, {}, seq)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.group_by = function(k, seq)
|
||||||
|
local f = function(acc, next)
|
||||||
|
if acc[k] == nil then
|
||||||
|
acc[k] = {next}
|
||||||
|
else
|
||||||
|
acc[k][#acc[k]] = next
|
||||||
|
end
|
||||||
|
return acc
|
||||||
|
end
|
||||||
|
return M.reduce(f, {}, seq)
|
||||||
|
end
|
||||||
|
|
||||||
M.concat = function(...)
|
M.concat = function(...)
|
||||||
return M.flatten({...})
|
return M.flatten({...})
|
||||||
end
|
end
|
||||||
|
|
234
src/sys.lua
234
src/sys.lua
|
@ -13,7 +13,12 @@ local dirname = function(s)
|
||||||
end
|
end
|
||||||
|
|
||||||
local read_micro = function(path)
|
local read_micro = function(path)
|
||||||
return i_o.read_file(path, nil, '*n') * 0.000001
|
local j = i_o.read_file(path, nil, '*n')
|
||||||
|
if j == nil then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return j * 0.000001
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local gmatch_to_table1 = function(pat, s)
|
local gmatch_to_table1 = function(pat, s)
|
||||||
|
@ -88,10 +93,10 @@ end
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- intel powercap
|
-- intel powercap
|
||||||
|
|
||||||
local SYSFS_RAPL = '/sys/class/powercap'
|
M.SYSFS_RAPL = '/sys/class/powercap'
|
||||||
|
|
||||||
M.intel_powercap_reader = function(dev)
|
M.intel_powercap_reader = function(dev)
|
||||||
local uj = __string_format('%s/%s/energy_uj', SYSFS_RAPL, dev)
|
local uj = __string_format('%s/%s/energy_uj', M.SYSFS_RAPL, dev)
|
||||||
i_o.assert_file_readable(uj)
|
i_o.assert_file_readable(uj)
|
||||||
return function() return read_micro(uj) end
|
return function() return read_micro(uj) end
|
||||||
end
|
end
|
||||||
|
@ -99,10 +104,10 @@ end
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- battery
|
-- battery
|
||||||
|
|
||||||
local SYSFS_POWER = '/sys/class/power_supply'
|
M.SYSFS_POWER = '/sys/class/power_supply'
|
||||||
|
|
||||||
local format_power_path = function(battery, property)
|
local format_power_path = function(battery, property)
|
||||||
local p = __string_format('%s/%s/%s', SYSFS_POWER, battery, property)
|
local p = __string_format('%s/%s/%s', M.SYSFS_POWER, battery, property)
|
||||||
i_o.assert_file_readable(p)
|
i_o.assert_file_readable(p)
|
||||||
return p
|
return p
|
||||||
end
|
end
|
||||||
|
@ -110,7 +115,15 @@ end
|
||||||
M.battery_power_reader = function(battery)
|
M.battery_power_reader = function(battery)
|
||||||
local current = format_power_path(battery, 'current_now')
|
local current = format_power_path(battery, 'current_now')
|
||||||
local voltage = format_power_path(battery, 'voltage_now')
|
local voltage = format_power_path(battery, 'voltage_now')
|
||||||
return function() return read_micro(current) * read_micro(voltage) end
|
return function()
|
||||||
|
local c = read_micro(current)
|
||||||
|
local v = read_micro(voltage)
|
||||||
|
if c == nil or v == nil then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return c * v
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.battery_status_reader = function(battery)
|
M.battery_status_reader = function(battery)
|
||||||
|
@ -172,14 +185,12 @@ end
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- cpu
|
-- cpu
|
||||||
|
|
||||||
-- ASSUME nproc and lscpu will always be available
|
M.get_cpu_number = function(topology)
|
||||||
|
local n = 0
|
||||||
M.get_core_number = function()
|
for g, c in pairs(topology) do
|
||||||
return __tonumber(i_o.read_file('/proc/cpuinfo', 'cpu cores%s+:%s(%d+)'))
|
n = n + g * #c
|
||||||
end
|
end
|
||||||
|
return n
|
||||||
M.get_cpu_number = function()
|
|
||||||
return __tonumber(i_o.execute_cmd('nproc', nil, '*n'))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_coretemp_dir = function()
|
local get_coretemp_dir = function()
|
||||||
|
@ -188,89 +199,134 @@ local get_coretemp_dir = function()
|
||||||
return pure.fmap_maybe(dirname, s)
|
return pure.fmap_maybe(dirname, s)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- map cores to integer values starting at 1; this is necessary since some cpus
|
-- return a table with keys corresponding to physcial core id and values to
|
||||||
-- don't report their core id's as a sequence of integers starting at 0
|
-- the number of threads of each core (usually 1 or 2)
|
||||||
local get_core_id_indexer = function()
|
M.get_core_threads = function()
|
||||||
|
i_o.assert_exe_exists('lscpu')
|
||||||
|
local cmd = 'lscpu -y -p=core | grep -v \'^#\' | sort -k1,1n | uniq -c'
|
||||||
|
local flip = function(c) return {__tonumber(c[2]), __tonumber(c[1])} end
|
||||||
local make_indexer = pure.compose(
|
local make_indexer = pure.compose(
|
||||||
pure.array_to_map,
|
pure.array_to_map,
|
||||||
pure.partial(pure.imap, function(i, c) return {__tonumber(c), i} end),
|
pure.partial(pure.map, flip),
|
||||||
pure.partial(gmatch_to_table1, '(%d+)')
|
pure.partial(gmatch_to_tableN, '(%d+) (%d+)')
|
||||||
)
|
)
|
||||||
return pure.fmap_maybe(
|
return pure.fmap_maybe(make_indexer, i_o.execute_cmd(cmd))
|
||||||
make_indexer,
|
end
|
||||||
i_o.execute_cmd('lscpu -p=CORE | tail -n+5 | sort | uniq')
|
|
||||||
|
local get_coretemp_mapper = function()
|
||||||
|
i_o.assert_exe_exists('grep')
|
||||||
|
local d = get_coretemp_dir()
|
||||||
|
local get_labels = pure.compose(
|
||||||
|
i_o.execute_cmd,
|
||||||
|
pure.partial(__string_format, 'grep Core %s/temp*_label', true)
|
||||||
)
|
)
|
||||||
|
local to_tuple = function(m)
|
||||||
|
return {__tonumber(m[2]), __string_format('%s/%s_input', d, m[1])}
|
||||||
|
end
|
||||||
|
local to_map = pure.compose(
|
||||||
|
pure.array_to_map,
|
||||||
|
pure.partial(pure.map, to_tuple),
|
||||||
|
pure.partial(gmatch_to_tableN, '/([^/\n]+)_label:Core (%d+)\n')
|
||||||
|
)
|
||||||
|
return pure.maybe({}, to_map, pure.fmap_maybe(get_labels, d))
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_core_mappings = function()
|
M.get_core_topology = function()
|
||||||
local ncores = M.get_core_number()
|
i_o.assert_exe_exists('lscpu')
|
||||||
local map_ids = function(indexer)
|
i_o.assert_exe_exists('grep')
|
||||||
local f = function(acc, next)
|
i_o.assert_exe_exists('sort')
|
||||||
local cpu_id = __tonumber(next[1]) + 1
|
local coretemp_paths = get_coretemp_mapper()
|
||||||
local core_id = next[2]
|
local assign_cpu = function(i, x)
|
||||||
local conky_core_idx = indexer[__tonumber(core_id)]
|
return {
|
||||||
acc.mappings[cpu_id] = {
|
lgl_cpu_id = i,
|
||||||
conky_core_idx = conky_core_idx,
|
phy_core_id = __tonumber(x[1]),
|
||||||
conky_thread_id = acc.thread_ids[conky_core_idx],
|
phy_cpu_id = __tonumber(x[2])
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local assign_core = function(acc, next)
|
||||||
|
local g = acc.grouped
|
||||||
|
local max_lgl_core_id = #g
|
||||||
|
local new_phy_core_id = next.phy_core_id
|
||||||
|
local new_cpu = {phy_cpu_id = next.phy_cpu_id, lgl_cpu_id = next.lgl_cpu_id}
|
||||||
|
if acc.prev_phy_core_id == new_phy_core_id then
|
||||||
|
local max_thread = #acc.grouped[max_lgl_core_id].cpus
|
||||||
|
acc.grouped[max_lgl_core_id].cpus[max_thread + 1] = new_cpu
|
||||||
|
else
|
||||||
|
local new_lgl_core_id = max_lgl_core_id + 1
|
||||||
|
acc.grouped[new_lgl_core_id] = {
|
||||||
|
phy_core_id = new_phy_core_id,
|
||||||
|
lgl_core_id = new_lgl_core_id,
|
||||||
|
coretemp_path = coretemp_paths[new_phy_core_id],
|
||||||
|
cpus = {new_cpu}
|
||||||
}
|
}
|
||||||
acc.thread_ids[conky_core_idx] = acc.thread_ids[conky_core_idx] + 1
|
acc.prev_phy_core_id = new_phy_core_id
|
||||||
return acc
|
|
||||||
end
|
end
|
||||||
local cpu_to_core_map = pure.maybe(
|
return acc
|
||||||
{},
|
|
||||||
pure.partial(gmatch_to_tableN, '(%d+),(%d+)'),
|
|
||||||
i_o.execute_cmd('lscpu -p=cpu,CORE | tail -n+5')
|
|
||||||
)
|
|
||||||
local init = {mappings = {}, thread_ids = pure.rep(ncores, 1)}
|
|
||||||
return pure.reduce(f, init, cpu_to_core_map).mappings
|
|
||||||
end
|
end
|
||||||
return pure.fmap_maybe(map_ids, get_core_id_indexer())
|
local get_threads = function(x)
|
||||||
|
return #x.cpus
|
||||||
|
end
|
||||||
|
local f = pure.compose(
|
||||||
|
pure.partial(pure.group_with, get_threads, pure.id),
|
||||||
|
pure.partial(pure.get, 'grouped'),
|
||||||
|
pure.partial(pure.reduce, assign_core, {prev_phy_core_id = -1, grouped = {}}),
|
||||||
|
pure.partial(pure.imap, assign_cpu),
|
||||||
|
pure.partial(gmatch_to_tableN, '(%d+),(%d+)')
|
||||||
|
)
|
||||||
|
local out =
|
||||||
|
i_o.execute_cmd('lscpu -y -p=core,cpu | grep -v \'^#\' | sort -k1,1n')
|
||||||
|
return pure.fmap_maybe(f, out)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.get_coretemp_paths = function()
|
M.topology_to_cpu_map = function(topology)
|
||||||
local get_paths = function(indexer)
|
local r = {}
|
||||||
local d = get_coretemp_dir()
|
for group_id, group in pairs(topology) do
|
||||||
i_o.assert_exe_exists('grep')
|
for _, core in pairs(group) do
|
||||||
local get_labels = pure.compose(
|
for _, cpu in pairs(core.cpus) do
|
||||||
i_o.execute_cmd,
|
r[cpu.lgl_cpu_id] = group_id
|
||||||
pure.partial(__string_format, 'grep Core %s/temp*_label', true)
|
end
|
||||||
)
|
|
||||||
local to_tuple = function(m)
|
|
||||||
return {
|
|
||||||
indexer[__tonumber(m[2])],
|
|
||||||
__string_format('%s/%s_input', d, m[1])
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
local f = pure.compose(
|
|
||||||
pure.array_to_map,
|
|
||||||
pure.partial(pure.map, to_tuple),
|
|
||||||
pure.partial(gmatch_to_tableN, '/([^/\n]+)_label:Core (%d+)\n')
|
|
||||||
)
|
|
||||||
return pure.maybe({}, f, pure.fmap_maybe(get_labels, d))
|
|
||||||
end
|
end
|
||||||
return pure.maybe({}, get_paths, get_core_id_indexer())
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
local match_freq = function(c)
|
M.read_ave_freqs = function(topology, cpu_group_map)
|
||||||
local f = 0
|
|
||||||
local n = 0
|
|
||||||
for s in __string_gmatch(c, '(%d+%.%d+)') do
|
|
||||||
f = f + __tonumber(s)
|
|
||||||
n = n + 1
|
|
||||||
end
|
|
||||||
return __string_format('%.0f Mhz', f / n)
|
|
||||||
end
|
|
||||||
|
|
||||||
M.read_freq = function()
|
|
||||||
-- NOTE: Using the builtin conky functions for getting cpu freq seems to make
|
-- NOTE: Using the builtin conky functions for getting cpu freq seems to make
|
||||||
-- the entire loop jittery due to high variance latency. Querying
|
-- the entire loop jittery due to high variance latency. Querying
|
||||||
-- scaling_cur_freq in sysfs seems to do the same thing. It appears lscpu
|
-- scaling_cur_freq in sysfs seems to do the same thing. It appears lscpu
|
||||||
-- (which queries /proc/cpuinfo) is much faster and doesn't have this jittery
|
-- (which queries /proc/cpuinfo) is much faster and doesn't have this jittery
|
||||||
-- problem.
|
-- problem.
|
||||||
return pure.maybe('N/A', match_freq, i_o.execute_cmd('lscpu -p=MHZ'))
|
i_o.assert_exe_exists('lscpu')
|
||||||
|
local out = i_o.execute_cmd('lscpu -p=MHZ')
|
||||||
|
local init_freqs = function(v)
|
||||||
|
local r = {}
|
||||||
|
for group_id, _ in pairs(topology) do
|
||||||
|
r[group_id] = v
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
if out == nil then
|
||||||
|
return init_freqs('N/A')
|
||||||
|
else
|
||||||
|
local ave_freqs = init_freqs(0)
|
||||||
|
local cpu_id = 1
|
||||||
|
for s in __string_gmatch(out, '(%d+%.%d+)') do
|
||||||
|
local group_id = cpu_group_map[cpu_id]
|
||||||
|
ave_freqs[group_id] = ave_freqs[group_id] + __tonumber(s)
|
||||||
|
cpu_id = cpu_id + 1
|
||||||
|
end
|
||||||
|
for group_id, _ in pairs(ave_freqs) do
|
||||||
|
ave_freqs[group_id] =
|
||||||
|
__string_format(
|
||||||
|
'%.0f Mhz',
|
||||||
|
ave_freqs[group_id] / (group_id * #topology[group_id])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return ave_freqs
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.get_hwp_paths = function()
|
M.get_hwp_paths = function(topology)
|
||||||
-- ASSUME this will never fail
|
-- ASSUME this will never fail
|
||||||
return pure.map_n(
|
return pure.map_n(
|
||||||
function(i)
|
function(i)
|
||||||
|
@ -278,7 +334,7 @@ M.get_hwp_paths = function()
|
||||||
.. (i - 1)
|
.. (i - 1)
|
||||||
.. '/cpufreq/energy_performance_preference'
|
.. '/cpufreq/energy_performance_preference'
|
||||||
end,
|
end,
|
||||||
M.get_cpu_number()
|
M.get_cpu_number(topology)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -306,35 +362,33 @@ M.read_hwp = function(hwp_paths)
|
||||||
return mixed and 'Mixed' or (HWP_MAP[hwp_pref] or 'Unknown')
|
return mixed and 'Mixed' or (HWP_MAP[hwp_pref] or 'Unknown')
|
||||||
end
|
end
|
||||||
|
|
||||||
M.init_cpu_loads = function()
|
M.init_cpu_loads = function(topo)
|
||||||
local m = get_core_mappings()
|
local ncpus = M.get_cpu_number(topo)
|
||||||
local cpu_loads = {}
|
local cpu_loads = {}
|
||||||
for cpu_id, core in pairs(m) do
|
for lgl_cpu_id = 1, ncpus do
|
||||||
cpu_loads[cpu_id] = {
|
cpu_loads[lgl_cpu_id] = {
|
||||||
active_prev = 0,
|
active_prev = 0,
|
||||||
total_prev = 0,
|
total_prev = 0,
|
||||||
percent_active = 0,
|
percent_active = 0,
|
||||||
conky_core_idx = core.conky_core_idx,
|
|
||||||
conky_thread_id = core.conky_thread_id,
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
return cpu_loads
|
return cpu_loads
|
||||||
end
|
end
|
||||||
|
|
||||||
M.read_cpu_loads = function(cpu_loads)
|
M.read_cpu_loads = function(cpu_loads)
|
||||||
local ncpus = #cpu_loads
|
|
||||||
local iter = io.lines('/proc/stat')
|
local iter = io.lines('/proc/stat')
|
||||||
iter() -- ignore first line
|
iter() -- ignore first line
|
||||||
for i = 1, ncpus do
|
for lgl_cpu_id = 1, #cpu_loads do
|
||||||
local ln = iter()
|
local ln = iter()
|
||||||
local user, system, idle = __string_match(ln, '%d+ (%d+) %d+ (%d+) (%d+)', 4)
|
local user, system, idle =
|
||||||
|
__string_match(ln, '%d+ (%d+) %d+ (%d+) (%d+)', 4)
|
||||||
local active = user + system
|
local active = user + system
|
||||||
local total = active + idle
|
local total = active + idle
|
||||||
local c = cpu_loads[i]
|
local cpu = cpu_loads[lgl_cpu_id]
|
||||||
if total > c.total_prev then -- guard against 1/0 errors
|
if total > cpu.total_prev then -- guard against 1/0 errors
|
||||||
c.percent_active = (active - c.active_prev) / (total - c.total_prev)
|
cpu.percent_active = (active - cpu.active_prev) / (total - cpu.total_prev)
|
||||||
c.active_prev = active
|
cpu.active_prev = active
|
||||||
c.total_prev = total
|
cpu.total_prev = total
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return cpu_loads
|
return cpu_loads
|
||||||
|
|
|
@ -0,0 +1,257 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local err = require 'err'
|
||||||
|
local i_o = require 'i_o'
|
||||||
|
local pure = require 'pure'
|
||||||
|
local sys = require 'sys'
|
||||||
|
|
||||||
|
M.assert_non_nil = function(what, x)
|
||||||
|
i_o.assertf(x ~= nil, '%s: %f must not be nil', what, x)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_number = function(what, x)
|
||||||
|
i_o.assertf(err.get_type(x) == 'number', '%s: \'%s\' must be a number', what, x)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_integer = function(what, x)
|
||||||
|
M.assert_number(what, x)
|
||||||
|
i_o.assertf(math.fmod(x, 1) == 0, '%s: \'%s\' must be an integer', what, x)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_less_than = function(what, x, upper)
|
||||||
|
M.assert_number(what, x)
|
||||||
|
i_o.assertf(
|
||||||
|
x < upper,
|
||||||
|
'%s: %f must be less than %f',
|
||||||
|
what, x, upper
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_greater_than = function(what, x, lower)
|
||||||
|
M.assert_number(what, x)
|
||||||
|
i_o.assertf(
|
||||||
|
x > lower,
|
||||||
|
'%s: %f must be greater than %f',
|
||||||
|
what, x, lower
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_less_than_or_eq = function(what, x, upper)
|
||||||
|
M.assert_number(what, x)
|
||||||
|
i_o.assertf(
|
||||||
|
x <= upper,
|
||||||
|
'%s: %f must be less than or equal to %f',
|
||||||
|
what, x, upper
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_greater_than_or_eq = function(what, x, lower)
|
||||||
|
M.assert_number(what, x)
|
||||||
|
i_o.assertf(
|
||||||
|
x >= lower,
|
||||||
|
'%s: %f must be greater than or equal to %f',
|
||||||
|
what, x, lower
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_positive = function(what, x)
|
||||||
|
M.assert_greater_than(what, x, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_between = function(what, x, lower, upper)
|
||||||
|
M.assert_number(what, x)
|
||||||
|
i_o.assertf(
|
||||||
|
x >= lower and x <= upper,
|
||||||
|
'%s: %f must be between %f and %f',
|
||||||
|
what, x, lower, upper
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_member = function(what, x, seq)
|
||||||
|
M.assert_non_nil(what, x)
|
||||||
|
local xs = pure.collapse(pure.keys(seq), ', ')
|
||||||
|
i_o.assertf(pure.member(x, seq), '%s: %s is not one of %s', what, x, xs)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_unit_fraction = function(what, x)
|
||||||
|
M.assert_number(what, x)
|
||||||
|
M.assert_between(what, x, 0, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_color = function(what, x)
|
||||||
|
M.assert_number(what, x)
|
||||||
|
M.assert_between(what, x, 0, 0xffffff)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_color_alpha = function(what, x)
|
||||||
|
M.assert_color(what..'.color', x.color)
|
||||||
|
M.assert_unit_fraction(what..'.alpha', x.alpha)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_color_stop = function(what, x)
|
||||||
|
M.assert_color(what..'.color', x.color)
|
||||||
|
M.assert_unit_fraction(what..'.stop', x.stop)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_color_stop_alpha = function(what, x)
|
||||||
|
M.assert_color(what..'.color', x.color)
|
||||||
|
M.assert_unit_fraction(what..'.stop', x.stop)
|
||||||
|
M.assert_unit_fraction(what..'.alpha', x.alpha)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_file_exists = function(what, path)
|
||||||
|
i_o.assertf(i_o.file_exists(path), '%s: %s does not exist', what, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_file_readable = function(what, path)
|
||||||
|
M.assert_file_exists(what, path)
|
||||||
|
i_o.assertf(i_o.file_readable(path), '%s: %s is not readable', what, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_valid_pattern = function(what, pat)
|
||||||
|
local t = pat.type
|
||||||
|
if t == "RGB" then
|
||||||
|
M.assert_color(what, pat.data)
|
||||||
|
elseif t == "RGBA" then
|
||||||
|
M.assert_color_alpha(what, pat.data)
|
||||||
|
elseif t == "GradientRGB" then
|
||||||
|
for _, x in pairs(pat.data) do
|
||||||
|
M.assert_color_stop(what, x)
|
||||||
|
end
|
||||||
|
elseif t == "GradientRGBA" then
|
||||||
|
for _, x in pairs(pat.data) do
|
||||||
|
M.assert_color_stop_alpha(what, x)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
i_o.assertf(nil, '%s: pattern has invalid type \'%s\'', what, t)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.validate_plot_ticks_y = function(what, x)
|
||||||
|
M.assert_greater_than(what..'.plot.ticks_y', x, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO make spacing parameters aware of thickness, right now they start at the
|
||||||
|
-- centry of lines, circles, etc.
|
||||||
|
|
||||||
|
local validate_filesystem = function(x)
|
||||||
|
for _, f in pairs(x.fs_paths) do
|
||||||
|
M.assert_file_exists('filesystem.fs_paths', f.path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local validate_graphics = function(x)
|
||||||
|
-- NOTE don't check that the bus exists because some optimus laptops 'turn
|
||||||
|
-- off' by pulling the entire kernel module and making the bus go poof
|
||||||
|
M.validate_plot_ticks_y('graphics.geometry', x.geometry.plot.ticks_y)
|
||||||
|
end
|
||||||
|
|
||||||
|
local validate_memory = function(x)
|
||||||
|
M.validate_plot_ticks_y('memory.geometry', x.geometry.plot.ticks_y)
|
||||||
|
end
|
||||||
|
|
||||||
|
local validate_network = function(x)
|
||||||
|
M.validate_plot_ticks_y('network.geometry', x.geometry.plot.ticks_y)
|
||||||
|
end
|
||||||
|
|
||||||
|
local validate_pacman = function(_)
|
||||||
|
-- nothing to check here
|
||||||
|
end
|
||||||
|
|
||||||
|
local validate_power = function(x)
|
||||||
|
M.assert_file_readable('power.battery', sys.SYSFS_POWER..'/'..x.battery)
|
||||||
|
for _, f in pairs(x.rapl_specs) do
|
||||||
|
M.assert_file_readable('power.rapl_specs', sys.SYSFS_RAPL..'/'..f.address)
|
||||||
|
end
|
||||||
|
M.assert_greater_than('power.genometry.plot.ticks_y', x.geometry.plot.ticks_y, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local validate_processor = function(x)
|
||||||
|
-- NOTE need to check that the processor groups actually exist after querying
|
||||||
|
-- the cpu in the module
|
||||||
|
M.assert_greater_than('processor.genometry.plot.ticks_y', x.geometry.plot.ticks_y, 2)
|
||||||
|
for _, c in pairs(x.core_groups) do
|
||||||
|
M.assert_greater_than('processor.core_groups.[].rows', c.rows, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local validate_readwrite = function(x)
|
||||||
|
M.validate_plot_ticks_y('readwrite.geometry', x.geometry.plot.ticks_y)
|
||||||
|
end
|
||||||
|
|
||||||
|
local validate_system = function(_)
|
||||||
|
-- nothing to check here
|
||||||
|
end
|
||||||
|
|
||||||
|
M.validate_config = function(config)
|
||||||
|
local boot = config.bootstrap
|
||||||
|
M.assert_positive('bootstrap.update_interval', boot.update_interval)
|
||||||
|
M.assert_positive('bootstrap.dimensions.x', boot.dimensions.x)
|
||||||
|
M.assert_positive('bootstrap.dimensions.y', boot.dimensions.y)
|
||||||
|
|
||||||
|
local font = config.theme.font.sizes
|
||||||
|
local _font = 'theme.font.sizes'
|
||||||
|
M.assert_positive(_font..'normal', font.normal)
|
||||||
|
M.assert_positive(_font..'plot_label', font.plot_label)
|
||||||
|
M.assert_positive(_font..'table', font.table)
|
||||||
|
M.assert_positive(_font..'header', font.header)
|
||||||
|
|
||||||
|
local geo = config.theme.geometry
|
||||||
|
local _geo = 'theme.geometry.'
|
||||||
|
M.assert_greater_than(_geo..'plot.seconds', geo.plot.seconds, 10)
|
||||||
|
M.assert_greater_than(_geo..'plot.ticks_x', geo.plot.ticks_x, 4)
|
||||||
|
M.assert_greater_than(_geo..'table.name_chars', geo.table.name_chars, 5)
|
||||||
|
M.assert_greater_than(_geo..'table.row_spacing', geo.table.row_spacing, 10)
|
||||||
|
|
||||||
|
local pat = config.theme.patterns
|
||||||
|
local _pat = 'theme.pattern.'
|
||||||
|
M.assert_valid_pattern(_pat..'header', pat.header)
|
||||||
|
M.assert_valid_pattern(_pat..'panel.bg', pat.panel.bg)
|
||||||
|
M.assert_valid_pattern(_pat..'text.active', pat.text.active)
|
||||||
|
M.assert_valid_pattern(_pat..'text.inactive', pat.text.inactive)
|
||||||
|
M.assert_valid_pattern(_pat..'text.critical', pat.text.critical)
|
||||||
|
M.assert_valid_pattern(_pat..'border', pat.border)
|
||||||
|
M.assert_valid_pattern(_pat..'plot.grid', pat.plot.grid)
|
||||||
|
M.assert_valid_pattern(_pat..'plot.outline', pat.plot.outline)
|
||||||
|
M.assert_valid_pattern(_pat..'plot.data.border', pat.plot.data.border)
|
||||||
|
M.assert_valid_pattern(_pat..'plot.data.fill', pat.plot.data.fill)
|
||||||
|
M.assert_valid_pattern(_pat..'indicator.bg', pat.indicator.bg)
|
||||||
|
M.assert_valid_pattern(_pat..'indicator.fg.active', pat.indicator.fg.active)
|
||||||
|
M.assert_valid_pattern(_pat..'indicator.fg.critical', pat.indicator.fg.critical)
|
||||||
|
|
||||||
|
for _, panel in pairs(config.layout.panels) do
|
||||||
|
if type(panel) == "table" then
|
||||||
|
for _, column in pairs(panel.columns) do
|
||||||
|
if type(column) == "table" then
|
||||||
|
for _, block in pairs(column.blocks) do
|
||||||
|
if type(block) == "table" then
|
||||||
|
local t = block.type
|
||||||
|
local d = block.data
|
||||||
|
if t == "filesystem" then
|
||||||
|
validate_filesystem(d)
|
||||||
|
elseif t == "graphics" then
|
||||||
|
validate_graphics(d)
|
||||||
|
elseif t == "memory" then
|
||||||
|
validate_memory(d)
|
||||||
|
elseif t == "network" then
|
||||||
|
validate_network(d)
|
||||||
|
elseif t == "pacman" then
|
||||||
|
validate_pacman(d)
|
||||||
|
elseif t == "power" then
|
||||||
|
validate_power(d)
|
||||||
|
elseif t == "processor" then
|
||||||
|
validate_processor(d)
|
||||||
|
elseif t == "readwrite" then
|
||||||
|
validate_readwrite(d)
|
||||||
|
elseif t == "system" then
|
||||||
|
validate_system(d)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -130,6 +130,7 @@ M.get_delta_y = function(y_align, font)
|
||||||
local fe = set_font_extents(font)
|
local fe = set_font_extents(font)
|
||||||
local descents = {
|
local descents = {
|
||||||
top = fe.height,
|
top = fe.height,
|
||||||
|
-- TODO this 92 thing shouldn't be hardcoded (probably)
|
||||||
center = 0.92 * fe.height * 0.5 - fe.descent,
|
center = 0.92 * fe.height * 0.5 - fe.descent,
|
||||||
bottom = -fe.descent
|
bottom = -fe.descent
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue