Explicit component inheritance (#132)

diff --git a/README.md b/README.md
index 0c093e0..22a1431 100755
--- a/README.md
+++ b/README.md
@@ -79,8 +79,8 @@
     span.component = Component.Flask
 # the span automatically stops when exiting the `with` context
 
-with context.new_exit_span(op='https://github.com/apache', peer='localhost:8080') as span:
-    span.component = Component.Flask
+with context.new_exit_span(op='https://github.com/apache', peer='localhost:8080', component=Component.Flask) as span:
+    span.tag(Tag(key='Singer', val='Nakajima'))
 
 with context.new_local_span(op='https://github.com/apache') as span:
     span.tag(Tag(key='Singer', val='Nakajima'))
diff --git a/skywalking/plugins/sw_aiohttp.py b/skywalking/plugins/sw_aiohttp.py
index ce3f0a8..c3b7c4d 100644
--- a/skywalking/plugins/sw_aiohttp.py
+++ b/skywalking/plugins/sw_aiohttp.py
@@ -33,9 +33,8 @@
         peer = '%s:%d' % (url.host or '', url.port)
         context = get_context()
 
-        with context.new_exit_span(op=url.path or "/", peer=peer) as span:
+        with context.new_exit_span(op=url.path or "/", peer=peer, component=Component.AioHttp) as span:
             span.layer = Layer.Http
-            span.component = Component.AioHttp
             span.tag(Tag(key=tags.HttpMethod, val=method.upper()))  # pyre-ignore
             span.tag(Tag(key=tags.HttpUrl, val=url))  # pyre-ignore
 
diff --git a/skywalking/plugins/sw_celery.py b/skywalking/plugins/sw_celery.py
index ae19ae8..173b2a5 100644
--- a/skywalking/plugins/sw_celery.py
+++ b/skywalking/plugins/sw_celery.py
@@ -42,9 +42,8 @@
         else:
             peer = '???'
 
-        with get_context().new_exit_span(op=op, peer=peer) as span:
+        with get_context().new_exit_span(op=op, peer=peer, component=Component.Celery) as span:
             span.layer = Layer.MQ
-            span.component = Component.Celery
 
             span.tag(Tag(key=tags.MqBroker, val=broker_url))
             # span.tag(Tag(key=tags.MqTopic, val=exchange))
diff --git a/skywalking/plugins/sw_elasticsearch.py b/skywalking/plugins/sw_elasticsearch.py
index 0d64db4..a7d0327 100644
--- a/skywalking/plugins/sw_elasticsearch.py
+++ b/skywalking/plugins/sw_elasticsearch.py
@@ -28,9 +28,9 @@
     def _sw_perform_request(this: Transport, method, url, headers=None, params=None, body=None):
         context = get_context()
         peer = ",".join([host["host"] + ":" + str(host["port"]) for host in this.hosts])
-        with context.new_exit_span(op="Elasticsearch/" + method + url, peer=peer) as span:
+        with context.new_exit_span(op="Elasticsearch/" + method + url, peer=peer,
+                                   component=Component.Elasticsearch) as span:
             span.layer = Layer.Database
-            span.component = Component.Elasticsearch
             res = _perform_request(this, method, url, headers=headers, params=params, body=body)
 
             span.tag(Tag(key=tags.DbType, val="Elasticsearch"))
diff --git a/skywalking/plugins/sw_flask.py b/skywalking/plugins/sw_flask.py
index 037f6cf..62ffb68 100644
--- a/skywalking/plugins/sw_flask.py
+++ b/skywalking/plugins/sw_flask.py
@@ -41,7 +41,7 @@
         for item in carrier:
             if item.key.capitalize() in req.headers:
                 item.val = req.headers[item.key.capitalize()]
-        with context.new_entry_span(op=req.path, carrier=carrier) as span:
+        with context.new_entry_span(op=req.path, carrier=carrier, inherit=Component.General) as span:
             span.layer = Layer.Http
             span.component = Component.Flask
             span.peer = '%s:%s' % (req.environ["REMOTE_ADDR"], req.environ["REMOTE_PORT"])
diff --git a/skywalking/plugins/sw_kafka.py b/skywalking/plugins/sw_kafka.py
index 201f6a4..2ccd945 100644
--- a/skywalking/plugins/sw_kafka.py
+++ b/skywalking/plugins/sw_kafka.py
@@ -72,10 +72,10 @@
 
         peer = ";".join(this.config["bootstrap_servers"])
         context = get_context()
-        with context.new_exit_span(op="Kafka/" + topic + "/Producer" or "/", peer=peer) as span:
+        with context.new_exit_span(op="Kafka/" + topic + "/Producer" or "/", peer=peer,
+                                   component=Component.KafkaProducer) as span:
             carrier = span.inject()
             span.layer = Layer.MQ
-            span.component = Component.KafkaProducer
 
             if headers is None:
                 headers = []
diff --git a/skywalking/plugins/sw_psycopg2.py b/skywalking/plugins/sw_psycopg2.py
index 01e65d4..27539f7 100644
--- a/skywalking/plugins/sw_psycopg2.py
+++ b/skywalking/plugins/sw_psycopg2.py
@@ -38,9 +38,9 @@
             dsn = self.connection.get_dsn_parameters()
             peer = dsn['host'] + ':' + dsn['port']
 
-            with get_context().new_exit_span(op="PostgreSLQ/Psycopg/execute", peer=peer) as span:
+            with get_context().new_exit_span(op="PostgreSLQ/Psycopg/execute", peer=peer,
+                                             component=Component.Psycopg) as span:
                 span.layer = Layer.Database
-                span.component = Component.Psycopg
 
                 span.tag(Tag(key=tags.DbType, val="PostgreSQL"))
                 span.tag(Tag(key=tags.DbInstance, val=dsn['dbname']))
@@ -60,9 +60,9 @@
             dsn = self.connection.get_dsn_parameters()
             peer = dsn['host'] + ':' + dsn['port']
 
-            with get_context().new_exit_span(op="PostgreSLQ/Psycopg/executemany", peer=peer) as span:
+            with get_context().new_exit_span(op="PostgreSLQ/Psycopg/executemany", peer=peer,
+                                             component=Component.Psycopg) as span:
                 span.layer = Layer.Database
-                span.component = Component.Psycopg
 
                 span.tag(Tag(key=tags.DbType, val="PostgreSQL"))
                 span.tag(Tag(key=tags.DbInstance, val=dsn['dbname']))
@@ -92,9 +92,9 @@
             dsn = self.connection.get_dsn_parameters()
             peer = dsn['host'] + ':' + dsn['port']
 
-            with get_context().new_exit_span(op="PostgreSLQ/Psycopg/callproc", peer=peer) as span:
+            with get_context().new_exit_span(op="PostgreSLQ/Psycopg/callproc", peer=peer,
+                                             component=Component.Psycopg) as span:
                 span.layer = Layer.Database
-                span.component = Component.Psycopg
                 args = '(' + ('' if not parameters else ','.join(parameters)) + ')'
 
                 span.tag(Tag(key=tags.DbType, val="PostgreSQL"))
diff --git a/skywalking/plugins/sw_pymongo.py b/skywalking/plugins/sw_pymongo.py
index 8aae500..0179d34 100644
--- a/skywalking/plugins/sw_pymongo.py
+++ b/skywalking/plugins/sw_pymongo.py
@@ -58,11 +58,10 @@
 
             operation = list(spec.keys())[0]
             sw_op = operation.capitalize() + "Operation"
-            with context.new_exit_span(op="MongoDB/" + sw_op, peer=peer) as span:
+            with context.new_exit_span(op="MongoDB/" + sw_op, peer=peer, component=Component.MongoDB) as span:
                 result = _command(this, dbname, spec, *args, **kwargs)
 
                 span.layer = Layer.Database
-                span.component = Component.MongoDB
                 span.tag(Tag(key=tags.DbType, val="MongoDB"))
                 span.tag(Tag(key=tags.DbInstance, val=dbname))
 
@@ -108,9 +107,8 @@
         context = get_context()
 
         sw_op = "MixedBulkWriteOperation"
-        with context.new_exit_span(op="MongoDB/"+sw_op, peer=peer) as span:
+        with context.new_exit_span(op="MongoDB/"+sw_op, peer=peer, component=Component.MongoDB) as span:
             span.layer = Layer.Database
-            span.component = Component.MongoDB
 
             bulk_result = _execute(this, *args, **kwargs)
 
@@ -143,9 +141,8 @@
         context = get_context()
         op = "FindOperation"
 
-        with context.new_exit_span(op="MongoDB/"+op, peer=peer) as span:
+        with context.new_exit_span(op="MongoDB/"+op, peer=peer, component=Component.MongoDB) as span:
             span.layer = Layer.Database
-            span.component = Component.MongoDB
 
             # __send_message return nothing
             __send_message(this, operation)
diff --git a/skywalking/plugins/sw_pymysql.py b/skywalking/plugins/sw_pymysql.py
index 715f918..4c9edd5 100644
--- a/skywalking/plugins/sw_pymysql.py
+++ b/skywalking/plugins/sw_pymysql.py
@@ -30,9 +30,8 @@
         peer = "%s:%s" % (this.connection.host, this.connection.port)
 
         context = get_context()
-        with context.new_exit_span(op="Mysql/PyMsql/execute", peer=peer) as span:
+        with context.new_exit_span(op="Mysql/PyMsql/execute", peer=peer, component=Component.PyMysql) as span:
             span.layer = Layer.Database
-            span.component = Component.PyMysql
             res = _execute(this, query, args)
 
             span.tag(Tag(key=tags.DbType, val="mysql"))
diff --git a/skywalking/plugins/sw_rabbitmq.py b/skywalking/plugins/sw_rabbitmq.py
index e595d74..5e2468a 100644
--- a/skywalking/plugins/sw_rabbitmq.py
+++ b/skywalking/plugins/sw_rabbitmq.py
@@ -41,10 +41,9 @@
         context = get_context()
         import pika
         with context.new_exit_span(op="RabbitMQ/Topic/" + exchange + "/Queue/" + routing_key + "/Producer" or "/",
-                                   peer=peer) as span:
+                                   peer=peer, component=Component.RabbitmqProducer) as span:
             carrier = span.inject()
             span.layer = Layer.MQ
