-
Notifications
You must be signed in to change notification settings - Fork 0
/
sysdelta.pas
325 lines (283 loc) · 9.83 KB
/
sysdelta.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
// Module showing network traffic. Shows how much data has been received (RX) or
// transmitted (TX) since the previous time this script ran.
program netTraffic;
{$modeswitch result+} // Infers result type (I think)
{$mode objfpc}
{$m+}
uses
Dos, Sysutils, Dateutils, Math, md5, StrUtils, RegExpr;
(* Keep track of one file *)
type
TDataPoint = class
private
FSourceFile: String;
FCacheFile: String;
FNowTimestamp: Int64;
FCachedModTimestamp: Int64;
FSource: Int64;
FCached: Int64;
public
constructor Init;
procedure ReadData(src: String);
procedure CacheData;
function GetSource: Integer;
function GetCached: Integer;
function GetDiff: Integer;
function GetDelta: Integer;
end;
type
TStrArray = array of String;
(* Read one line from one file *)
function FileReadLine(FileName: string): string;
var
F: TextFile;
begin
Assign(F, FileName);
Reset(F);
ReadLn(F, result);
Close(F);
end;
(* Write one line to one file *)
procedure FileWriteLine(FileName: string; Store: string);
var
F: TextFile;
begin
Assign(F, FileName);
ReWrite(F);
WriteLn(F, Store);
Close(F);
end;
constructor TDataPoint.Init;
begin
FNowTimestamp := 0;
FCachedModTimestamp := 0;
FSource := 0;
FCached := 0;
end;
procedure TDataPoint.ReadData(Src: String);
begin
FSourceFile := Src;
FCacheFile := GetEnv('XDG_RUNTIME_DIR') + '/sysdelta/' + ReplaceStr(Src, '/', '_');
FNowTimestamp := DateTimeToUnix(Now, false);
FCachedModTimestamp := FileAge(FCacheFile);
FSource := StrToInt(fileReadLine(FSourceFile));
if FileExists(FCacheFile) then
FCached := StrToInt(fileReadLine(FCacheFile));
end;
procedure TDataPoint.CacheData;
begin
ForceDirectories(GetEnv('XDG_RUNTIME_DIR') + '/sysdelta/');
FileWriteLine(FCacheFile, IntToStr(FSource));
end;
function TDataPoint.GetSource: Integer;
begin Result := FSource; end;
function TDataPoint.GetCached: Integer;
begin Result := FCached; end;
function TDataPoint.GetDiff: Integer;
begin Result := FCached - FSource; end;
function TDataPoint.GetDelta: Integer;
var
TimeDiff: Int64;
begin
TimeDiff := FNowTimestamp - FCachedModTimestamp;
if TimeDiff = 0 then TimeDiff := 1;
Result := (FSource - FCached) div TimeDiff;
end;
procedure Die(Code: Integer; Message: string);
begin
WriteLn(Message);
halt(Code);
end;
procedure WriteHelp();
begin
WriteLn('Usage: filedelta <format> file...');
WriteLn('Format: Like pascal string formats. Except placeholder values denotes how to display file data');
WriteLn(' Supports indenting like usual');
WriteLn('Values: %a Plain. Write the file content directly out');
WriteLn(' %b Diff from cache');
WriteLn(' %c Plain. Returns what was cached');
WriteLn(' %d Delta from cache. Default is to show the difference in seconds');
WriteLn(' n if set can scale the result to another units of time');
WriteLn(' For example setting n to 60 makes the unit of time in minutes rather in seconds');
WriteLn(' Sampling (how often to run this program) should somewhat reflect what you set to n');
WriteLn(' as data will become inaccurate if sampling to often');
WriteLn(' %i IEC Byte rounding. Gains one prefix of [KiB, MiB, GiB, ..., YiB]');
WriteLn(' %j IEC Byte rounding + diff');
WriteLn(' %k IEC Byte rounding cached');
WriteLn(' %l IEC Byte rounding + delta');
//WriteLn(' %s Si-Byte rounding. Gains one prefix of [KB, MB, GB, ..., YB');
//WriteLn(' %t Si-Byte rounding + diff');
//WriteLn(' %u Si-Byte rounding + delta');
end;
procedure MaybeHelp(param: String);
var help: boolean = false;
begin
case param of
'-h': help := true;
'help': help := true;
'-help': help := true;
'--help': help := true;
end;
if help then
begin
writeHelp;
halt(0);
end;
end;
(*
Takes an format string and returns a dynamic array of strings,
divided by their format functionality. Strings with no format functionality
is placed in even cells and strings with format functionality is placed in
odd cells. Remember that arrays are zero indexed.
Method will return empty strings inbetween format strings if no contextual
strings are placed between format strings.
*)
function FormatSplitter(const format: String): TStrArray;
var
buf: String = '';
i: Integer = 0;
ptr: Integer = 0;
begin
(* Guess the needed array capacity *)
for i := 1 to Length(format) do
if format[i] = '%' then
ptr := ptr + 2;
SetLength(result, ptr + 1);
(* For loop with mutable i *)
i := 1;
ptr := 0;
while i <= Length(format) do
begin
if (ptr mod 2) = 1 then (* State is odd *)
begin
buf := buf + format[i]; (* Append anything and delim *)
if format[i] in ['A'..'Z', 'a'..'z'] then (* Switch state and flush buffer *)
begin
result[ptr] := buf;
inc(ptr);
buf := '';
end
end (* State is even *)
else if format[i] <> '%' then (* Not format delimeter so safe to append *)
buf := buf + format[i]
else if i = Length(format) then (* Check for out of bounds and throw error*)
raise Exception.Create('Malformed format string. Format contains no rules')
else if format[i+1] = '%' then (* Lookahead for escaped % and append it *)
begin
buf := buf + '%';
Inc(i);
end
else
begin (* Switch state and flush buffer *)
Result[ptr] := buf;
Inc(ptr);
buf := '';
end;
Inc(i);
end;
if buf = '' then
ptr := ptr - 1 (* Move pointer back to last used cell *)
else if (ptr mod 2) = 1 then
raise Exception.Create('Malformed format string. Format has no ending character')
else
Result[ptr] := buf; (* Putting in leftovers from buffer *)
SetLength(Result, ptr + 1); (* Shrink array to actual needed capacity *)
end;
(*
Round a float to a certain number of characters, removing '.'
if no decimal fits
*)
function RoundFloat(X: Double; Len: Integer): string;
var
Buf: String;
begin
Buf := Copy(FloatToStr(X), 1, Len);
if RightStr(Buf, 1)='.' then
begin
Insert(' ', Buf, 1);
SetLength(Buf, Length(Buf) -1);
end;
Result := buf;
end;
(* Rounds up bytes to largest byte power *)
function PostFixBytes(bytes: Int64): string;
const
Suffixes: array of string=('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
var
I: Integer = 0;
begin
while bytes >> (10 * I) >= 1024 do Inc(I);
Result := RoundFloat(bytes / power(1024, i), 5) + Suffixes[I];
end;
var
I: Integer;
DataPoints: array of TDataPoint;
DataPoint: TDataPoint;
DataPtr: Integer = 0;
DataStr: String;
FormArr: TStrArray;
FormArg: String;
FormEnd: String;
begin
if (ParamCount < 1) or (Length(ParamStr(1)) = 0) then
begin
writeHelp;
halt(1);
end;
(* Check for help option and exit *)
for I := 1 to ParamCount do
maybeHelp(ParamStr(I));
(* Initiate array of Data objects with *)
SetLength(DataPoints, ParamCount - 1);
for I := 0 to ParamCount-2 do // Skip format section
begin
DataPoints[I] := TDataPoint.Init;
DataPoints[I].ReadData(ParamStr(I+2));
end;
FormArr := FormatSplitter(ParamStr(1));
for I := 0 to Length(FormArr) -1 do
if (I mod 2) = 0 then
Write(FormArr[I])
else
begin
// TODO add sourcefile indexer functionality for format arguments here
DataPtr := I div 2;
FormArg := '%' + Copy(FormArr[I], 1, Length(FormArr[I]) -1) + 's';
FormEnd := formArr[I][Length(FormArr[I])];
if (FormEnd <> 'n') then
if DataPtr = Length(DataPoints) then
Die(1, format('Insufficent amount of sourcefiles attached. Expected %d. Was %d', [DataPtr+1, DataPtr]))
else
DataPoint := DataPoints[DataPtr]
end
end.
case FormEnd of
'a': DataStr := IntToStr(DataPoint.GetSource);
'b': DataStr := IntToStr(DataPoint.GetDiff);
'c': DataStr := IntToStr(DataPoint.GetCached);
'd': DataStr := IntToStr(DataPoint.GetDelta);
'i': DataStr := PostFixBytes(DataPoint.GetSource);
'j': DataStr := PostFixBytes(DataPoint.GetDiff);
'k': DataStr := PostFixBytes(DataPoint.GetCached);
'l': DataStr := PostFixBytes(DataPoint.GetDelta);
'n': if FormArg = '%s' then
begin
WriteLn;
continue;
end
else Die(1, 'Newline takes no format argument');
// TODO implement SI things
// 's': WriteLn(DataPoint);
// 't': WriteLn(DataPoint);
// 'u': WriteLn(DataPoint);
else
WriteLn;
Die(1, 'Not a legal placeholder value: ' + FormArr[I]);
end;
Write(Format(FormArg, [DataStr]));
end;
// TODO Implement flag to disable caching
(* Save in cache *)
for I := 0 to Length(DataPoints) -1 do
DataPoints[I].CacheData;
end.