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