-            span.component = Component.RabbitmqProducer
             properties = pika.BasicProperties() if properties is None else properties
 
             if properties.headers is None:
diff --git a/skywalking/plugins/sw_redis.py b/skywalking/plugins/sw_redis.py
index 55113bf..b931e49 100644
--- a/skywalking/plugins/sw_redis.py
+++ b/skywalking/plugins/sw_redis.py
@@ -30,9 +30,8 @@
         peer = "%s:%s" % (this.host, this.port)
         op = args[0]
         context = get_context()
-        with context.new_exit_span(op="Redis/"+op or "/", peer=peer) as span:
+        with context.new_exit_span(op="Redis/"+op or "/", peer=peer, component=Component.Redis) as span:
             span.layer = Layer.Cache
-            span.component = Component.Redis
 
             res = _send_command(this, *args, **kwargs)
             span.tag(Tag(key=tags.DbType, val="Redis"))
diff --git a/skywalking/plugins/sw_requests.py b/skywalking/plugins/sw_requests.py
index 69397b5..31aae21 100644
--- a/skywalking/plugins/sw_requests.py
+++ b/skywalking/plugins/sw_requests.py
@@ -43,10 +43,10 @@
                             hooks, stream, verify, cert, json)
 
         context = get_context()
-        with context.new_exit_span(op=url_param.path or "/", peer=url_param.netloc) as span:
+        with context.new_exit_span(op=url_param.path or "/", peer=url_param.netloc,
+                                   component=Component.Requests) as span:
             carrier = span.inject()
             span.layer = Layer.Http
