Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
HttpStatistics.cs
1using System;
2using System.Collections.Generic;
3using System.Net;
4using System.Text;
5using System.Threading.Tasks;
10using Waher.Script;
12using Waher.Things;
15
17{
18 public class HttpStatistics : ICommand
19 {
20 [Page(1, "Time", 150)]
21 [Header(2, "From:")]
22 [ToolTip(3, "Search for records from this point in time.")]
23 public DateTime From = DateTime.MinValue;
24
25 [Page(1, "Time", 150)]
26 [Header(4, "To:")]
27 [ToolTip(5, "Search for records to this point in time.")]
28 public DateTime To = DateTime.MaxValue;
29
30 public string CommandID => "Statistics";
31 public CommandType Type => CommandType.Query;
32 public string SortCategory => "Protocols";
33 public string SortKey => "Statistics";
34
35 public Task<string> GetNameAsync(Language Language)
36 {
37 return Language.GetStringAsync(typeof(HttpStatistics), 6, "Statistics...");
38 }
39
40 public Task<bool> CanExecuteAsync(RequestOrigin Caller)
41 {
42 return XmppServerModule.IsAdmin(Caller.From);
43 }
44
45 public ICommand Copy()
46 {
47 return new HttpStatistics()
48 {
49 From = this.From,
50 To = this.To
51 };
52 }
53
54 public Task ExecuteCommandAsync()
55 {
56 throw new NotSupportedException();
57 }
58
60 {
61 return Task.FromResult(string.Empty);
62 }
63
65 {
66 return Task.FromResult(string.Empty);
67 }
68
70 {
71 return Task.FromResult(string.Empty);
72 }
73
75 {
76 this.Execute(Query, Language);
77 return Task.CompletedTask;
78 }
79
80 private async void Execute(Query Query, Language Language)
81 {
82 try
83 {
84 await Query.Start();
85 await Query.SetTitle(await Language.GetStringAsync(typeof(HttpStatistics), 7, "Requests"));
86
87 if (this.To < this.From)
88 {
89 DateTime Temp = this.To;
90 this.To = this.From;
91 this.From = Temp;
92 }
93
94 List<Filter> Filters = new List<Filter>();
95 IEnumerable<HttpStatistic> Records;
96
97 if (this.From > DateTime.MinValue)
98 Filters.Add(new FilterFieldGreaterOrEqualTo("Timestamp", this.From));
99
100 if (this.To < DateTime.MaxValue)
101 Filters.Add(new FilterFieldLesserOrEqualTo("Timestamp", this.To));
102
103 await Query.SetStatus(await Language.GetStringAsync(typeof(HttpStatistics), 8, "Searching database..."));
104
105 switch (Filters.Count)
106 {
107 case 0:
108 Records = await Database.Find<HttpStatistic>("Timestamp");
109 break;
110
111 case 1:
112 Records = await Database.Find<HttpStatistic>(Filters[0], "Timestamp");
113 break;
114
115 default:
116 Records = await Database.Find<HttpStatistic>(new FilterAnd(Filters.ToArray()), "Timestamp");
117 break;
118 }
119
120 string Header = await Language.GetStringAsync(typeof(HttpStatistics), 9, "HTTP Statistics");
121 await Query.BeginSection(Header);
122
123 int NrRecords = 0;
124
125 foreach (HttpStatistic Record in Records)
126 NrRecords++;
127
128 const int MaxRecords = 50;
129 bool ShowTable = NrRecords <= MaxRecords;
130
131 if (ShowTable)
132 {
133 await Query.NewTable("Statistics", Header, new Column[]
134 {
135 new Column("Start", await Language.GetStringAsync(typeof(HttpStatistics), 10, "Start"),
136 null, null, null, null, ColumnAlignment.Left, null),
137 new Column("Timestamp", await Language.GetStringAsync(typeof(HttpStatistics), 11, "Timestamp"),
138 null, null, null, null, ColumnAlignment.Left, null),
139 new Column("Requests", await Language.GetStringAsync(typeof(HttpStatistics), 12, "#Requests"),
140 null, null, null, null, ColumnAlignment.Left, null),
141 new Column("Rx", await Language.GetStringAsync(typeof(HttpStatistics), 13, "#Rx"),
142 null, null, null, null, ColumnAlignment.Left, null),
143 new Column("Tx", await Language.GetStringAsync(typeof(HttpStatistics), 14, "#Tx"),
144 null, null, null, null, ColumnAlignment.Left, null)
145 });
146 }
147 else
148 {
149 StringBuilder Markdown = new StringBuilder();
150
151 Markdown.Append(await Language.GetStringAsync(typeof(HttpStatistics), 18, "Number of records:"));
152 Markdown.Append(" **");
153 Markdown.Append(NrRecords.ToString());
154 Markdown.Append("**. (");
155 Markdown.Append((await Language.GetStringAsync(typeof(HttpStatistics), 19, "Table shown if %0% records or less.")).Replace("%0%", MaxRecords.ToString()));
156 Markdown.Append(")");
157
158 await Query.NewObject(new MarkdownContent(Markdown.ToString()));
159 }
160
161 SortedDictionary<string, double> PerMethod = new SortedDictionary<string, double>();
162 SortedDictionary<string, double> PerUserAgent = new SortedDictionary<string, double>();
163 SortedDictionary<string, double> PerFrom = new SortedDictionary<string, double>();
164 SortedDictionary<string, double> PerResource = new SortedDictionary<string, double>();
165 List<DateTime> Timestamps = new List<DateTime>();
166 List<double> NrCalls = new List<double>();
167 List<double> NrRx = new List<double>();
168 List<double> NrTx = new List<double>();
169 double MaxNrCalls = 0;
170 double MaxNrRx = 0;
171 double MaxNrTx = 0;
172 double DivisorNrCalls = 1;
173 double DivisorNrRx = 1;
174 double DivisorNrTx = 1;
175 string PrefixNrCalls = string.Empty;
176 string PrefixNrRx = string.Empty;
177 string PrefixNrTx = string.Empty;
178
179 foreach (HttpStatistic Rec in Records)
180 {
181 if (ShowTable)
182 {
183 await Query.NewRecords("Statistics", new Record(new object[]
184 {
185 Rec.Start,
186 Rec.Timestamp,
187 Rec.NrCalls,
188 Rec.NrBytesRx,
189 Rec.NrBytesTx
190 }));
191 }
192
193 Timestamps.Add(Rec.Timestamp);
194 NrCalls.Add(Rec.NrCalls);
195 NrRx.Add(Rec.NrBytesRx);
196 NrTx.Add(Rec.NrBytesTx);
197
198 if (Rec.NrCalls > MaxNrCalls)
199 MaxNrCalls = Rec.NrCalls;
200
201 if (Rec.NrBytesRx > MaxNrRx)
202 MaxNrRx = Rec.NrBytesRx;
203
204 if (Rec.NrBytesTx > MaxNrTx)
205 MaxNrTx = Rec.NrBytesTx;
206
207 Accumulate(PerMethod, Rec.CallsPerMethod, null);
208 Accumulate(PerUserAgent, Rec.CallsPerUserAgent, (s) =>
209 {
210 StringBuilder sb = new StringBuilder();
211 bool InParenthesis = false;
212 bool Removed = false;
213
214 foreach (char ch in s)
215 {
216 if (InParenthesis)
217 {
218 if (ch == ')')
219 {
220 InParenthesis = false;
221 sb.Append(')');
222 }
223 else if (!Removed)
224 {
225 sb.Append("...");
226 Removed = true;
227 }
228 }
229 else
230 {
231 if (ch >= '0' && ch <= '9')
232 sb.Append('#');
233 else if (ch == '(')
234 {
235 sb.Append('(');
236 InParenthesis = true;
237 Removed = false;
238 }
239 else
240 sb.Append(ch);
241 }
242 }
243
244 return sb.ToString();
245 });
246 Accumulate(PerFrom, Rec.CallsPerFrom, (s) =>
247 {
248 int i, j;
249
250 if (IPAddress.TryParse(s, out _))
251 s = "IP Address";
252 else if ((i = s.IndexOf('@')) > 0 & (j = s.LastIndexOf('/')) > 0 && j > i)
253 s = s.Substring(0, j);
254
255 return s;
256 });
257 Accumulate(PerResource, Rec.CallsPerResource, null);
258 }
259
260 ScaleAmounts(MaxNrCalls, 1000, ref DivisorNrCalls, ref PrefixNrCalls);
261 ScaleAmounts(MaxNrRx, 1024, ref DivisorNrRx, ref PrefixNrRx);
262 ScaleAmounts(MaxNrTx, 1024, ref DivisorNrTx, ref PrefixNrTx);
263
264 if (DivisorNrRx < DivisorNrTx)
265 {
266 DivisorNrRx = DivisorNrTx;
267 PrefixNrRx = PrefixNrTx;
268 }
269
270 if (ShowTable)
271 await Query.TableDone("Statistics");
272
273 await Query.SetStatus(await Language.GetStringAsync(typeof(HttpStatistics), 20, "Preparing Request graph."));
275 "G:=plot2dline(Timestamps,NrCalls/" + DivisorNrCalls.ToString() + ",'Red');" +
276 "G.Title:=Title;" +
277 "G.LabelX:=LabelX;" +
278 "G.LabelY:=LabelY;" +
279 "G",
280 new Variables()
281 {
284 { "Timestamps", Timestamps.ToArray() },
285 { "NrCalls", NrCalls.ToArray() },
286 { "Title", await Language.GetStringAsync(typeof(HttpStatistics), 26, "HTTP Requests over time") },
287 { "LabelX", await Language.GetStringAsync(typeof(HttpStatistics), 1, "Time") },
288 { "LabelY", PrefixNrCalls + await Language.GetStringAsync(typeof(HttpStatistics), 27, "Requests/day") }
289 }));
290
291 await Query.SetStatus(await Language.GetStringAsync(typeof(HttpStatistics), 21, "Preparing transfer graph."));
293 "G:=plot2dline(Timestamps,NrRx/" + DivisorNrTx.ToString() + ",'Blue')+" +
294 "plot2dline(Timestamps,NrTx/" + DivisorNrTx.ToString() + ",'Red');" +
295 "G.Title:=Title;" +
296 "G.LabelX:=LabelX;" +
297 "G.LabelY:=LabelY;" +
298 "G",
299 new Variables()
300 {
303 { "Timestamps", Timestamps.ToArray() },
304 { "NrRx", NrRx.ToArray() },
305 { "NrTx", NrTx.ToArray() },
306 { "Title", await Language.GetStringAsync(typeof(HttpStatistics), 28, "HTTP Bytes Transmitted over time") },
307 { "LabelX", await Language.GetStringAsync(typeof(HttpStatistics), 1, "Time") },
308 { "LabelY", PrefixNrTx + await Language.GetStringAsync(typeof(HttpStatistics), 29, "Bytes/day") }
309 }));
311 "legend(['NrRx','NrTx'],['Blue','Red'],'White',2)"));
312
313 double Height = 100 + 20 * PerMethod.Count;
314 double Scale = Height > 8000 ? Scale = 8000 / Height : 1;
315 double Max = CalcMax(PerMethod);
316 double Divisor = 1;
317 string Prefix = string.Empty;
318 ScaleAmounts(Max, 1000, ref Divisor, ref Prefix);
319
320 await Query.SetStatus(await Language.GetStringAsync(typeof(HttpStatistics), 22, "Preparing method graph."));
322 "G:=horizontalbars(" +
323 "[foreach Label in PerMethod.Keys: Left(Label,50)]," +
324 "[foreach Value in PerMethod.Values: Value/Divisor]);" +
325 "G.Title:=Title;" +
326 "G.LabelX:=LabelX;" +
327 "G.LabelY:=LabelY;" +
328 "G",
329 new Variables()
330 {
332 { Graph.GraphHeightVariableName, Height * Scale },
334 { "Divisor", Divisor },
335 { "PerMethod", PerMethod },
336 { "Title", await Language.GetStringAsync(typeof(HttpStatistics), 30, "Calls per Method") },
337 { "LabelX", Prefix + await Language.GetStringAsync(typeof(HttpStatistics), 31, "Calls") },
338 { "LabelY", await Language.GetStringAsync(typeof(HttpStatistics), 32, "Method") }
339 }));
340
341 Height = 100 + 20 * PerUserAgent.Count;
342 Scale = Height > 8000 ? Scale = 8000 / Height : 1;
343 Max = CalcMax(PerUserAgent);
344 Divisor = 1;
345 Prefix = string.Empty;
346 ScaleAmounts(Max, 1000, ref Divisor, ref Prefix);
347
348 await Query.SetStatus(await Language.GetStringAsync(typeof(HttpStatistics), 23, "Preparing User-Agent graph."));
350 "G:=horizontalbars(" +
351 "[foreach Label in PerUserAgent.Keys: Left(Label,50)]," +
352 "[foreach Value in PerUserAgent.Values: Value/Divisor]);" +
353 "G.Title:=Title;" +
354 "G.LabelX:=LabelX;" +
355 "G.LabelY:=LabelY;" +
356 "G",
357 new Variables()
358 {
360 { Graph.GraphHeightVariableName, Height * Scale },
362 { "Divisor", Divisor },
363 { "PerUserAgent", PerUserAgent },
364 { "Title", await Language.GetStringAsync(typeof(HttpStatistics), 33, "Calls per User-Agent") },
365 { "LabelX", Prefix + await Language.GetStringAsync(typeof(HttpStatistics), 31, "Calls") },
366 { "LabelY", "User-Agent" }
367 }));
368
369 Height = 100 + 20 * PerFrom.Count;
370 Scale = Height > 8000 ? Scale = 8000 / Height : 1;
371 Max = CalcMax(PerFrom);
372 Divisor = 1;
373 Prefix = string.Empty;
374 ScaleAmounts(Max, 1000, ref Divisor, ref Prefix);
375
376 await Query.SetStatus(await Language.GetStringAsync(typeof(HttpStatistics), 24, "Preparing From graph."));
378 "G:=horizontalbars(" +
379 "[foreach Label in PerFrom.Keys: Left(Label,50)]," +
380 "[foreach Value in PerFrom.Values: Value/Divisor]);" +
381 "G.Title:=Title;" +
382 "G.LabelX:=LabelX;" +
383 "G.LabelY:=LabelY;" +
384 "G",
385 new Variables()
386 {
388 { Graph.GraphHeightVariableName, Height * Scale },
390 { "Divisor", Divisor },
391 { "PerFrom", PerFrom },
392 { "Title", await Language.GetStringAsync(typeof(HttpStatistics), 34, "Calls per From") },
393 { "LabelX", Prefix + await Language.GetStringAsync(typeof(HttpStatistics), 31, "Calls") },
394 { "LabelY", "From" }
395 }));
396
397 Height = 100 + 20 * PerResource.Count;
398 Scale = Height > 8000 ? Scale = 8000 / Height : 1;
399 Max = CalcMax(PerResource);
400 Divisor = 1;
401 Prefix = string.Empty;
402 ScaleAmounts(Max, 1000, ref Divisor, ref Prefix);
403
404 await Query.SetStatus(await Language.GetStringAsync(typeof(HttpStatistics), 25, "Preparing resource graph."));
406 "G:=horizontalbars(" +
407 "[foreach Label in PerResource.Keys: Left(Label,50)]," +
408 "[foreach Value in PerResource.Values: Value/Divisor]);" +
409 "G.Title:=Title;" +
410 "G.LabelX:=LabelX;" +
411 "G.LabelY:=LabelY;" +
412 "G",
413 new Variables()
414 {
416 { Graph.GraphHeightVariableName, Height * Scale },
418 { "Divisor", Divisor },
419 { "PerResource", PerResource },
420 { "Title", await Language.GetStringAsync(typeof(HttpStatistics), 35, "Calls per Resource") },
421 { "LabelX", Prefix + await Language.GetStringAsync(typeof(HttpStatistics), 31, "Calls") },
422 { "LabelY", await Language.GetStringAsync(typeof(HttpStatistics), 36, "Resource") }
423 }));
424
425 await Query.SetStatus(string.Empty);
426 await Query.EndSection();
427 }
428 catch (Exception ex)
429 {
430 await Query.LogMessage(ex);
431 }
432 finally
433 {
434 await Query.Done();
435 }
436 }
437
438 private static double CalcMax(SortedDictionary<string, double> Accumulated)
439 {
440 double Result = 0;
441
442 foreach (double d in Accumulated.Values)
443 {
444 if (d > Result)
445 Result = d;
446 }
447
448 return Result;
449 }
450
451 private delegate string ReduceCallback(string s);
452
453 private static void Accumulate(SortedDictionary<string, double> Accumulated, PersistedData.Commands.Statistic[] Statistics, ReduceCallback ReduceEndpoint)
454 {
455 if (!(Statistics is null))
456 {
457 foreach (PersistedData.Commands.Statistic Statistic in Statistics)
458 {
459 string s = Statistic.Name;
460
461 if (!(ReduceEndpoint is null))
462 s = ReduceEndpoint(s);
463
464 if (!Accumulated.TryGetValue(s, out double Count))
465 Count = 0;
466
467 Accumulated[s] = Count + Statistic.Count;
468 }
469 }
470 }
471
472 private static void ScaleAmounts(double Value, double Thousand, ref double Divisor, ref string Prefix)
473 {
474 while (Value > 2000)
475 {
476 switch (Prefix)
477 {
478 case "":
479 Prefix = "k";
480 break;
481
482 case "k":
483 Prefix = "M";
484 break;
485
486 case "M":
487 Prefix = "G";
488 break;
489
490 case "G":
491 Prefix = "T";
492 break;
493
494 case "T":
495 Prefix = "P";
496 break;
497
498 case "P":
499 Prefix = "E";
500 break;
501
502 case "E":
503 Prefix = "Z";
504 break;
505
506 case "Z":
507 Prefix = "Y";
508 break;
509
510 default:
511 return;
512 }
513
514 Divisor *= Thousand;
515 Value /= Thousand;
516 }
517 }
518 }
519}
Class that can be used to encapsulate Markdown to be returned from a Web Service, bypassing any encod...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field greater or equal to a given value.
This filter selects objects that have a named field lesser or equal to a given value.
Contains information about a language.
Definition: Language.cs:17
Task< string > GetStringAsync(Type Type, int Id, string Default)
Gets the string value of a string ID. If no such string exists, a string is created with the default ...
Definition: Language.cs:209
Class managing a script expression.
Definition: Expression.cs:39
static Task< object > EvalAsync(string Script)
Evaluates script, in string format.
Definition: Expression.cs:5488
Base class for graphs.
Definition: Graph.cs:79
const string GraphHeightVariableName
Variable name for graph height
Definition: Graph.cs:108
const string GraphWidthVariableName
Variable name for graph width
Definition: Graph.cs:103
const string GraphLabelFontSizeVariableName
Variable name for graph label font size
Definition: Graph.cs:113
Collection of variables.
Definition: Variables.cs:25
Task< string > GetNameAsync(Language Language)
Gets the name of data source.
Task< string > GetSuccessStringAsync(Language Language)
Gets a success string, if any, of the command. If no specific success string is available,...
Task StartQueryExecutionAsync(Query Query, Language Language)
Starts the execution of a query.
Task< bool > CanExecuteAsync(RequestOrigin Caller)
If the command can be executed by the caller.
ICommand Copy()
Creates a copy of the command object.
Task< string > GetFailureStringAsync(Language Language)
Gets a failure string, if any, of the command. If no specific failure string is available,...
Task< string > GetConfirmationStringAsync(Language Language)
Gets a confirmation string, if any, of the command. If no confirmation is necessary,...
Service Module hosting the XMPP broker and its components.
Defines a column in a table.
Definition: Column.cs:30
Class handling the reception of data from a query.
Definition: Query.cs:12
Task TableDone(string TableId)
Reports a table as being complete.
Definition: Query.cs:318
Task EndSection()
Ends a section. Each call to BeginSection(string) must be followed by a call to EndSection().
Definition: Query.cs:495
Task NewObject(object Object)
Reports a new object.
Definition: Query.cs:345
Task Start()
Starts query execution.
Definition: Query.cs:204
Task NewRecords(string TableId, params Record[] Records)
Reports a new set of records in a table.
Definition: Query.cs:291
Task BeginSection(string Header)
Begins a new section. Sections can be nested. Each call to BeginSection(string) must be followed by a...
Definition: Query.cs:474
Task NewTable(string TableId, string TableName, params Column[] Columns)
Defines a new table in the query output.
Definition: Query.cs:263
Task LogMessage(Exception Exception)
Logs an Exception as a query message.
Definition: Query.cs:372
async Task Done()
Query execution completed.
Definition: Query.cs:232
Task SetStatus(string Status)
Sets the current status of the query execution.
Definition: Query.cs:448
Task SetTitle(string Title)
Sets the title of the report.
Definition: Query.cs:421
Defines a record in a table.
Definition: Record.cs:9
Tokens available in request.
Definition: RequestOrigin.cs:9
string From
Address of caller.
Interface for commands.
Definition: ICommand.cs:32
Prefix
SI prefixes. http://physics.nist.gov/cuu/Units/prefixes.html
Definition: Prefixes.cs:11
ColumnAlignment
Column alignment.
Definition: Column.cs:9
CommandType
Command type.
Definition: ICommand.cs:11