1
1
import csv
2
+ import json
2
3
from collections import OrderedDict
3
4
from contextlib import contextmanager
4
5
from dataclasses import dataclass
16
17
CsvTestConfig ,
17
18
GraphiteTestConfig ,
18
19
HistoStatTestConfig ,
20
+ JsonTestConfig ,
19
21
PostgresMetric ,
20
22
PostgresTestConfig ,
21
23
TestConfig ,
@@ -268,7 +270,7 @@ def fetch_data(self, test_conf: TestConfig, selector: DataSelector = DataSelecto
268
270
# Read metric values. Note we can still fail on conversion to float,
269
271
# because the user is free to override the column selection and thus
270
272
# they may select a column that contains non-numeric data:
271
- for ( name , i ) in zip (metric_names , metric_indexes ):
273
+ for name , i in zip (metric_names , metric_indexes ):
272
274
try :
273
275
data [name ].append (float (row [i ]))
274
276
except ValueError as err :
@@ -498,7 +500,7 @@ def fetch_data(self, test_conf: TestConfig, selector: DataSelector = DataSelecto
498
500
# Read metric values. Note we can still fail on conversion to float,
499
501
# because the user is free to override the column selection and thus
500
502
# they may select a column that contains non-numeric data:
501
- for ( name , i ) in zip (metric_names , metric_indexes ):
503
+ for name , i in zip (metric_names , metric_indexes ):
502
504
try :
503
505
data [name ].append (float (row [i ]))
504
506
except ValueError as err :
@@ -537,19 +539,133 @@ def fetch_all_metric_names(self, test_conf: PostgresTestConfig) -> List[str]:
537
539
return [m for m in test_conf .metrics .keys ()]
538
540
539
541
542
+ class JsonImporter (Importer ):
543
+ def __init__ (self ):
544
+ self ._data = {}
545
+
546
+ @staticmethod
547
+ def _read_json_file (filename : str ):
548
+ try :
549
+ return json .load (open (filename ))
550
+ except FileNotFoundError :
551
+ raise DataImportError (f"Input file not found: { filename } " )
552
+
553
+ def inputfile (self , test_conf : JsonTestConfig ):
554
+ if test_conf .file not in self ._data :
555
+ self ._data [test_conf .file ] = self ._read_json_file (test_conf .file )
556
+ return self ._data [test_conf .file ]
557
+
558
+ def fetch_data (self , test_conf : TestConfig , selector : DataSelector = DataSelector ()) -> Series :
559
+
560
+ if not isinstance (test_conf , JsonTestConfig ):
561
+ raise ValueError ("Expected JsonTestConfig" )
562
+
563
+ # TODO: refactor. THis is copy pasted from CSV importer
564
+ since_time = selector .since_time
565
+ until_time = selector .until_time
566
+
567
+ if since_time .timestamp () > until_time .timestamp ():
568
+ raise DataImportError (
569
+ f"Invalid time range: ["
570
+ f"{ format_timestamp (int (since_time .timestamp ()))} , "
571
+ f"{ format_timestamp (int (until_time .timestamp ()))} ]"
572
+ )
573
+
574
+ time = []
575
+ data = OrderedDict ()
576
+ metrics = OrderedDict ()
577
+ attributes = OrderedDict ()
578
+
579
+ for name in self .fetch_all_metric_names (test_conf ):
580
+ # Ignore metrics if selector.metrics is not None and name is not in selector.metrics
581
+ if selector .metrics is not None and name not in selector .metrics :
582
+ continue
583
+ data [name ] = []
584
+
585
+ attr_names = self .fetch_all_attribute_names (test_conf )
586
+ for name in attr_names :
587
+ attributes [name ] = []
588
+
589
+ # If the user specified a branch, only include results from that branch.
590
+ # Otherwise if the test config specifies a branch, only include results from that branch.
591
+ # Else include all results.
592
+ branch = None
593
+ if selector .branch :
594
+ branch = selector .branch
595
+ elif test_conf .base_branch :
596
+ branch = test_conf .base_branch
597
+
598
+ objs = self .inputfile (test_conf )
599
+ list_of_json_obj = []
600
+ for o in objs :
601
+ if branch and o ["attributes" ]["branch" ] != branch :
602
+ continue
603
+ list_of_json_obj .append (o )
604
+
605
+ for result in list_of_json_obj :
606
+ time .append (result ["timestamp" ])
607
+ for metric in result ["metrics" ]:
608
+ # Skip metrics not in selector.metrics if selector.metrics is enabled
609
+ if metric ["name" ] not in data :
610
+ continue
611
+
612
+ data [metric ["name" ]].append (metric ["value" ])
613
+ metrics [metric ["name" ]] = Metric (1 , 1.0 )
614
+ for a in attr_names :
615
+ attributes [a ] = [o ["attributes" ][a ] for o in list_of_json_obj ]
616
+
617
+ # Leave last n points:
618
+ time = time [- selector .last_n_points :]
619
+ tmp = data
620
+ data = {}
621
+ for k , v in tmp .items ():
622
+ data [k ] = v [- selector .last_n_points :]
623
+ tmp = attributes
624
+ attributes = {}
625
+ for k , v in tmp .items ():
626
+ attributes [k ] = v [- selector .last_n_points :]
627
+
628
+ return Series (
629
+ test_conf .name ,
630
+ branch = None ,
631
+ time = time ,
632
+ metrics = metrics ,
633
+ data = data ,
634
+ attributes = attributes ,
635
+ )
636
+
637
+ def fetch_all_metric_names (self , test_conf : JsonTestConfig ) -> List [str ]:
638
+ metric_names = set ()
639
+ list_of_json_obj = self .inputfile (test_conf )
640
+ for result in list_of_json_obj :
641
+ for metric in result ["metrics" ]:
642
+ metric_names .add (metric ["name" ])
643
+ return [m for m in metric_names ]
644
+
645
+ def fetch_all_attribute_names (self , test_conf : JsonTestConfig ) -> List [str ]:
646
+ attr_names = set ()
647
+ list_of_json_obj = self .inputfile (test_conf )
648
+ for result in list_of_json_obj :
649
+ for a in result ["attributes" ].keys ():
650
+ attr_names .add (a )
651
+ return [m for m in attr_names ]
652
+
653
+
540
654
class Importers :
541
655
__config : Config
542
656
__csv_importer : Optional [CsvImporter ]
543
657
__graphite_importer : Optional [GraphiteImporter ]
544
658
__histostat_importer : Optional [HistoStatImporter ]
545
659
__postgres_importer : Optional [PostgresImporter ]
660
+ __json_importer : Optional [JsonImporter ]
546
661
547
662
def __init__ (self , config : Config ):
548
663
self .__config = config
549
664
self .__csv_importer = None
550
665
self .__graphite_importer = None
551
666
self .__histostat_importer = None
552
667
self .__postgres_importer = None
668
+ self .__json_importer = None
553
669
554
670
def csv_importer (self ) -> CsvImporter :
555
671
if self .__csv_importer is None :
@@ -571,6 +687,11 @@ def postgres_importer(self) -> PostgresImporter:
571
687
self .__postgres_importer = PostgresImporter (Postgres (self .__config .postgres ))
572
688
return self .__postgres_importer
573
689
690
+ def json_importer (self ) -> JsonImporter :
691
+ if self .__json_importer is None :
692
+ self .__json_importer = JsonImporter ()
693
+ return self .__json_importer
694
+
574
695
def get (self , test : TestConfig ) -> Importer :
575
696
if isinstance (test , CsvTestConfig ):
576
697
return self .csv_importer ()
@@ -580,5 +701,7 @@ def get(self, test: TestConfig) -> Importer:
580
701
return self .histostat_importer ()
581
702
elif isinstance (test , PostgresTestConfig ):
582
703
return self .postgres_importer ()
704
+ elif isinstance (test , JsonTestConfig ):
705
+ return self .json_importer ()
583
706
else :
584
707
raise ValueError (f"Unsupported test type { type (test )} " )
0 commit comments