-            span.component = Component.Requests
 
             if headers is None:
                 headers = {}
diff --git a/skywalking/plugins/sw_urllib3.py b/skywalking/plugins/sw_urllib3.py
index 9f3f1cc..0d25c39 100644
--- a/skywalking/plugins/sw_urllib3.py
+++ b/skywalking/plugins/sw_urllib3.py
@@ -31,10 +31,10 @@
         from urllib.parse import urlparse
         url_param = urlparse(url)
         context = get_context()
-        with context.new_exit_span(op=url_param.path or "/", peer=url_param.netloc) as span:
+        with context.new_exit_span(op=url_param.path or "/", peer=url_param.netloc,
+                                   component=Component.Urllib3) as span:
             carrier = span.inject()
             span.layer = Layer.Http
-            span.component = Component.Urllib3
 
             if headers is None:
                 headers = {}
diff --git a/skywalking/plugins/sw_urllib_request.py b/skywalking/plugins/sw_urllib_request.py
index 66d138e..6b0a64e 100644
--- a/skywalking/plugins/sw_urllib_request.py
+++ b/skywalking/plugins/sw_urllib_request.py
@@ -36,10 +36,9 @@
 
         context = get_context()
         url = fullurl.selector.split("?")[0] if fullurl.selector else '/'
-        with context.new_exit_span(op=url, peer=fullurl.host) as span:
+        with context.new_exit_span(op=url, peer=fullurl.host, component=Component.General) as span:
             carrier = span.inject()
             span.layer = Layer.Http
-            span.component = Component.General
             code = None
 
             [fullurl.add_header(item.key, item.val) for item in carrier]
diff --git a/skywalking/trace/context.py b/skywalking/trace/context.py
index 431ff28..07b7734 100644
--- a/skywalking/trace/context.py
+++ b/skywalking/trace/context.py
@@ -15,7 +15,7 @@
 # limitations under the License.
 #
 
-from skywalking import agent, config
+from skywalking import Component, agent, config
 from skywalking.trace import ID
 from skywalking.trace.carrier import Carrier
 from skywalking.trace.segment import Segment, SegmentRef
@@ -91,7 +91,7 @@
             kind=Kind.Local,
         )
 
-    def new_entry_span(self, op: str, carrier: 'Carrier' = None) -> Span:
+    def new_entry_span(self, op: str, carrier: 'Carrier' = None, inherit: Component = None) -> Span:
         span = self.ignore_check(op, Kind.Entry)
         if span is not None:
             return span
@@ -99,19 +99,23 @@
         spans = _spans_dup()
         parent = spans[-1] if spans else None  # type: Span
 
-        span = parent if parent is not None and parent.kind.is_entry else EntrySpan(
-            context=self,
-            sid=self._sid.next(),
-            pid=parent.sid if parent else -1,
-        )
-        span.op = op
+        if parent is not None and parent.kind.is_entry and inherit == parent.component:
+            span = parent
+            span.op = op
+        else:
+            span = EntrySpan(
+                context=self,
+                sid=self._sid.next(),
+                pid=parent.sid if parent else -1,
+                op=op,
+            )
 
-        if carrier is not None and carrier.is_valid:
-            span.extract(carrier=carrier)
+            if carrier is not None and carrier.is_valid:  # TODO: should this be done irrespective of inheritance?
+                span.extract(carrier=carrier)
 
         return span
 
-    def new_exit_span(self, op: str, peer: str) -> Span:
+    def new_exit_span(self, op: str, peer: str, component: Component = None, inherit: Component = None) -> Span:
         span = self.ignore_check(op, Kind.Exit)
         if span is not None:
             return span
@@ -119,13 +123,22 @@
         spans = _spans_dup()
         parent = spans[-1] if spans else None  # type: Span
 
-        span = ExitSpan(
-            context=self,
-            sid=self._sid.next(),
-            pid=parent.sid if parent else -1,
-            op=op,
-            peer=peer,
-        )
+        if parent is not None and parent.kind.is_exit and component == parent.inherit:
+            span = parent
+            span.op = op
+            span.component = component
+        else:
+            span = ExitSpan(
+                context=self,
+                sid=self._sid.next(),
+                pid=parent.sid if parent else -1,
+                op=op,
+                peer=peer,
+                component=component,
+            )
+
+        if inherit:
+            span.inherit = inherit
 
         return span
 
diff --git a/skywalking/trace/span.py b/skywalking/trace/span.py
index 2a473ac..80f000a 100644
--- a/skywalking/trace/span.py
+++ b/skywalking/trace/span.py
@@ -55,6 +55,7 @@
         self.kind = kind  # type: Kind
         self.component = component or Component.Unknown  # type: Component
         self.layer = layer or Layer.Unknown  # type: Layer
+        self.inherit = Component.Unknown  # type: Component
 
         self.tags = []  # type: List[Tag]
         self.logs = []  # type: List[Log]